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.