Pular para o conteúdo
_lvleo21
← Todos os artigos

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.

Referências