NOT IN子句和NULL值
这个问题出现时,我得到不同的logging计数为我认为是相同的查询一个使用not in
where
约束和另一个left join
。 not in
约束中的表有一个空值(坏数据),导致查询返回0logging的计数。 我有点理解为什么,但我可以用一些帮助完全理解这个概念。
为了简单说明,为什么查询A返回一个结果,但是B不是?
A: select 'true' where 3 in (1, 2, 3, null) B: select 'true' where 3 not in (1, 2, null)
这是在SQL Server 2005上。我还发现, set ansi_nulls off
会导致B返回一个结果。
查询A与以下内容相同:
select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null
由于3 = 3
是真的,你会得到一个结果。
查询B与以下内容相同:
select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null
当ansi_nulls
打开时, 3 <> null
是UNKNOWN,所以谓词的计算结果为UNKNOWN,并且没有任何行。
当ansi_nulls
closures时, 3 <> null
为true,所以谓词的计算结果为true,并且得到一行。
每当你使用NULL,你实际上正在处理一个三值逻辑。
您的第一个查询返回WHERE子句的计算结果为:
3 = 1 or 3 = 2 or 3 = 3 or 3 = null which is: FALSE or FALSE or TRUE or UNKNOWN which evaluates to TRUE
第二个:
3 <> 1 and 3 <> 2 and 3 <> null which evaluates to: TRUE and TRUE and UNKNOWN which evaluates to: UNKNOWN
UNKNOWN和FALSE不一样,你可以通过调用:
select 'true' where 3 <> null select 'true' where not (3 <> null)
这两个查询都不会给你任何结果
如果UNKNOWN与FALSE相同,那么假设第一个查询会给你假,第二个查询将不得不求值为TRUE,因为它将与NOT(FALSE)相同。
事实并非如此。
在SqlServerCentral上有关于这个主题的非常好的文章 。
NULL和三值逻辑的整个问题起初可能有点混乱,但为了在TSQL中编写正确的查询
另一篇我会推荐的文章是SQL Aggregate Functions和NULL 。
与null相比是未定义的,除非你使用IS NULL。
所以,当比较3到NULL(查询A)时,它返回undefined。
Ie SELECT'true'where 3 in(1,2,null)and SELECT'true'where 3 not in(1,2,null)
会产生相同的结果,因为NOT(UNDEFINED)仍然未定义,但不是TRUE
与未知值进行比较时, NOT IN
返回0条logging
由于NULL
是一个未知数,所以在可能值列表中包含NULL
或NULL
的NOT IN
查询将始终返回0
logging,因为没有办法确保NULL
值不是被testing的值。
这个问题在写这篇文章的标题是
SQL NOT IN约束和NULL值
从问题的文本看来,问题出现在SQL DML SELECT
查询中,而不是SQL DDL CONSTRAINT
。
但是,尤其是考虑到标题的措词,我想指出,这里提出的一些陈述是有误导性的陈述,那些(改写)
当谓词评估为UNKNOWN时,不会得到任何行。
虽然这是SQL DML的情况,但在考虑约束条件时,效果是不同的。
考虑这个非常简单的表格,其中两个约束直接来自问题中的谓词(在@Brannon的优秀回答中):
DECLARE @T TABLE ( true CHAR(4) DEFAULT 'true' NOT NULL, CHECK ( 3 IN (1, 2, 3, NULL )), CHECK ( 3 NOT IN (1, 2, NULL )) ); INSERT INTO @T VALUES ('true'); SELECT COUNT(*) AS tally FROM @T;
根据@ Brannon的回答,第一个约束(使用IN
)评估为TRUE,第二个约束(使用NOT IN
)评估为UNKNOWN。 但是 ,插入成功! 因此,在这种情况下,说“你没有得到任何的行”是不正确的,因为我们确实已经插入了一行。
上述效果对于SQL-92标准来说确实是正确的。 比较和对比SQL-92规范中的以下部分
7.6哪里条款
这个结果是一个T行的表,其search条件的结果是真的。
4.10完整性约束
当且仅当指定的search条件对于表的任何行不为假时才满足表格检查约束。
换一种说法:
在SQL DML中,当WHERE
计算为UNKNOWN时,行将从结果中删除,因为它不满足条件“is true”。
在SQL DDL(即约束)中,当结果计算为UNKNOWN时,行不会从结果中删除,因为它确实满足条件“不是假”。
虽然在SQL DML和SQL DDL中的效果可能看起来是矛盾的,但是给出UNKNOWN结果“允许怀疑”的实际原因是允许它们满足一个约束(更准确地说,允许它们不能不满足约束) :没有这种行为,每个约束都必须明确地处理空值,从语言devise的angular度来看,这将是非常令人不满意的(更不用说,对于编码人员来说是一个正确的痛苦!
ps如果你发现按照“未知不能满足约束条件”这样的逻辑来挑战它是有挑战性的,那么考虑你可以简单地通过避免SQL DDL中的可为空的列和SQL中的任何东西产生空值的DML(例如外连接)!
在A中,3对于每个成员的集合进行检验,得到(FALSE,FALSE,TRUE,UNKNOWN)。 由于其中一个元素为TRUE,因此条件为TRUE。 (这里也可能发生短路,所以一旦遇到第一个TRUE,它就会停止,并且从不计算3 = NULL。)
在B中,我认为它正在评估条件为NOT(3(1,2,null))。 testing3是否等于集合到UNKNOWN的设置收益率(FALSE,FALSE,UNKNOWN)。 NOT(UNKNOWN)产生UNKNOWN。 所以总的来说,情况的真相是未知的,最后基本上被视为FALSE。
空值表示和不存在数据,这是未知的,不是没有数据的值。 编程背景的人很容易混淆这个,因为在使用指针的C语言中,null实际上什么也不是。
因此,在第一种情况下,3确实在(1,2,3,null)的集合中,所以返回true
然而在第二个,你可以减less它
select'true',其中3不在(null)
所以没有任何东西会被返回,因为parsing器对你所比较的设置一无所知 – 它不是一个空集,而是一个未知集。 使用(1,2,null)没有帮助,因为(1,2)集合显然是错误的,但是你和那个对未知的,这是未知的。
如果你想用NOT IN过滤包含NULL的子查询,只需检查是否为空
SELECT blah FROM t WHERE blah NOT IN (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
从这里的答案可以得出结论: NOT IN (subquery)
不能正确处理空值,应该避免使用NOT EXISTS
。 但是,这样的结论可能为时过早。 在以下情况下,记入克里斯date(数据库编程和devise,第2卷第9号,1989年9月),它是NOT IN
正确处理空值,并返回正确的结果,而不是NOT EXISTS
。
考虑一个表格来表示供应商( sno
),他们已知供应数量( qty
)的零件( pno
)。 该表目前拥有以下值:
VALUES ('S1', 'P1', NULL), ('S2', 'P1', 200), ('S3', 'P1', 1000)
请注意,数量是可以为空的,即能够logging供应商已知供应零件的事实,即使不知道数量如何。
任务是find已知供应件号“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
存在不是它可能出现的银弹!
当然,问题的根源在于存在空值,因此“真正”的解决scheme是消除这些空值。
这可以通过使用两个表来实现(在其他可能的devise中):
- 已知供应零件的供应商
-
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;
这也许可以用来了解join,存在和http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx之间的逻辑区别;
这是男孩:
select party_code from abc as a where party_code not in (select party_code from xyz where party_code = a.party_code);
这个工程不pipeansi设置