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


当前回答

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

SQL NOT IN约束和NULL值

从问题的文本来看,问题似乎发生在SQL DML SELECT查询中,而不是SQL DDL约束中。

然而,特别是考虑到标题的措辞,我想指出这里所做的一些陈述是潜在的误导性陈述,那些沿着(意译)

当谓词求值为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,第二个约束(使用非IN)计算为UNKNOWN。但是,插入成功了!因此,在这种情况下,严格地说“您没有得到任何行”是不正确的,因为我们确实得到了一行插入作为结果。

上述效果对于SQL-92标准来说确实是正确的。比较和对比SQL-92规范中的以下部分

7.6 where条款 的结果是T的那些行组成的表 其中搜索条件的结果为真。 4.10完整性约束 表检查约束当且仅当指定 表中任何一行的查询条件都不为false。

换句话说:

在SQL DML中,当WHERE值为UNKNOWN时,将从结果中删除行,因为它不满足条件“is true”。

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

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

附注:如果你发现像我写的那样遵循“未知不满足约束”这样的逻辑具有挑战性,那么你可以考虑通过避免SQL DDL中可空列和SQL DML中任何产生空值的列(例如外部连接)来省去这一切!

其他回答

查询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为真,因此谓词的计算结果为真,并得到一行。

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

由于NULL是一个未知值,在可能值列表中包含NULL或NULLs的NOT IN查询将总是返回0记录,因为没有办法确保NULL值不是正在测试的值。

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

SQL NOT IN约束和NULL值

从问题的文本来看,问题似乎发生在SQL DML SELECT查询中,而不是SQL DDL约束中。

然而,特别是考虑到标题的措辞,我想指出这里所做的一些陈述是潜在的误导性陈述,那些沿着(意译)

当谓词求值为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,第二个约束(使用非IN)计算为UNKNOWN。但是,插入成功了!因此,在这种情况下,严格地说“您没有得到任何行”是不正确的,因为我们确实得到了一行插入作为结果。

上述效果对于SQL-92标准来说确实是正确的。比较和对比SQL-92规范中的以下部分

7.6 where条款 的结果是T的那些行组成的表 其中搜索条件的结果为真。 4.10完整性约束 表检查约束当且仅当指定 表中任何一行的查询条件都不为false。

换句话说:

在SQL DML中,当WHERE值为UNKNOWN时,将从结果中删除行,因为它不满足条件“is true”。

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

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

附注:如果你发现像我写的那样遵循“未知不满足约束”这样的逻辑具有挑战性,那么你可以考虑通过避免SQL DDL中可空列和SQL DML中任何产生空值的列(例如外部连接)来省去这一切!

SQL使用三值逻辑来表示真值。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

这是因为上面的查询与以下查询是等价的:

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⁽³⁾                  |

注意:

涉及NULL的比较结果为UNKNOWN 没有一个操作数为TRUE且至少有一个操作数为UNKNOWN的OR表达式的结果为UNKNOWN (ref) UNKNOWN的NOT产生UNKNOWN(参考)

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

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