SQL Server: Почему сравнение null = значение возвращает true для NOT IN?
Почему сравнение value
с null
возвращает 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. я буду действовать логически следующим образом:
Проверить 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 этого не делает. Если у вас есть NULL в вашем списке in
, тогда он находит совпадение. Он неожиданно находит матч. Внезапно 399573 = null
оценивается как true.
Почему сравнение value
с null
возвращает неизвестно, кроме случаев, когда оно возвращает true?
Изменить: я знаю, что могу обойти это поведение бессмысленного, специально исключив нули:
SELECT * FROM Users
WHERE UserID NOT IN (
SELECT UserID FROM Posts
WHERE UserID IS NOT NULL)
Но мне не нужно, насколько я могу сказать, логическая логика должна быть в порядке без него - следовательно, мой вопрос.
Сноски
- 1 гипотетические данные; если вам это не нравится: составьте ваш.
- celko теперь имеет свой собственный тег
Ответы
Ответ 1
Общая проблема, законченный ответ:
Поведение предложения 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
Обратите внимание, что оба запроса возвращают ожидаемые результаты. Теперь напомним теорему ДеМоргана, в которой говорится, что:
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). Применяя теорему ДеМоргана к этим запросам, следует, что эти два запроса также эквивалентны:
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 < > NULL никогда не может быть правдой
Ответ 2
Предположение в вашем первом предложении неверно:
Почему сравнение значения с null возвращает false, за исключением случаев, когда используется NOT IN, где он возвращает true?
Но сравнение значения к null не возвращает false
; он возвращает unknown
. И unknown
имеет свою собственную логику:
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
, поэтому это исключает любую строку.
Вы можете найти полную славу 3-значной логики в Wikipedia.