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)
-- equivalente em sql
SELECT *
FROM clientes_cliente c
WHERE EXISTS (
SELECT 1
FROM pedidos_pedido p
WHERE p.cliente_id = c.id
);
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()
)
)
SELECT *,
CASE
WHEN gastos > 1000 THEN 'VIP'
ELSE 'Regular'
END AS status
FROM clientes_cliente;
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'
)
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)
)
-- equivalente sql
SELECT *,
COALESCE(saldo, 0) + COALESCE(bonus, 0) AS total
FROM clientes_cliente;
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')
)
SELECT *
FROM produtos_produto
WHERE LOWER(nome) LIKE '%teclado%' OR LOWER(descricao) LIKE '%teclado%';
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)
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)