NOT IN 子句中的 NULL 值
Posted
技术标签:
【中文标题】NOT IN 子句中的 NULL 值【英文标题】:NULL values inside NOT IN clause 【发布时间】:2010-09-12 20:18:21 【问题描述】:当我使用 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 返回结果。
【问题讨论】:
【参考方案1】:与 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
【讨论】:
好点。 select 1 where null in (null) 不返回行 (ansi)。【参考方案2】:在 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。
【讨论】:
【参考方案3】:
NOT IN
与未知值比较时返回 0 条记录
由于NULL
是未知数,因此在可能值列表中包含NULL
或NULL
s 的NOT IN
查询将始终返回0
记录,因为无法确定@ 987654327@值不是正在测试的值。
【讨论】:
这就是答案。我发现即使没有任何示例也更容易理解。【参考方案4】:查询 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
转换为一系列<> and
会将not in this set 的语义行为更改为其他内容?
@Ian - 看起来“A NOT IN ('X', 'Y')”实际上是 SQL 中 A 'X' AND A 'Y' 的别名。 (我看到您自己在***.com/questions/3924694/… 中发现了这一点,但想确保您的反对意见已在此问题中得到解决。)
我想这解释了为什么SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);
产生一行而不是我预期的空结果集。
这是 SQL 服务器的一个非常糟糕的行为,因为如果它期望使用“IS NULL”进行 NULL 比较,那么它应该将 IN 子句扩展为相同的行为,而不是愚蠢地将错误的语义应用于本身。
@binki,如果在此处运行rextester.com/l/sql_server_online_compiler,则查询将执行,但如果在此处运行sqlcourse.com/cgi-bin/interpreter.cgi,则查询无效。【参考方案5】:
Null 表示没有数据,也就是未知,不是什么都没有的数据值。具有编程背景的人很容易混淆这一点,因为在 C 类型语言中,使用指针时 null 确实什么都不是。
因此在第一种情况下,3 确实在 (1,2,3,null) 的集合中,因此返回 true
然而,你可以将它减少到
选择 'true' where 3 not in (null)
所以没有返回任何内容,因为解析器对您要与之比较的集合一无所知 - 它不是一个空集合,而是一个未知集合。使用 (1, 2, null) 并没有帮助,因为 (1,2) 集合显然是错误的,但随后您将与未知数相矛盾,这是未知数。
【讨论】:
【参考方案6】:这也可能有助于了解 join、exists 和 in 之间的逻辑差异 http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
【讨论】:
【参考方案7】:每当您使用 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。
【讨论】:
【参考方案8】:这是给男孩的:
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' where 3 not in (1, 2, null) 必须执行删除空值的方法,例如select 'true' where 3 not in (1, 2, isnull(null,0)) 总体逻辑是,如果 NULL 是原因,那么在查询中的某个步骤找到一种方法来删除 NULL 值。 select party_code from abc as a where party_code not in (select party_code from xyz where party_code is not null) 但是如果你忘记了字段允许空值,那么祝你好运,这通常是这种情况【参考方案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 的那些行的表 搜索条件的结果为真。
4.10 完整性约束
当且仅当指定的表检查约束满足 表中任何一行的搜索条件都不为假。
换句话说:
在 SQL DML 中,当 WHERE
的计算结果为 UNKNOWN 时,会从结果中删除行,因为它不满足条件“为真”。
在 SQL DDL(即约束)中,行在评估为 UNKNOWN 时不会从结果中删除,因为它确实满足条件“不为假”。
虽然 SQL DML 和 SQL DDL 中的效果可能看起来相互矛盾,但通过允许它们满足约束(更准确地说,允许它们不满足一个约束):没有这种行为,每个约束都必须显式处理空值,从语言设计的角度来看,这将是非常不令人满意的(更不用说,对编码人员来说是一种痛苦!)
附言如果您发现遵循“未知不会不满足约束”之类的逻辑就像我写它一样具有挑战性,那么考虑您可以通过避免 SQL DDL 中的可空列和 SQL DML 中的任何内容来省去所有这些产生空值(例如外连接)!
【讨论】:
老实说,我认为在这个问题上没有什么可说的了。很有趣。 @Jamie Ide:实际上,我对这个问题还有另一个答案:因为涉及空值的NOT IN (subquery)
会产生意想不到的结果,所以很容易完全避免IN (subquery)
并始终使用NOT EXISTS (subquery)
(因为我曾经做过!)因为它似乎总是正确处理空值。但是,在某些情况下,NOT IN (subquery)
给出了预期的结果,而NOT EXISTS (subquery)
给出了意想不到的结果!如果我能找到关于该主题的笔记(需要笔记,因为它不直观!),我可能会开始写这篇文章,但结论是一样的:避免空值!
@onedaywhen 我对您的断言感到困惑,即 NULL 需要特殊大小写才能具有一致的行为(内部一致,与规范不一致)。将 4.10 改成“A table check constraint is满足当且仅当指定的搜索条件为真”还不够吗?
@DylanYoung:不,规范的措辞是出于一个关键原因:SQL 存在三个值逻辑,其中这些值是 TRUE
、FALSE
和 UNKNOWN
。我想 4.10 可能会读到,“当且仅当指定的搜索条件对于表的每一行都是 TRUE 或 UNKNOWN 时,才满足表检查约束”-注意我对句子末尾的更改-您省略了- - 从“for any”到“for all”。我觉得有必要将逻辑值大写,因为自然语言中的“真”和“假”的含义肯定是指经典的二值逻辑。
考虑:CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );
- 这里的意图是b
必须等于a
或为空。如果必须满足 TRUE 的约束,那么我们需要更改约束以显式处理空值,例如CHECK( a = b OR b IS NULL )
。因此,每个约束都需要用户为所涉及的每个可空列添加...OR IS NULL
逻辑:更复杂,当他们忘记这样做时会出现更多错误,等等。所以我认为 SQL 标准委员会只是试图务实。 【参考方案10】:
从这里的答案可以得出结论,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
已知供应已知数量零件的供应商
注意应该有一个外键约束,其中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;
【讨论】:
天哪。谢谢你真的写了这篇文章……这让我发疯了……【参考方案11】:如果您想用 NOT IN 过滤包含 NULL 的子查询,只需检查 not null
SELECT blah FROM t WHERE blah NOT IN
(SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
【讨论】:
我的外连接查询有问题,在特殊情况下没有返回任何记录,所以检查了这个解决方案对于 Null 和存在记录的情况,它对我有用,如果发生其他问题,我会提到在这里,非常感谢。【参考方案12】: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
OR
表达式中没有一个操作数是 TRUE
并且至少有一个操作数是 UNKNOWN
产生 UNKNOWN
(ref)
UNKNOWN
的 NOT
产生 UNKNOWN
(ref)
您可以将上面的示例扩展到两个以上的值(例如 NULL、1 和 2),但结果将是相同的:如果其中一个值是 NULL
,则不会有任何行匹配。
【讨论】:
以上是关于NOT IN 子句中的 NULL 值的主要内容,如果未能解决你的问题,请参考以下文章