NOT IN子句中的NULL值


244

当我获得不同的记录计数时,出现了这个问题,我认为这是相同的查询,一个使用not in where约束,另一个使用a left joinnot 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返回结果。

Answers:


282

查询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是未知的,所以谓词计算结果为未知的,你没有得到任何行。

ansi_nullsoff 3 <> null为true时,谓词为true,因此谓词的值为true,您将获得一行。


11
有没有人指出过将NOT IN一系列<> and语言中没有的语义行为转换为一系列变化?
伊恩·博伊德

8
@Ian-看起来“ A NOT IN('X','Y')”实际上是SQL中A <>'X'和A <>'Y'的别名。(我发现您自己在stackoverflow.com/questions/3924694/…中发现了此问题,但想确保在此问题中解决了您的异议。)
Ryan Olson 2010年

我猜这解释了为什么会SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);产生一行而不是我期望的空结果集。
宾基

2
这是SQL Server的一个非常差的行为,因为如果期望使用“ IS NULL”进行NULL比较,则应将IN子句扩展为相同的行为,而不是愚蠢地将错误的语义应用于自身。
OzrenTkalcecKrznaric '16

@binki,您执行查询,如果跑这里rextester.com/l/sql_server_online_compiler但不工作,如果运行此sqlcourse.com/cgi-bin/interpreter.cgi
Istiaque Ahmed

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)相同。
事实并非如此。

在SqlServerCentral上有关于此主题的非常好的文章

整个NULL和三值逻辑问题一开始可能会让人有些困惑,但必须理解才能在TSQL中编写正确的查询

我推荐的另一篇文章是SQL Aggregate Functions和NULL


33

NOT IN 与未知值比较时返回0条记录

由于NULL未知,由于可能无法确保该值不是要测试的值,所以在可能值列表中NOT IN包含a NULLNULLs 的查询将始终返回0记录NULL


3
简而言之,这就是答案。我发现即使没有任何示例,也更容易理解。
Govind Rai

18

除非您使用IS NULL,否则比较为null是未定义的。

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

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

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


好点。选择1,其中(null)中的null不返回行(ansi)。
crokusek 2012年

9

在撰写本文时,此问题的标题是

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子句

的结果是T的那些行的表,其搜索条件的结果为true。

4.10完整性约束

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

换一种说法:

在SQL DML,行从结果中删除时,WHERE计算结果为未知的,因为它符合条件“是真的”。

在SQL DDL(即约束)中,当行评估为UNKNOWN时,不会从结果中删除行,因为行 确实满足条件“不为假”。

尽管分别在SQL DML和SQL DDL中的效果似乎是矛盾的,但是有实际的理由通过允许UNKNOWN结果满足约束条件(更正确地说,就是让它们不满足约束条件)来给UNKNOWN结果“怀疑的好处”。 :没有这种行为,每个约束都必须显式处理空值,从语言设计的角度来看,这将是非常不令人满意的(更不用说,对于程序员来说是正确的痛苦!)

ps,如果您发现遵循我写的“未知不会满足约束”之类的逻辑具有挑战性,那么考虑通过避免SQL DDL中的可空列以及SQL中的任何内容,就可以省去所有这些操作DML产生空值(例如外部联接)!


老实说,我认为关于这个问题没有什么可说的。有趣。
Jamie Ide

2
@Jamie Ide:实际上,我对这个问题还有另一个答案:因为NOT IN (subquery)涉及null可能会产生意外的结果,因此很想避免IN (subquery)并始终使用它NOT EXISTS (subquery)(就像我曾经做的那样!),因为它似乎总是正确地处理了null。但是,在某些情况下NOT IN (subquery)会给出预期的结果而却NOT EXISTS (subquery)给出了意外的结果!如果我可以找到关于该主题的笔记(需要笔记,因为这是非直觉的!),那么我可能会写这篇文章,但是结论是相同的:避免使用null!
2011年

@oneday,当我对您的断言感到困惑时,需要将NULL特殊化才能具有一致的行为(内部一致,不符合规范)。将4.10更改为“仅当指定的搜索条件为true时才满足表检查约束”,这还不够吗?
DylanYoung,

@DylanYoung:不,规范的措辞的重要原因是这样:从三个值逻辑SQL遭罪,在这些价值观TRUEFALSEUNKNOWN。我想4.10可能会读到:“并且仅当表的每一行指定的搜索条件为TRUE或UNKNOWN时,才满足表检查约束条件”-请注意我对句子结尾的更改-您省略了- -从“对任何”到“所有‘我觉得有必要利用逻辑值,因为的意思。‘真’和'自然语言肯定是指经典的二值逻辑。假
onedaywhen

1
考虑:CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );-这里的意图是b必须等于a或为null。如果约束必须满足TRUE,则我们需要更改约束以显式处理null,例如CHECK( a = b OR b IS NULL )。因此,每个约束都需要...OR IS NULL由用户为涉及的每个可为空的列添加逻辑:更多的复杂性,当他们忘记这样做时会出现更多的错误,等等。因此,我认为SQL标准委员会只是在尝试务实。
一天,

7

在A中,针对集合中的每个成员测试3个是否相等,得出(FALSE,FALSE,TRUE,UNKNOWN)。由于元素之一为TRUE,因此条件为TRUE。(也有可能在此处发生一些短路,因此,只要它碰到第一个TRUE,它实际上就会停止,并且永远不会求值3 = NULL。)

在B中,我认为它将条件评估为NOT((1,2,null)中的3)。针对设定的收益率(FALSE,FALSE,UNKNOWN)测试3的相等性,该收益率汇总为UNKNOWN。NOT(UNKNOWN)产生UNKNOWN。因此,总体而言,该条件的真相是未知的,最后将其本质上视为FALSE。


7

可以从这里的答案得出结论,即NOT IN (subquery)不能正确处理空值,应避免使用NOT EXISTS。但是,这样的结论可能为时过早。在以下情况下,应归功于Chris Date(数据库编程和设计,第2卷第9期,1989年9月),它可以NOT IN正确处理null并返回正确的结果,而不是NOT EXISTS

考虑一个表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

然后可以使用“减号”关系运算符(EXCEPT在Standard SQL中为关键字)获得结果,例如

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;

1
我的天啊。谢谢您实际写这篇文章。。。这使我发疯..
Govind Rai

6

Null表示缺少数据,即未知,不是没有数据值的数据。对于具有编程背景的人们来说,这很容易混淆,因为在C类型语言中使用指针时,null确实不算什么。

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

在第二个中,您可以将其减少为

选择“ true”,其中3个不为(空)

因此,不会返回任何内容,因为解析器对与之比较的集合一无所知-它不是空集合,而是未知集合。使用(1,2,null)并没有帮助,因为(1,2)设置显然是假的,但是接下来您要针对未知的对象(未知)进行操作。


6

如果要使用NOT IN筛选包含NULL的子查询,请检查是否为非null

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

我遇到了外部联接查询的问题,在特殊情况下该查询未返回任何记录,因此针对Null和存在记录场景检查了此解决方案,它对我有用,如果发生其他问题,在这里我将在此提及,非常感谢。
QMaster

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设置如何


对于原始问题:B:必须选择“ true”,其中3个不在(1、2,null)中,例如,必须执行删除空值的方法,例如,选择“ true”,其中3个不在(1、2,null,null(null,0) )的总体逻辑是,如果原因为NULL,则在查询的某个步骤中找到一种方法来删除NULL值。

从abc中选择party_code作为不在其中的party_code(从xyz中选择party_code,其中party_code不为null),但如果您忘记了该字段允许为null,那么很幸运,通常是这种情况

1

SQL对真值使用三值逻辑。该IN查询产生预期的结果:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

但是添加a NOT不会反转结果:

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

这是因为上述查询与以下查询等效:

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

这是where子句的计算方式:

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

注意:

  1. 涉及NULL收益的比较UNKNOWN
  2. OR没有操作数TRUE且至少一个操作数为UNKNOWNyield 的表达式UNKNOWNref
  3. 所述NOTUNKNOWN产率UNKNOWNREF

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.