Django
QuerySet
order_by
ascending
descending

django order_by query set, ascending and descending

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Ordering query results in Django is simple syntactically, but correctness depends on deterministic sorting strategy. This matters for pagination, feeds, and audit outputs where stable order is required. Good ordering design includes explicit direction, tie-breakers, and null handling.

Basic Ascending and Descending Sorting

Django uses order_by with field names:

  • Ascending: "created_at"
  • Descending: "-created_at"
python
1from myapp.models import Invoice
2
3asc = Invoice.objects.order_by("created_at")
4desc = Invoice.objects.order_by("-created_at")

This generates SQL ORDER BY with expected direction.

Multi-Field Ordering for Stability

If first field has duplicates, pagination can become unstable. Add tie-breaker fields.

python
rows = Invoice.objects.order_by("-priority", "created_at", "id")

Including primary key as final tie-breaker is a common stability pattern.

Null Handling in Ordering

Database engines treat null ordering differently. Django supports explicit null placement through expression ordering.

python
1from django.db.models import F
2
3rows = Invoice.objects.order_by(F("processed_at").asc(nulls_last=True))
4rows2 = Invoice.objects.order_by(F("processed_at").desc(nulls_first=True))

Use explicit null behavior if your application depends on predictable placement.

Ordering Across Relations

You can order by related model fields using double underscores.

python
orders = Invoice.objects.select_related("customer").order_by("customer__name", "-created_at")

Ensure related columns are indexed when used heavily in ordering.

Dynamic Ordering from API Parameters

If sort field comes from request query params, validate against allow-list to avoid invalid field errors.

python
1ALLOWED_SORTS = {
2    "created": "created_at",
3    "amount": "total_amount",
4    "customer": "customer__name",
5}
6
7sort_key = request.GET.get("sort", "created")
8direction = request.GET.get("dir", "desc")
9field = ALLOWED_SORTS.get(sort_key, "created_at")
10
11prefix = "-" if direction == "desc" else ""
12qs = Invoice.objects.order_by(f"{prefix}{field}", "id")

This keeps user-controlled sorting safe and deterministic.

Interaction with distinct

Ordering with distinct can produce database-specific behavior, especially on PostgreSQL with distinct("field"). Keep ordering aligned with distinct fields where required by backend rules.

When in doubt, inspect generated SQL and test on real database backend, not only SQLite.

Performance Considerations

Ordering large datasets can be expensive without indexes. Common improvements:

  • Add indexes on frequently sorted fields.
  • Avoid ordering on computed expressions unless needed.
  • Use pagination with indexed order keys.

For very large tables, keyset pagination can outperform offset pagination with heavy sorting.

Testing Order Semantics

Include tests for:

  • Direction correctness.
  • Stable tie-break behavior.
  • Null placement expectations.
  • API parameter mapping.
python
def test_invoice_ordering_stable(client):
    response = client.get("/api/invoices?sort=created&dir=desc")
    assert response.status_code == 200

Order-related regressions often appear after schema or index changes, so keep these tests in CI.

Keyset Pagination with Deterministic Order

Offset pagination with unstable ordering can skip or duplicate rows when new records arrive between requests. A safer pattern uses keyset pagination with explicit sort key and tie-breaker.

python
1from django.db.models import Q
2
3def next_page(last_created_at, last_id, page_size=20):
4    return (
5        Invoice.objects
6        .filter(Q(created_at__lt=last_created_at) | Q(created_at=last_created_at, id__lt=last_id))
7        .order_by("-created_at", "-id")[:page_size]
8    )

This keeps page boundaries deterministic even under concurrent inserts.

Query Inspection and Index Verification

Use str(queryset.query) to inspect generated SQL and verify that ordering columns have supporting indexes. For large tables, missing indexes on order keys can dominate response latency. Add index migrations for frequent sort fields and validate with database execution plans in staging before production rollout.

python
1qs = Invoice.objects.order_by("-created_at", "id")
2print(str(qs.query))
3
4Add explicit tie-breaker tests in CI to ensure pagination remains stable after schema or index changes.```## Common Pitfalls
5- Ordering by non-unique field without tie-breaker.
6- Assuming null order is same across databases.
7- Accepting raw user-provided field names directly.
8- Ignoring index requirements for heavy ordered queries.
9- Mixing offset pagination with unstable ordering keys.
10
11## Summary
12- Use `order_by` with explicit ascending or descending direction.
13- Add tie-breakers for stable pagination and reproducible results.
14- Control null ordering when behavior matters.
15- Validate dynamic sort parameters with allow-lists.
16- Back ordering strategy with proper indexes and tests.

Course illustration
Course illustration

All Rights Reserved.