Pode-se concluir a partir de respostas aqui que NOT IN (subquery)
não tratam nulos corretamente e devem ser evitadas em favor de NOT EXISTS
. No entanto, essa conclusão pode ser prematura. No cenário a seguir, creditado a Chris Date (Database Programming and Design, Vol. 2, n. 9, setembro de 1989), é NOT IN
que lida com nulos corretamente e retorna o resultado correto, em vez deNOT EXISTS
.
Considere uma tabela sp
para representar os fornecedores ( sno
) que são conhecidos por fornecer peças ( pno
) em quantidade ( qty
). A tabela atualmente possui os seguintes valores:
VALUES ('S1', 'P1', NULL),
('S2', 'P1', 200),
('S3', 'P1', 1000)
Observe que a quantidade é anulável, ou seja, para poder registrar o fato de que um fornecedor é conhecido por fornecer peças, mesmo que não seja conhecido em qual quantidade.
A tarefa é encontrar os fornecedores que sabem o número da peça de fornecimento 'P1', mas não em quantidades de 1000.
Os seguintes usos usam NOT IN
para identificar corretamente apenas o fornecedor 'S2':
WITH sp AS
( SELECT *
FROM ( VALUES ( 'S1', 'P1', NULL ),
( 'S2', 'P1', 200 ),
( 'S3', 'P1', 1000 ) )
AS T ( sno, pno, qty )
)
SELECT DISTINCT spx.sno
FROM sp spx
WHERE spx.pno = 'P1'
AND 1000 NOT IN (
SELECT spy.qty
FROM sp spy
WHERE spy.sno = spx.sno
AND spy.pno = 'P1'
);
No entanto, a consulta abaixo usa a mesma estrutura geral, mas com, NOT EXISTS
mas inclui incorretamente o fornecedor 'S1' no resultado (ou seja, para o qual a quantidade é nula):
WITH sp AS
( SELECT *
FROM ( VALUES ( 'S1', 'P1', NULL ),
( 'S2', 'P1', 200 ),
( 'S3', 'P1', 1000 ) )
AS T ( sno, pno, qty )
)
SELECT DISTINCT spx.sno
FROM sp spx
WHERE spx.pno = 'P1'
AND NOT EXISTS (
SELECT *
FROM sp spy
WHERE spy.sno = spx.sno
AND spy.pno = 'P1'
AND spy.qty = 1000
);
Portanto, NOT EXISTS
não é a bala de prata que pode ter aparecido!
Obviamente, a fonte do problema é a presença de nulos; portanto, a solução 'real' é eliminar esses nulos.
Isso pode ser alcançado (entre outros projetos possíveis) usando duas tabelas:
sp
fornecedores conhecidos por fornecer peças
spq
fornecedores conhecidos por fornecer peças em quantidades conhecidas
observando que provavelmente deve haver uma restrição de chave estrangeira em que as spq
referênciassp
.
O resultado pode ser obtido usando o operador relacional 'menos' (sendo a EXCEPT
palavra - chave no SQL padrão), por exemplo
WITH sp AS
( SELECT *
FROM ( VALUES ( 'S1', 'P1' ),
( 'S2', 'P1' ),
( 'S3', 'P1' ) )
AS T ( sno, pno )
),
spq AS
( SELECT *
FROM ( VALUES ( 'S2', 'P1', 200 ),
( 'S3', 'P1', 1000 ) )
AS T ( sno, pno, qty )
)
SELECT sno
FROM spq
WHERE pno = 'P1'
EXCEPT
SELECT sno
FROM spq
WHERE pno = 'P1'
AND qty = 1000;
NOT IN
para uma série de<> and
mudanças altera o comportamento semântico de não neste conjunto para outra coisa?