SQL Server na prática

NOT IN, NOT EXISTS e NULL: erros de anti-join no SQL Server

Entenda por que NOT IN pode retornar zero linhas e escolha um anti-join correto diante de NULL, duplicações e filtros de negócio.

Um relatório procura clientes que nunca fizeram pedidos. Funciona durante meses e de repente não retorna nada. Ainda existem clientes inativos e a consulta não mudou. Um único pedido importado sem referência de cliente pode revelar o problema quando o relatório utiliza NOT IN.

Comparações SQL com NULL produzem desconhecido, além de verdadeiro e falso. WHERE conserva somente as linhas cuja condição é verdadeira. Desconhecido não passa pelo filtro. Compreender isso é mais útil do que decorar uma recomendação genérica contra qualquer expressão IN.

Reproduzir o resultado inesperado

A primeira consulta não retorna clientes. NOT EXISTS retorna 2 e 3. O left join também retorna 2 e 3, pois testa uma coluna que não pode ser NULL em um pedido real. O último resultado conta três pedidos, mas apenas duas referências de cliente preenchidas.

DECLARE @Customers TABLE(CustomerId int NOT NULL PRIMARY KEY);
DECLARE @Orders TABLE(OrderId int NOT NULL, CustomerId int NULL);
INSERT @Customers VALUES (1),(2),(3);
INSERT @Orders VALUES (10,1),(11,NULL),(12,1);

SELECT c.CustomerId
FROM @Customers AS c
WHERE c.CustomerId NOT IN
    (SELECT o.CustomerId FROM @Orders AS o);

SELECT c.CustomerId
FROM @Customers AS c
WHERE NOT EXISTS
(
    SELECT 1 FROM @Orders AS o
    WHERE o.CustomerId=c.CustomerId
);

SELECT c.CustomerId
FROM @Customers AS c
LEFT JOIN @Orders AS o ON o.CustomerId=c.CustomerId
WHERE o.OrderId IS NULL;

SELECT COUNT(*) AS AllOrders,
       COUNT(CustomerId) AS OrdersWithCustomer
FROM @Orders;

Para o cliente 2, NOT IN exige que as comparações de desigualdade com todos os valores da subconsulta sejam satisfeitas. Comparar com 1 resulta verdadeiro; comparar com NULL resulta desconhecido. A condição completa não é verdadeira. O cliente 1 é eliminado pela correspondência real, deixando o resultado vazio.

Remover NULL da subconsulta pode corrigir NOT IN para essa chave externa não anulável. É uma solução legítima quando a nulabilidade faz parte de um contrato documentado. Se a expressão externa passar a aceitar NULL, sua semântica precisa ser reavaliada. NOT EXISTS expressa mais diretamente a ausência de correspondência.

Definir a relação desejada

NOT EXISTS verifica se existe qualquer linha correspondente. Vários pedidos do cliente 1 não duplicam o cliente 2. Entretanto, isso não faz NULL ser igual a NULL. Se dois valores ausentes devem representar uma correspondência de negócio, a igualdade comum na subconsulta não implementa essa regra.

O left join precisa testar uma coluna direita que prove a ausência de uma linha. Uma data de entrega anulável pode estar vazia em um pedido existente. OrderId é declarado NOT NULL no exemplo; seu NULL depois da junção significa que não houve correspondência.

A posição dos filtros também muda o significado. Para localizar clientes sem pedidos pagos, coloque a condição de pagamento dentro de NOT EXISTS ou no ON do left join. Filtrar o estado direito no WHERE externo pode remover justamente as linhas estendidas com NULL que você procurava. Monte uma amostra com pedidos pagos, não pagos e inexistentes.

Verificar o significado antes do plano

Em uma tabela grande, um índice iniciado por Orders.CustomerId pode apoiar a verificação de existência. Se houver filtro de pagamento, considere distribuição e possibilidade de índice filtrado ou composto. Formas SQL diferentes podem produzir operadores anti-semi-join semelhantes. A sintaxe sozinha não determina a mais rápida.

EXCEPT tem semântica de conjunto, remove duplicações e trata NULL como igual na comparação distinta. Não é uma substituição transparente quando multiplicidade ou relacionamentos nulos importam. Adicionar DISTINCT depois de uma junção muitos-para-muitos acidental também pode esconder um erro e acrescentar processamento.

O conjunto de testes deve conter lado direito vazio, uma correspondência, correspondências duplicadas, NULL à direita e chave esquerda anulável quando permitida. Compare linhas esperadas, não apenas contagens. Dois resultados errados podem conter a mesma quantidade de registros.

Quando o defeito aparece após uma importação, investigue também por que a referência ausente foi aceita. Uma consulta corrigida não torna completos os dados de origem. Um anti-join confiável explicita a ausência relevante ao negócio, conserva a semântica dos NULL e só depois recebe otimizações de acesso.

Referências técnicas: Microsoft Learn: IN · Microsoft Learn: EXISTS.

Pergunte sobre este artigo

Tem alguma dúvida sobre este tema?

Conte o que você está avaliando ou onde encontrou dificuldades. Responderemos com uma recomendação prática.

Inquiries are not enabled in this preview.

Fazer uma pergunta sobre este artigo