Не в предложении и нулевых значениях

эта проблема возникла, когда я получил разные записи для того, что я думал, были идентичными запросами, используя not in where ограничение и другое a left join. Стол в имело одно нулевое значение (плохие данные), которое заставило этот запрос вернуть количество записей 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 ответов


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


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

неизвестное не то же самое, что ложное вы можете легко проверить его, позвонив:

select 'true' where 3 <> null
select 'true' where not (3 <> null)

оба запроса не дадут вам никаких результатов

Если неизвестное было таким же, как FALSE, то предполагая, что первый запрос даст вам FALSE второй пришлось бы оценивать на TRUE, так как это было бы то же самое, что и не(FALSE).
Это не так.

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

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

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


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

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

т. е. Выберите "true", где 3 в (1,2, null) и Выберите "true", где 3 не в (1,2, null)

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


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

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


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

SQL не в ограничении и нулевых значениях

из текста вопроса кажется, что проблема возникла в SQL DML SELECT запрос, а не SQL DDL 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) оценивается как неизвестный. вставить преуспевает! Поэтому в этом случае не совсем правильно говорить: "вы не получаете строк", потому что в результате мы действительно получили строку, вставленную.

вышеуказанный эффект действительно правильный в отношении стандарта SQL-92. Сравните и сравните следующий раздел из спецификации SQL-92

7.6 предложение where

результат в таблице те строки Т что является результатом условия поиска true.

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

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

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

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

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

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

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


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

В B, Я думаю, что он оценивает условие как не (3 в (1,2, null)). Тестирование 3 на равенство с заданными выходами (FALSE, FALSE, UNKNOWN), которое агрегированный до неизвестного. Не (неизвестно) дает неизвестное. Таким образом, в целом истинность условия неизвестна, что в конце концов рассматривается как ложное.


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

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

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

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

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


Если вы хотите фильтровать с NOT IN для подзапроса containg NULLs justcheck для not null

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

из ответов здесь можно сделать вывод, что 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" в результате (т. е. для которого количество равно нулю):

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;

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

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

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


также это может быть полезно знать логическую разницу между join, exists и in http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx