从这里的答案可以得出结论,NOT IN(子查询)不能正确处理空值,应该避免使用NOT EXISTS。然而,这样的结论可能为时过早。在下面的场景中,归功于Chris Date(数据库编程与设计,Vol 2 No 9, 1989年9月),正确处理null并返回正确结果的不是NOT In,而不是NOT EXISTS。
考虑一个表sp表示已知按数量(qty)供应零件(pno)的供应商(sno)。该表当前保存以下值:
VALUES ('S1', 'P1', NULL),
('S2', 'P1', 200),
('S3', 'P1', 1000)
注意,数量是可空的,即能够记录已知供应商供应零件的事实,即使不知道数量是多少。
任务是找到已知供应部件编号为“P1”但数量不是1000的供应商。
以下仅使用NOT IN来正确识别供应商“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'
);
然而,下面的查询使用了相同的一般结构,但使用了NOT EXISTS,但错误地将供应商'S1'包含在结果中(即其数量为空):
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
);
所以NOT EXISTS不是它可能出现过的银弹!
当然,问题的根源是空值的存在,因此“真正的”解决方案是消除这些空值。
这可以通过以下两个表来实现(在其他可能的设计中):
已知Sp供应商提供零件
已知SPQ供应商以已知数量供应零件
注意,在SPQ引用sp的地方应该有一个外键约束。
然后可以使用'minus'关系运算符(在标准SQL中是EXCEPT关键字)来获得结果。
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;