SQL Server:为什么比较null = value为NOT IN返回true?

时间:2010-10-13 14:22:10

标签: sql-server null celko

为什么valuenull的比较返回false,除非使用NOT IN,否则返回true?


给出查询以查找拥有帖子的所有stackoverflow用户:

SELECT * FROM Users
WHERE UserID IN (SELECT UserID FROM Posts)

这按预期工作;我得到一个有帖子的所有用户的列表。

现在查询逆;找到发帖的所有stackoverflow用户:

SELECT * FROM Users
WHERE UserID NOT IN (SELECT UserID FROM Posts)

这不会返回任何记录,这是不正确的。

给出假设数据 1

Users              Posts
================   ===============================
UserID  Username   PostID   UserID  Subject
------  --------   -------  ------  ----------------
1       atkins     1        1       Welcome to stack ov...
2       joels      2        2       Welcome all!
...     ...        ...      ...
399573  gt6989b    ...      ...
...     ...        ...      ...
                   10592    null    (deleted by nsl&fbi...
                   ...      ... 

并假设NULL规则:

  • NULL = NULL评估为未知
  • NULL <> NULL评估为未知
  • value = NULL评估未知

如果我们查看第二个查询,我们有兴趣查找在Posts.UserID列中找到Users.UserID 的所有行。我会按照以下逻辑进行:

检查UserID 1

  • 1 = 1返回true。因此,我们得出结论,该用户有一些帖子,并且不将它们包含在输出列表中

现在检查UserID 2:

  • 2 = 1返回false,所以我们一直在寻找
  • 2 = 2返回true,因此我们得出结论,此用户有一些帖子,并且不将它们包含在输出列表中

现在检查UserID 399573

  • 399573 = 1返回false,所以我们一直在寻找
  • 399573 = 2返回false,所以我们一直在寻找
  • ...
  • 399573 = null返回未知,所以我们一直在寻找
  • ...

我们没有找到UserID 399573的帖子,因此我们会将他包含在输出列表中。

除了SQL Server不这样做。如果您的in列表中有NULL,则会突然找到匹配项。 突然发现匹配。突然399573 = null评估为真。

为什么valuenull的比较会返回未知,除非它返回true?

编辑:我知道我可以通过明确排除空值来解决此无意义行为:

SELECT * FROM Users
WHERE UserID NOT IN (
   SELECT UserID FROM Posts
   WHERE UserID IS NOT NULL)

但是我不应该这样做,据我所知,布尔逻辑在没有它的情况下应该没问题 - 因此我的问题。

脚注

  • 1 假设数据;如果你不喜欢它:弥补你的压力。
  • celko现在有自己的标签

2 个答案:

答案 0 :(得分:9)

你的第一句话中的假设是不正确的:

  

为什么值的比较   null返回false,除非使用a   NOT IN,它返回true?

但是将值与null进行比较不会返回false;它返回unknownunknown有自己的逻辑:

unknown  AND  true   = unknown
unknown  OR   true   = true
unknown  OR   false  = unknown

如何解决这个问题的一个例子:

where 1 not in (2, null)
--> where 1 <> 2 and 1 <> null
--> where true and unknown
--> where unknown

where子句仅匹配true,因此会筛选出任何行。

你可以在Wikipedia找到3值逻辑的全部荣耀。

答案 1 :(得分:8)

常见问题,罐头答案:

NOT IN子句的行为可能令人困惑,因此需要一些解释。请考虑以下查询:

SELECT LastName, FirstName FROM Person.Contact WHERE LastName NOT IN('Hedlund', 'Holloway', NULL)

虽然AdventureWorks.Person.Contact中有超过一千个不同的姓氏,但查询不返回任何内容。这可能与初学者数据库程序员看起来有悖常理,但实际上它非常有意义。解释包括几个简单的步骤。首先,考虑以下两个明确等同的查询:

SELECT LastName, FirstName FROM Person.Contact

WHERE LastName IN('Hedlund', 'Holloway', NULL)



SELECT LastName, FirstName FROM Person.Contact

WHERE LastName='Hedlund' OR LastName='Holloway' OR LastName=NULL

请注意,两个查询都会返回预期结果。现在,让我们回顾一下DeMorgan的定理,该定理指出:

not (P and Q) = (not P) or (not Q)

not (P or Q) = (not P) and (not Q)

我正在从维基百科(http://en.wikipedia.org/wiki/De_Morgan_duality)剪辑和粘贴。将DeMorgan定理应用于此查询,因此这两个查询也是等价的:

SELECT LastName, FirstName FROM Person.Contact WHERE LastName NOT IN('Hedlund', 'Holloway', NULL)



SELECT LastName, FirstName FROM Person.Contact

WHERE LastName<>'Hedlund' AND LastName<>'Holloway' AND LastName<>NULL

这最后一个LastName&lt;&gt; NULL永远不会是真的