NOT IN子句和NULL值

时间:2008-09-24 18:51:32

标签: sql sql-server tsql null notin

当我使用not in where约束而另一个使用left join约束我的相同查询时,我得到了不同的记录数。 not in约束中的表有一个空值(错误数据),导致该查询返回0个记录的计数。我有点理解为什么,但我可以使用一些帮助来完全理解这个概念。

简单地说,为什么查询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返回结果。

12 个答案:

答案 0 :(得分:260)

查询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关闭时,3 <> null为真,因此谓词的计算结果为true,并且你得到一行。

答案 1 :(得分:52)

每当你使用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相同,那么假设第一个查询给你FALSE,则第二个查询必须评估为TRUE,因为它与NOT(FALSE)相同。
事实并非如此。

非常好article on this subject on SqlServerCentral

NULL和三值逻辑的整个问题起初可能有点令人困惑,但为了在TSQL中编写正确的查询,必须理解

我建议的另一篇文章是SQL Aggregate Functions and NULL

答案 2 :(得分:24)

NOT IN与未知值

进行比较时返回0条记录

由于NULL是未知的,因此在可能值列表中包含NOT INNULL的{​​{1}}查询将始终返回NULL条记录无法确定0值不是正在测试的值。

答案 3 :(得分:18)

比较null是未定义的,除非你使用IS NULL。

因此,当将3与NULL(查询A)进行比较时,它将返回undefined。

即。 SELECT'true'其中3 in(1,2,null)  和 SELECT'true'其中3不在(1,2,null)

将产生相同的结果,因为NOT(UNDEFINED)仍未定义,但不是TRUE

答案 4 :(得分:8)

撰写本文时,这个问题的标题是

  

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 where where

     

结果是T表示这些行的表   搜索条件的结果为真。

     

4.10完整性约束

     

当且仅当指定时,才满足表检查约束   对于表的任何行,搜索条件都不为假。

换句话说:

在SQL DML中,当WHERE评估为UNKNOWN时,会从结果中删除行,因为满足条件“为真”。

在SQL DDL(即约束)中,当计算结果为UNKNOWN时,不会从结果中删除行,因为 满足条件“不是false”。

虽然SQL DML和SQL DDL中的效果分别可能看似矛盾,但实际上有理由通过允许UNKNOWN结果满足约束(更准确地说,允许它们不能满足)来给予UNKNOWN结果“怀疑的好处”。约束):没有这种行为,每个约束都必须显式处理空值,从语言设计的角度来看这是非常不令人满意的(更不用说编码器的正确痛苦了!)

P.S。如果你发现在我编写它时遵循“未知不能满足约束”这样的逻辑是有挑战性的,那么考虑你可以通过避免SQL DDL中的可为空的列以及SQL DML中的任何内容来省去所有这些产生空值(例如外连接)!

答案 5 :(得分:6)

在A中,测试3对集合中每个成员的相等性,产生(FALSE,FALSE,TRUE,UNKNOWN)。由于其中一个元素为TRUE,因此条件为TRUE。 (也有可能在这里发生一些短路,因此它一旦达到第一个TRUE就会停止,并且从不评估3 = NULL。)

在B中,我认为它将条件评估为NOT(3 in(1,2,null))。测试3是否与设定的收益相等(FALSE,FALSE,UNKNOWN),它被聚合到UNKNOWN。 NOT(UNKNOWN)产生UNKNOWN。总的来说,条件的真实性是未知的,最终基本上被视为假。

答案 6 :(得分:6)

Null表示和缺少数据,即它是未知的,而不是数据值。编程背景的人很容易混淆这一点,因为在C类语言中使用指针时,null确实没什么。

因此,在第一种情况下,3确实在(1,2,3,null)的集合中,因此返回

在第二种情况下,您可以将其缩小为

选择'true',其中3不在(null)

因此,没有返回任何内容,因为解析器对您要比较它的集合一无所知 - 它不是空集而是未知集。使用(1,2,null)没有用,因为(1,2)集合显然是错误的,但是那时你和那个未知的那个是未知的。

答案 7 :(得分:6)

从这里的答案可以得出结论,NOT IN (subquery)没有正确处理空值,应该避免使用NOT EXISTS。但是,这样的结论可能为时过早。在以下场景中,记入Chris Date(数据库编程和设计,第2卷,第9期,1989年9月),正确处理空值并返回正确结果NOT IN,而不是NOT EXISTS。< / p>

考虑使用表格sp来表示已知数量(sno)提供部分(pno)的供应商(qty)。该表目前包含以下值:

      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时可能存在外键约束。

然后可以使用'减'关系运算符(标准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;

答案 8 :(得分:5)

如果要使用NOT IN过滤包含NULL的子查询,只需检查not null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

答案 9 :(得分:1)

这是为男孩:

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

无论ansi设置如何,这都有效

答案 10 :(得分:0)

这也可能有助于了解join,exists和in之间的逻辑区别 http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

答案 11 :(得分:0)

SQL对真值使用三值逻辑。 y= NA,log(3),NA,log(4),NA i=1,3,5 查询产生预期结果:

IN

但是添加SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1) -- returns first row 不会反转结果:

NOT

这是因为上面的查询等同于以下内容:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

以下是where子句的计算方式:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

注意:

  1. 涉及| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) | |-----|----------------|---------|-----------------------|-----------------------------| | 1 | UNKNOWN | TRUE | TRUE | FALSE | | 2 | UNKNOWN | FALSE | UNKNOWN (2) | UNKNOWN (3) | 的比较产生NULL
  2. UNKNOWN表达式中,所有操作数都不为OR,至少一个操作数为TRUE会产生UNKNOWNref
  3. UNKNOWN中的NOT产生UNKNOWNref

您可以将上面的示例扩展为两个以上的值(例如NULL,1和2),但结果将是相同的:如果其中一个值为UNKNOWN,则没有行会匹配。