NOT IN子句和NULL值

这个问题出现时,我得到不同的logging计数为我认为是相同的查询一个使用not in where约束和另一个left joinnot 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_nullsclosures时, 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是一个未知数,所以在可能值列表中包含NULLNULLNOT IN查询将始终返回0logging,因为没有办法确保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; 

这是男孩:

 select party_code from abc as a where party_code not in (select party_code from xyz where party_code = a.party_code); 

这个工程不pipeansi设置

    Interesting Posts