Seção: Dicas Rápidas
Tem um erro que parece pequeno, mas derruba muita análise silenciosamente: usar NOT IN contra uma subquery que devolve pelo menos um NULL.
Na prática, o efeito é frustrante. Você espera listar clientes sem pedido, produtos sem venda ou eventos que não passaram por uma etapa do funil. A query roda, não quebra, mas devolve menos linhas do que deveria, ou até zero. Quem olha rápido acha que o problema está no negócio. Muitas vezes ele está na semântica do NULL.
O erro em 20 segundos
Imagine esta consulta:
SELECT c.id_cliente, c.nome
FROM clientes c
WHERE c.id_cliente NOT IN (
SELECT p.id_cliente
FROM pedidos p
WHERE p.status = 'cancelado'
);
Se pedidos.id_cliente tiver um único NULL entre os cancelados, o filtro pode parar de funcionar como você imagina. O banco não consegue afirmar com segurança que c.id_cliente não está na lista, porque a lista contém um valor desconhecido.
Resultado: linhas legítimas ficam de fora.
O que validar antes de confiar na saída
Antes de usar NOT IN, cheque duas coisas:
- A coluna retornada pela subquery pode vir com
NULL? - A sua intenção real é excluir valores de uma lista ou fazer um anti-join entre tabelas?
Esse passo leva menos de um minuto e evita um monte de conclusão errada em dashboard, análise ad hoc e query de produção.
Sinal verde
O sinal verde aparece quando a subquery garante um conjunto sem NULL ou quando você explicita isso no filtro:
SELECT c.id_cliente, c.nome
FROM clientes c
WHERE c.id_cliente NOT IN (
SELECT p.id_cliente
FROM pedidos p
WHERE p.status = 'cancelado'
AND p.id_cliente IS NOT NULL
);
Aqui a intenção fica coerente: você está comparando ids conhecidos contra ids conhecidos.
Erro comum
O erro mais comum é assumir que NOT IN é sempre o oposto direto de IN. Em SQL, NULL quebra essa intuição porque ele representa valor desconhecido, não vazio e não zero.
Quando a subquery pode retornar NULL, a comparação deixa de ser binária. Não é mais apenas “está na lista” ou “não está na lista”. Passa a existir o terceiro estado: “não sei”. E filtro com resultado desconhecido não entra no WHERE.
Isso costuma aparecer em cenários como:
- chaves estrangeiras opcionais;
- tabelas de eventos com preenchimento inconsistente;
- uniões temporárias feitas sem saneamento;
- dados históricos legados.
Ação rápida
Se o objetivo for anti-join, a troca mais segura costuma ser NOT EXISTS:
SELECT c.id_cliente, c.nome
FROM clientes c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.status = 'cancelado'
AND p.id_cliente = c.id_cliente
);
Essa versão tende a ser mais legível para o caso de negócio e não cai na mesma armadilha do NULL na lista retornada.
Quando usar cada abordagem
Use NOT IN quando você realmente tem uma lista limpa e controlada de valores.
Use NOT EXISTS quando:
- a comparação depende de relacionamento entre tabelas;
- a origem pode trazer
NULL; - a query precisa comunicar claramente que você quer ausência de correspondência.
Não é questão de dogma. É questão de reduzir surpresa operacional.
Leitura prática para analista
Esse tipo de detalhe separa query que “roda” de query que sustenta decisão. Em ambiente real, o maior risco não é um erro de sintaxe. É um resultado aparentemente plausível que passa batido para quem está olhando só o número final.
Se você trabalha com exclusões, reconciliação de bases, segmentos de CRM ou funis operacionais, vale transformar isso em checklist:
- validar
NULLna subquery; - decidir entre
NOT INeNOT EXISTS; - comparar a contagem antes e depois da troca;
- documentar a escolha se a query vai virar ativo recorrente.
Resumo direto
NOT IN com NULL é uma armadilha clássica porque não explode a query. Ele só corrói a confiança na saída.
Quando houver qualquer chance de NULL aparecer na subquery, filtre esse valor explicitamente ou prefira NOT EXISTS. É um ajuste pequeno, mas evita muito falso negativo em análise SQL.
