Значения NOT IN и NULL

Эта проблема возникла, когда у меня были разные учетные записи для того, что, как я думал, было идентичным запросом, использующим ограничение not in where, а другое a 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

Запрос 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, и вы не получаете никаких строк.

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

Ответ 2

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

Ответ 3

Сравнение с null - это undefined, если вы не используете IS NULL.

Итак, при сравнении 3 с NULL (запрос A) он возвращает undefined.

т.е. SELECT 'true', где 3 in (1,2, null)  а также SELECT 'true', где 3 не в (1,2, null)

даст тот же результат, что и NOT (UNDEFINED) по-прежнему undefined, но не TRUE

Ответ 4

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

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

Ответ 5

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

Значения 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 где предложение

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

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

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

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

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

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

Хотя эффекты в SQL DML и SQL DDL, соответственно, могут показаться противоречивыми, есть практические причины для того, чтобы дать UNKNOWN результаты "выгоду от сомнений", позволяя им удовлетворять ограничение (вернее, позволяя им не поддаваться ограничение): без этого поведения каждому ограничению пришлось бы явно обрабатывать нули, и это было бы очень неудовлетворительным с точки зрения дизайна языка (не говоря уже о правильной боли для кодеров!)

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

Ответ 6

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

В B, я думаю, что он оценивает условие как NOT (3 in (1,2, null)). Тестирование 3 для равенства по отношению к установленным доходностям (FALSE, FALSE, UNKNOWN), которое агрегируется в UNKNOWN. NOT (UNKNOWN) дает UNKNOWN. Таким образом, общая истина условия неизвестна, которая в конце по существу трактуется как ЛОЖЬ.

Ответ 7

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

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

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

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

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

Ответ 8

ЕСЛИ вы хотите фильтровать с помощью NOT IN для подзапроса, содержащего NULL, для проверки нулевого значения

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

Ответ 9

Из ответов здесь можно сделать вывод, что NOT IN (subquery) не обрабатывает нули правильно и его следует избегать в пользу NOT EXISTS. Однако такой вывод может быть преждевременным. В следующем сценарии, зачисленном в Chris Date (Database Programming and Design, Vol 2 No 9, September 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 в результате (т.е. для которого значение равно null):

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;

Ответ 10

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

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

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