Предложения NOT IN и значения NULL - PullRequest
225 голосов
/ 24 сентября 2008

Эта проблема возникла, когда я получил различное количество записей для идентичных запросов, один из которых использовал ограничение 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 вернуть результат.

Ответы [ 11 ]

261 голосов
/ 24 сентября 2008

Запрос 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 НЕИЗВЕСТНО, поэтому предикат оценивается как НЕИЗВЕСТНО, и вы не получаете никаких строк.

Когда ansi_nulls выключен, 3 <> null имеет значение true, поэтому предикат оценивается как true, и вы получаете строку.

52 голосов
/ 25 сентября 2008

Всякий раз, когда вы используете 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). Это не тот случай.

На SqlServerCentral есть очень хорошая статья на эту тему .

Весь вопрос о NULL и трехзначной логике поначалу может немного сбивать с толку, но это важно понять, чтобы писать правильные запросы в TSQL

Другая статья, которую я бы порекомендовал, это Агрегатные функции SQL и NULL .

24 голосов
/ 24 сентября 2008

NOT IN возвращает 0 записей при сравнении с неизвестным значением

Поскольку NULL является неизвестным, запрос NOT IN, содержащий NULL или NULL s в списке возможных значений, всегда будет возвращать 0 записей, поскольку нет никакого способа убедиться, что NULL значение не является проверяемым значением.

18 голосов
/ 24 сентября 2008

Сравнение со значением null не определено, если вы не используете IS NULL.

Таким образом, при сравнении 3 с NULL (запрос A) возвращается неопределенное значение.

т.е. ВЫБЕРИТЕ «истина», где 3 в (1,2, ноль) а также ВЫБЕРИТЕ «истина», где 3 не в (1,2, ноль)

даст тот же результат, поскольку NOT (НЕ УКАЗАНО) по-прежнему не определен, но не ИСТИНА

8 голосов
/ 23 сентября 2011

Название этого вопроса на момент написания

Ограничение SQL NOT IN и значения NULL

Из текста вопроса видно, что проблема возникла в запросе SQL DML SELECT, а не в DDL SQL CONSTRAINT.

Однако, особенно учитывая формулировку заголовка, я хочу отметить, что некоторые высказанные здесь заявления являются потенциально вводящими в заблуждение утверждениями, аналогичными (перефразируя)

Когда предикат оценивается как НЕИЗВЕСТНЫЙ, вы не получаете никаких строк.

Хотя это относится к 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, где пункт

Результатом является таблица этих строк T для что результат условия поиска верно.

4.10 Ограничения целостности

Ограничение проверки таблицы выполняется тогда и только тогда, когда условие поиска не является ложным для любой строки таблицы.

Другими словами:

В SQL DML строки удаляются из результата, когда WHERE оценивается как UNKNOWN, потому что не удовлетворяет условию "true".

В SQL DDL (т.е. в ограничениях) строки не удаляются из результата, когда они оцениваются как UNKNOWN, поскольку удовлетворяет условию "не ложно".

Хотя эффекты в SQL DML и SQL DDL соответственно могут показаться противоречивыми, существует практическая причина для того, чтобы дать НЕИЗВЕСТНЫМ результатам «преимущество сомнения», позволяя им удовлетворять ограничению (точнее, не нарушая их ограничение): без этого поведения все ограничения должны были бы явно обрабатывать пустые значения, и это было бы очень неудовлетворительно с точки зрения разработки языка (не говоря уже о том, что для программистов это очень болезненно!)

p.s. если вам трудно следовать такой логике, как «неизвестно, не нарушает ограничение», как я это написал, то подумайте, что вы можете обойтись без всего этого, просто избегая пустых столбцов в SQL DDL и всего в SQL DML который производит нули (например, внешние соединения)!

6 голосов
/ 27 сентября 2011

Из приведенных здесь ответов можно сделать вывод, что NOT IN (subquery) неправильно обрабатывает нули и его следует избегать в пользу NOT EXISTS. Однако такой вывод может быть преждевременным. В следующем сценарии, зачисленном Крису Дейту (Программирование баз данных и проектирование, Том 2, № 9, сентябрь 1989 г.), 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.

Затем результат можно получить с помощью реляционного оператора «минус» (который является ключевым словом EXCEPT в стандартном SQL), например,

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;
6 голосов
/ 24 сентября 2008

Нуль означает и отсутствие данных, то есть неизвестно, а не значение данных ничего. Людям из среды программирования очень легко запутать это, потому что в языках типов C при использовании указателей null действительно ничего не значит.

Следовательно, в первом случае 3 действительно находится в наборе (1,2,3, null), поэтому возвращается true

Во втором, однако, вы можете уменьшить его до

выберите 'true', где 3 не в (ноль)

Так что ничего не возвращается, потому что парсер ничего не знает о наборе, с которым вы сравниваете его - это не пустой набор, а неизвестный набор. Использование (1, 2, null) не помогает, потому что набор (1,2), очевидно, ложен, но тогда вы и против этого неизвестного, который неизвестен.

6 голосов
/ 24 сентября 2008

В A 3 проверяется на равенство по отношению к каждому члену множества, уступая (FALSE, FALSE, TRUE, UNKNOWN). Поскольку один из элементов имеет значение ИСТИНА, условие ИСТИНА. (Также возможно, что здесь происходит некоторое короткое замыкание, поэтому оно фактически останавливается, как только достигает первого ИСТИНА, и никогда не оценивает 3 = NULL.)

В B, я думаю, это оценивает условие как НЕ (3 в (1,2, ноль)). Тестирование 3 на равенство с заданным выходом (FALSE, FALSE, UNKNOWN), которое агрегируется в UNKNOWN. НЕ (НЕИЗВЕСТНО) приводит к НЕИЗВЕСТНО. Таким образом, в целом истинность условия неизвестна, что в конце концов рассматривается как ЛОЖЬ.

5 голосов
/ 01 ноября 2014

Если вы хотите отфильтровать с NOT IN для подзапроса, связанного с NULL, просто отметьте для not null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
1 голос
/ 23 июня 2009

это для мальчика:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

это работает независимо от настроек ANSI

...