DEV Community

Cover image for Django ORM: consultas complexas pt.2
Lucas Mateus
Lucas Mateus

Posted on

Django ORM: consultas complexas pt.2

Na parte 1 falamos sobre F, Subquery e Window que são otimos recursos nativos do Django ORM que resolvem muitos problemas complexos sem sair do Python.

Agora chegou a hora de explorar ainda mais os recursos desse ORM.

Testando existência de dados sem JOIN

A classe Exists permite incluir subqueries que retornam True/False na cláusula WHERE ou como anotações, o que pode ser extremamente útil para filtros booleanos complexos. Vejamos o exemplo a seguir onde faremos uso também do que aprendemos na parte 1, o OuterRef.

# Exemplo: clientes com pedidos
from django.db.models import Exists, OuterRef
from pedidos.models import Pedido
from clientes.models import Cliente

pedidos_do_cliente = Pedido.objects.filter(
    cliente=OuterRef('pk')
)

clientes = Cliente.objects.annotate(
    tem_pedido=Exists(pedidos_do_cliente)
).filter(tem_pedido=True)

# ou a versao inline desse codigo
clientes = Cliente.objects.annotate(
    tem_pedido=Exists(
        Pedido.objects.filter(cliente=OuterRef('pk'))
    )
).filter(tem_pedido=True)
Enter fullscreen mode Exit fullscreen mode
-- equivalente em sql
SELECT *
FROM clientes_cliente c
WHERE EXISTS (
    SELECT 1
    FROM pedidos_pedido p
    WHERE p.cliente_id = c.id
);
Enter fullscreen mode Exit fullscreen mode

Usar Exists é mais eficiente em muitos casos porque o banco de dados para de procurar assim que encontra um único registro correspondente, evitando a leitura desnecessária de várias linhas (como ocorreria com um JOIN). Além disso, Exists evita duplicatas na tabela principal, que podem surgir com JOIN 1:N, e geralmente resulta em planos de execução mais rápidos para checagens booleanas simples como "existe ou não".

Lógica condicional

Lógica condicional no SQL permite executar diferentes ações ou retornar diferentes valores com base em condições específicas, funcionando de forma semelhante às estruturas if/else de outras linguagens. Ela é usada principalmente com as expressões CASE, IF e COALESCE (veremos esse logo a frente), permitindo que o SQL avalie condições e retorne resultados dinâmicos conforme os dados da consulta. Isso é útil, por exemplo, para categorizar registros, substituir valores nulos ou criar colunas calculadas com base em regras. Como podem adivinhar, o ORM do django fornece estruturas para aplicarmos essa lógica sem sair do python, as classes Case e When.

# Exemplo: classificar clientes como VIP
from django.db.models import Case, When, Value, CharField

clientes = Cliente.objects.annotate(
    status=Case(
        When(gastos__gt=1000, then=Value('VIP')),
        default=Value('Regular'),
        output_field=CharField()
    )
)
Enter fullscreen mode Exit fullscreen mode
SELECT *,
       CASE
           WHEN gastos > 1000 THEN 'VIP'
           ELSE 'Regular'
       END AS status
FROM clientes_cliente;
Enter fullscreen mode Exit fullscreen mode

Além da propria consulta, essa classes podem ser usadas em ordenções:

from django.db.models import Case, When, Value, IntegerField
from clientes.models import Cliente

clientes = Cliente.objects.order_by(
    Case(
        When(status='VIP', then=Value(0)),
        default=Value(1),
        output_field=IntegerField()
    ),
    'nome'
)
Enter fullscreen mode Exit fullscreen mode

Lidando com valores Nulos

Existem situações onde no banco de dados existem campos que podem ser nulos (ai já é culpa de que projetou o banco rsrs), mas isso pode acabar com outras operações que usem esse campo, o django oferece ferramentas para lidar com isso, a classe Coalesce, onde é feita a verificação se um campo é nulo, e definido um valor padrão para não quebrar os resultados esperados. Vejamos esse exemplo onde queremos somar dois campos numéricos, por exemplo saldo e bonus, mas qualquer um deles pode ser NULL no banco, então vamos definir que se é nulo, o valor será 0 para a operação,e não quebraremos nosso código. Vamos aproveitar e utilizar a classe F, que conhecemos na parte anterior, para acessar os valores que precisamos.

from django.db.models import F
from django.db.models.functions import Coalesce

clientes = Cliente.objects.annotate(
    total=Coalesce(F('saldo'), 0) + Coalesce(F('bonus'), 0)
)
Enter fullscreen mode Exit fullscreen mode
-- equivalente sql
SELECT *,
       COALESCE(saldo, 0) + COALESCE(bonus, 0) AS total
FROM clientes_cliente;
Enter fullscreen mode Exit fullscreen mode

Consultas com lógica condicional dinâmica

a classe Q permite compor filtros com AND, OR, NOT e lógica dinâmica, que é excelente para combinar vários filtros. O Q que é utilizado principalmente quando queremos impor uma negação ou um OR no nosso filtro, já que por padrão o .filter() usa AND.

# Exemplo: busca flexível
from django.db.models import Q

Produto.objects.filter(
    Q(nome__icontains='teclado') | Q(descricao__icontains='teclado')
)
Enter fullscreen mode Exit fullscreen mode
SELECT *
FROM produtos_produto
WHERE LOWER(nome) LIKE '%teclado%' OR LOWER(descricao) LIKE '%teclado%';
Enter fullscreen mode Exit fullscreen mode

O Q geralmente é utilizado quando temos várias opções de filtros que não são obrigatórios, ou seja, dependendo de quais filtros eu escolha, terei diferentes resultados, de forma dinâmica.

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.db.models import Q
from produtos.models import Produto
from produtos.serializers import ProdutoSerializer

class ProdutoBuscaAvancadaAPIView(APIView):
    def get(self, request):
        termo = request.query_params.get('q')
        categoria = request.query_params.get('categoria')
        excluir_gratis = request.query_params.get('excluir_gratis') == 'true'
        destaque = request.query_params.get('destaque') == 'true'

        query = Q()

        if termo:
            query |= Q(nome__icontains=termo) | Q(descricao__icontains=termo)

        if categoria:
            query &= Q(categoria__nome__iexact=categoria)

        if excluir_gratis:
            query &= ~Q(preco=0)

        if destaque:
            query |= Q(status='destaque')

        produtos = Produto.objects.filter(query).distinct()

        serializer = ProdutoSerializer(produtos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

Enter fullscreen mode Exit fullscreen mode

Conclusão

As ferramentas que vimos são subestimadas, mas dominá-las permite escrever queries sofisticadas sem abrir mão da expressividade do ORM. Na maioria dos casos, você não precisa abandonar o ORM. Basta conhecê-lo a fundo. Espero ter agregado conhecimento a quem leu, e trazer mais conteudos em uma proxima ocasião (que seja logo), até mais.

Referências

Todos os conteudos abordados nessa publicação foram feitos com base na própria documentação do Django.

Django Docs - Making Queries
Django Docs - Query Expressions
Django Docs - Conditional Expressions

Top comments (0)