DEV Community

Cover image for Django ORM: consultas complexas
Lucas Mateus
Lucas Mateus

Posted on • Edited on

Django ORM: consultas complexas

O ORM do Django é simples e intuitivo no seu uso mas muitos devs acreditam que consultas SQL mais avançadas são demais para essa ferramenta.

A verdade é que o ORM do Django vai muito além do consultas básicas, e nesta publicação vou mostrar como usar recursos poderosos deste ORM para resolver situações reais de forma limpa e performática.

Expressões

Um dev iniciante com o ORM, provavelmente faria uma operação de atualizar um campo dessa seguinte maneira, o que não só requer mais etapas, além de que esse fluxo pode ser problemático, especialmente em situações de concorrência, porque o valor pode mudar entre o SELECT e o UPDATE.

produto = Produto.objects.get(id=1)  # Isso faz um SELECT no banco
produto.estoque -= 1                 # Operação feita em Python
produto.save()                       # Isso faz um UPDATE com o novo valor
Enter fullscreen mode Exit fullscreen mode

A classe F permite referenciar valores de outros campos da mesma linha diretamente no banco, sem precisar carregar os dados pro Python.Com F, a operação ocorre direto no banco de dados de forma segura:

from django.db.models import F

Produto.objects.filter(id=1).update(
    estoque=F('estoque') - 1
)
Enter fullscreen mode Exit fullscreen mode

Essa operação é atômica, ou seja, evita condições de corrida entre processos concorrentes. O Django gera diretamente um UPDATE estoque = estoque - 1 no banco, sem realizar um SELECT antes. Sendo esse o código equivalente em SQL.

UPDATE produto
SET estoque = estoque - 1
WHERE id = 1;
Enter fullscreen mode Exit fullscreen mode

Subconsultas

Como já estamos acostumados a fazer utlizando SQL,Subqueries são ideais quando você precisa anexar dados derivados de outras tabelas. Vejamos essa consulta feita em SQL puro que anexa o último pedido de um cliente a consulta.

SELECT *,
       (SELECT data
        FROM pedidos
        WHERE pedidos.cliente_id = clientes.id
        ORDER BY data DESC
        LIMIT 1) AS data_ultimo_pedido
FROM clientes;
Enter fullscreen mode Exit fullscreen mode

A primeiro momento parece bem chato fazer essa subquery usando somente o ORM, mas vamos ver algumas classes que irão nos ajudar com isso. Subquery e OuterRef são usados juntos para inserir subconsultas SQL dentro de uma consulta principal. O Subquery permite definir uma consulta que será executada para cada linha da consulta externa. Já o OuterRef é usado dentro da subconsulta para referenciar dinamicamente uma coluna da consulta externa (como o ID do cliente no nosso caso). Essa mesma consulta agora feita somente com o Django ORM ficaria assim:

from django.db.models import OuterRef, Subquery
from pedidos.models import Pedido
from clientes.models import Cliente

ultima_data = Pedido.objects.filter(
    cliente=OuterRef('pk')
).order_by('-data').values('data')[:1]

clientes = Cliente.objects.annotate(
    data_ultimo_pedido=Subquery(ultima_data)
)

# ou podemos fazer isso tudo inline
clientes = Cliente.objects.annotate(
    data_ultimo_pedido=Subquery(
        Pedido.objects.filter(
            cliente=OuterRef('pk')
        ).order_by('-data').values('data')[:1]
    )
)
Enter fullscreen mode Exit fullscreen mode

NOTA: Sempre use.values('campo')[:1] nas Subquery() para garantir que a subconsulta retorne exatamente um valor escalar, evitando erros e tornando a consulta válida para ser usada em annotate().

Window functions

Em SQL, window functions (ou funções de janela) são funções que realizam cálculos em um conjunto de linhas relacionando à linha atual sem colapsar o resultado como fazem as funções de agregação tradicionais (SUM, AVG, COUNT, etc). Queries com window functions são úteis para análises que dependem de agrupamentos e ordenações, e o Django já suporta isso de forma nativa.

# ranquear os produtos mais vendidos por categoria
from django.db.models import Window, F
from django.db.models.functions import RowNumber
from produtos.models import Produto

produtos = Produto.objects.annotate(
    rank=Window(
        expression=RowNumber(),
        partition_by=[F('categoria')],
        order_by=F('vendas').desc()
    )
)
Enter fullscreen mode Exit fullscreen mode
--Equivalente em SQL puro
SELECT *,
       ROW_NUMBER() OVER (
           PARTITION BY categoria_id
           ORDER BY vendas DESC
       ) AS rank
FROM produtos_produto;
Enter fullscreen mode Exit fullscreen mode

Conclusão

O Django ORM vai muito além do que parece. Ferramentas como as vistas nesta publicação permitem expressar consultas complexas com clareza e segurança, direto do Python. Em vez de fugir pro SQL cru de cara, tente explorar esses recursos. Muitas vezes, a solução está ao seu alcance e pode deixar o seu código bem mais limpo do que você imagina. Existem ainda mais funções complexas do ORM a serem exploradas, quem sabe não exploro elas numa próxima publicação, até a próxima.

Referências

Você pode dar uma olhada com mais calma nessa e outras várias funções avançadas de consulta, diretamente na documentação do Django:

Django Docs - Query Expressions

Top comments (1)

Collapse
 
elisson profile image
Elisson

Explicação super clara, abriu uma nova perspectiva. Muito bom!