Значения NULL внутри предложения NOT IN

245

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

Джейми Иде
источник

Ответы:

283

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

Brannon
источник
11
Кто-нибудь когда-либо указывал, что преобразование NOT INв серию <> andизменений меняет семантическое поведение не в этом наборе на что-то еще?
Ян Бойд
8
@Ian - Похоже, что "NOT IN ('X', 'Y')" на самом деле является псевдонимом A <> 'X' и A <> 'Y' в SQL. (Я вижу, что вы сами обнаружили это в stackoverflow.com/questions/3924694/… , но хотели убедиться, что ваши возражения были рассмотрены в этом вопросе.)
Райан Олсон,
Я думаю, это объясняет, почему SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);выдает строку вместо пустого набора результатов, который я ожидал.
Бинки
2
Это очень плохое поведение SQL-сервера, потому что, если он ожидает NULL-сравнения с использованием «IS NULL», он должен расширить предложение IN до того же поведения, а не тупо применять неправильную семантику к себе.
OzrenTkalcecKrznaric
@binki, ваш запрос выполняется, если запустить здесь rextester.com/l/sql_server_online_compiler, но не работает, если запустить здесь sqlcourse.com/cgi-bin/interpreter.cgi .
Истак Ахмед
53

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

Кристофа
источник
33

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

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

YonahW
источник
3
Это ответ в двух словах. Я обнаружил, что это легче понять, даже без какого-либо примера.
Говинд Рай
18

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

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

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

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

Солнечный Миленов
источник
Отличный момент. выберите 1, где ноль в (ноль) не возвращает строки (ANSI).
Crokusek
9

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

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

Из текста вопроса видно, что проблема возникла в SELECTзапросе SQL DML , а не в DDL SQL 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, первое ограничение (using IN) оценивается как TRUE, а второе ограничение (using NOT IN) - UNKNOWN. Тем не менее , вставка удалась! Следовательно, в этом случае не совсем правильно говорить «вы не получаете никаких строк», потому что мы действительно вставили строку в результате.

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

7.6, где пункт

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

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

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

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

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

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

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

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

onedaywhen
источник
Я, честно говоря, не думал, что по этому вопросу осталось что-то сказать. Интересный.
Джейми Ид
2
@Jamie Ide: На самом деле, у меня есть другой ответ на эту тему: поскольку NOT IN (subquery)использование нулей может дать неожиданные результаты, заманчиво избегать IN (subquery)полностью и всегда использовать NOT EXISTS (subquery)(как я когда-то делал!), Потому что кажется, что оно всегда обрабатывает нулевые значения правильно. Однако есть случаи, когда NOT IN (subquery)дает ожидаемый результат, тогда как NOT EXISTS (subquery)дает неожиданные результаты! Я могу найти время, чтобы написать это, если смогу найти свои заметки на эту тему (нужны заметки, потому что это не интуитивно понятно!) Вывод тот же: избегайте нулей!
onedaywhen
@onedaywhen Я смущен твоим утверждением, что NULL должен быть в специальном случае, чтобы иметь согласованное поведение (внутренне согласованное, не совместимое со спецификацией). Разве не достаточно изменить 4.10 следующим образом: «Ограничение проверки таблицы выполняется тогда и только тогда, когда заданное условие поиска истинно»?
DylanYoung
@DylanYoung: Нет, спецификация формулируется таким образом для решающей причины: SQL страдает от три логики значения, где эти значения TRUE, FALSEи UNKNOWN. Я полагаю, что 4.10 мог бы прочитать: «Ограничение проверки таблицы выполняется тогда и только тогда, когда указанное условие поиска равно TRUE или UNKNOWN для каждой строки таблицы», - обратите внимание на мое изменение в конце предложения, которое вы пропустили. - от «для любого» до «для всех„Я чувствую необходимость капитализировать логические значения , так как смысл. „истина“ и „ложь“ на естественном языке , должны , безусловно , относится к классической двузначной логике.
onedaywhen
1
Рассмотрим: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- намерение здесь bдолжно быть равным aили равным нулю. Если ограничение должно было приводить к TRUE, чтобы быть удовлетворенным, тогда нам нужно изменить ограничение, чтобы явно обрабатывать нули, например CHECK( a = b OR b IS NULL ). Таким образом, каждое ограничение должно иметь ...OR IS NULLдобавленную пользователем логику для каждого включаемого столбца: больше сложности, больше ошибок, когда они забыли это сделать и т. Д. Поэтому я думаю, что комитет по стандартам SQL просто пытался быть прагматичным.
onedaywhen
7

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

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

Дейв Коста
источник
7

Из приведенных здесь ответов можно сделать вывод, что 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;
onedaywhen
источник
1
О, мой бог. Спасибо за то, что на самом деле написали это .... это сводило меня с ума ..
Говинд Рай
6

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

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

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

выберите «истина», где 3 не в (ноль)

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

Cruachan
источник
6

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

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Михай
источник
У меня была проблема с запросом внешнего соединения, который не возвращал никаких записей в особых ситуациях, поэтому проверил это решение для сценария как с пустыми, так и с существующими записями, и это сработало для меня. Если возникнут другие проблемы, я упомяну здесь, большое спасибо
QMaster
1

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

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

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

CB
источник
для исходного вопроса: B: выберите «истина», где 3 не в (1, 2, ноль), способ удаления нулей должен быть сделан, например, выберите «истина», где 3 не в (1, 2, isnull (ноль, 0) ) общая логика состоит в том, что если NULL является причиной, то найдите способ удалить значения NULL на каком-то этапе запроса.
выберите party_code из abc как код party_code, в котором нет party_code (выберите код party_code из xyz, где код party_code не нулевой), но удачи, если вы забыли, что поле допускает пустые значения, что часто имеет место
1

SQL использует трехзначную логику для значений истинности. INЗапрос выдает ожидаемый результат:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

Но добавление NOTне инвертирует результаты:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

Это потому, что приведенный выше запрос эквивалентен следующему:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

Вот как оценивается предложение where:

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

Заметь:

  1. Сравнение с NULLучетом урожайностиUNKNOWN
  2. ORВыражение , где ни один из операндов не являются TRUEи по крайней мере один из операндов имеет UNKNOWNвыходы UNKNOWN( исх )
  3. NOTИз UNKNOWNвыходов UNKNOWN( исх )

Вы можете расширить приведенный выше пример более чем на два значения (например, NULL, 1 и 2), но результат будет таким же: если одно из значений будет равно, NULLни одна строка не будет соответствовать.

Салман А
источник