Como ordenar querysets no Django?
Como usar order_by para ordenar em ordem crescente e decrescente, desempatar por mais de um campo, ignorar maiúsculas, controlar onde ficam os valores nulos e definir uma ordem padrão no model.
- django
- python
Publiquei este artigo originalmente no dev.to em agosto de 2024. Nesta versão corrigi o exemplo de desempate, que falava em preços diferentes mas usava datas, e acrescentei ordenação sem diferenciar maiúsculas, valores nulos, campos relacionados e a ordem padrão do model. Os exemplos foram testados com Django 6.1.
Os exemplos usam um model Product com nome, preço, data de criação e categoria.
Ordem ascendente
A ordem ascendente (ascending, em inglês) organiza os itens do menor para o maior: A antes de B, 1 antes de 2, a data mais antiga antes da mais recente. É o padrão do order_by:
Product.objects.order_by("name")
O .all() antes do order_by que aparecia na versão original não é necessário, porque o order_by já está disponível direto no manager.
Ordem descendente
A ordem descendente (descending) faz o contrário, do maior para o menor. Basta colocar um - antes do nome do campo:
Product.objects.order_by("-name")
Desempatando por mais de um campo
O order_by aceita vários campos. O segundo só é usado quando o primeiro empata, o terceiro quando os dois primeiros empatam, e assim por diante. Imagine dois produtos com o mesmo nome e datas de criação diferentes:
| Nome | Data de criação |
|---|---|
| Produto A | 2024-08-01 |
| Produto A | 2024-08-02 |
| Produto B | 2024-08-03 |
| Produto C | 2024-08-04 |
| Produto D | 2024-08-05 |
Para listar por nome e, entre produtos de mesmo nome, mostrar primeiro o mais recente:
Product.objects.order_by("name", "-created_at")
O resultado fica assim:
| Nome | Data de criação |
|---|---|
| Produto A | 2024-08-02 |
| Produto A | 2024-08-01 |
| Produto B | 2024-08-03 |
| Produto C | 2024-08-04 |
| Produto D | 2024-08-05 |
Sem o segundo campo, a ordem entre os dois “Produto A” fica a critério do banco e pode mudar de uma consulta para outra. Isso faz diferença na paginação, em que um item pode aparecer em duas páginas enquanto outro some. Quando o primeiro campo pode repetir, termine a ordenação com um campo único, como o id.
Chamar order_by duas vezes não soma os critérios. A segunda chamada substitui a primeira, então order_by("name").order_by("-created_at") ordena só pela data.
Ignorando maiúsculas e minúsculas
A comparação de texto segue a collation do banco. No SQLite, por exemplo, todas as letras maiúsculas vêm antes das minúsculas, e “produto a” aparece depois de “Produto D”. Para ordenar sem diferenciar maiúsculas de minúsculas, use a função Lower:
from django.db.models.functions import Lower
Product.objects.order_by(Lower("name"), "-created_at")
Para a ordem descendente, use Lower("name").desc().
Onde ficam os valores nulos
Se o campo aceita null, cada banco decide onde colocar os nulos. O PostgreSQL trata o nulo como maior que qualquer valor, então order_by("-price") mostra primeiro os produtos sem preço. O SQLite e o MySQL fazem o oposto. Para ter o mesmo resultado em qualquer banco, diga explicitamente onde os nulos ficam com F:
from django.db.models import F
Product.objects.order_by(F("price").desc(nulls_last=True))
Também existe o nulls_first=True, e os dois funcionam com .asc().
Ordenando por campos relacionados
Para ordenar por um campo de outro model, use __, como nos filtros:
Product.objects.order_by("category__name", "name")
A consulta faz um join com a tabela de categorias e ordena pelo nome da categoria e depois pelo nome do produto.
Ordem padrão no model
Se uma ordem vale para quase todas as consultas, ela pode ir no Meta do model:
class Product(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateField()
class Meta:
ordering = ["name", "-created_at"]
Qualquer order_by na consulta substitui essa ordem, e order_by() sem argumentos remove a ordenação, o que ajuda quando você só precisa de uma contagem ou agregação. Para pegar só o registro mais recente ou o mais antigo, Product.objects.latest("created_at") e earliest("created_at") dispensam escrever o order_by e fatiar o resultado.