从这里的回答可以得出结论,
NOT IN (subquery)
不能正确处理null,应该使用
NOT EXISTS
。然而,这样的结论可能过早。在以下情况下,由Chris Date(数据库编程和设计,第2卷第9期,1989年9月)提供的资料表明,
NOT IN
可以正确处理null并返回正确的结果,而不是
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'(即其数量为null):
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
的外键约束可能会更好。
然后可以使用“减”关系运算符(标准 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;
NOT IN
转换为一系列的<> and
会改变“不在这个集合中”的语义行为,变成其他东西了吗? - Ian Boydnull
的含义解释为“某个未知的值”,那么语义就是一致的:3 in (1, 2, 3, unknown)
是真的,因为你_知道_3在这个组中。3 not in (1, 2, unknown)
既不是真的也不是假的,因为它_可能_在这个组中。“给定1、2和我们不知道实际值的东西,3不在这个集合中吗?”我不确定。 - Hau