278

当我为我认为相同的查询一个使用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 返回结果。

4

12 回答 12

307

查询 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则为真,因此谓词评估为真,并且您得到一行。

于 2008-09-24T19:01:50.323 回答
57

每当您使用 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

于 2008-09-25T09:54:41.723 回答
49

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

由于NULL是未知数,因此在可能值列表中NOT IN包含 aNULLNULLs 的查询将始终返回0记录,因为无法确定该NULL值不是正在测试的值。

于 2008-09-24T19:01:34.177 回答
18

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

因此,当比较 3 和 NULL(查询 A)时,它返回 undefined。

即 SELECT 'true' where 3 in (1,2,null) 和 SELECT 'true' where 3 not in (1,2,null)

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

于 2008-09-24T18:53:18.503 回答
9

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

SQL NOT IN 约束和 NULL 值

从问题的文本看来,问题出现在 SQL DMLSELECT查询中,而不是 SQL DDLCONSTRAINT中。

但是,特别是考虑到标题的措辞,我想指出,这里的某些陈述可能具有误导性,类似于(释义)

当谓词评估为 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 中搜索条件结果为真的那些行的表。

4.10 完整性约束

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

换句话说:

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

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

尽管 SQL DML 和 SQL DDL 中的效果可能看起来相互矛盾,但有实际理由通过允许它们满足约束来赋予 UNKNOWN 结果“怀疑的好处”(更准确地说,允许它们不会不满足约束) :如果没有这种行为,每个约束都必须明确处理空值,从语言设计的角度来看,这将是非常不令人满意的(更不用说,对编码人员来说是一种痛苦!)

ps 如果您发现遵循“未知不会不满足约束”之类的逻辑就像我要编写它一样具有挑战性,那么考虑您可以通过避免 SQL DDL 中的可空列和 SQL 中的任何内容来简单地免除所有这些产生空值的 DML(例如外连接)!

于 2011-09-23T08:39:28.697 回答
7

在 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。所以总的来说,条件的真相是未知的,最后基本上被视为 FALSE。

于 2008-09-24T18:58:15.730 回答
7

可以从这里的答案中得出结论,这些答案NOT IN (subquery)没有正确处理空值,应该避免使用NOT EXISTS. 然而,这样的结论可能为时过早。在以下场景中,归功于 Chris Date(数据库编程和设计,第 2 卷第 9 期,1989 年 9 月),它NOT IN正确处理空值并返回正确结果,而不是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已知供应已知数量的零件的供应商

注意应该有一个外键约束,其中spqreferences sp

然后可以使用“减号”关系运算符(作为EXCEPT标准 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;
于 2011-09-27T15:00:47.613 回答
6

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

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

但是,在第二个中,您可以将其减少到

选择“真”,其中 3 不在(空)

所以什么都没有返回,因为解析器对你正在比较它的集合一无所知——它不是一个空集合,而是一个未知集合。使用 (1, 2, null) 并没有帮助,因为 (1,2) 集显然是错误的,但随后您将与未知数相矛盾,这是未知数。

于 2008-09-24T19:08:44.380 回答
6

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

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
于 2014-10-31T22:06:22.550 回答
4

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

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

但添加 aNOT不会反转结果:

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⁽¹⁾  | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN⁽²⁾            | UNKNOWN⁽³⁾                  |

请注意:

  1. 涉及NULL收益率的比较UNKNOWN
  2. 没有任何操作数且至少有一个操作数为yield ( ref )的OR表达式TRUEUNKNOWNUNKNOWN
  3. 收益NOT率( ref ) _UNKNOWNUNKNOWN

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

于 2019-11-28T08:44:08.423 回答
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 设置如何,这都有效

于 2009-06-23T10:02:13.837 回答
0

这也可能有助于了解加入、存在和 http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx之间的逻辑差异

于 2008-09-24T22:47:33.403 回答