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

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

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
261
0
285 219
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

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

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

Т.е. ВЫБЕРИТЕ 'true', где 3 в (1,2, null) и ВЫБЕРИТЕ 'true', где 3 не в (1,2, null)

даст тот же результат, поскольку НЕ (НЕОПРЕДЕЛЕННО) все еще не определено, но не ИСТИНА

Отличный момент. выберите 1, где null в (null) не возвращает строки (ansi).

crokusek 18.07.2012 04:52

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

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

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

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

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

Govind Rai 13.09.2016 20:31
Ответ принят как подходящий

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

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

Кто-нибудь когда-нибудь указывал, что преобразование NOT IN в серию <> and меняет семантическое поведение не в этом наборе на что-то другое?

Ian Boyd 13.10.2010 18:47

@Ian - Похоже, что «A NOT IN ('X', 'Y')» на самом деле является псевдонимом для A <> 'X' AND A <> 'Y' в SQL. (Я вижу, что вы сами обнаружили это в stackoverflow.com/questions/3924694/…, но хотели убедиться, что ваше возражение было рассмотрено в этом вопросе.)

Ryan Olson 27.12.2010 22:21

Думаю, это объясняет, почему SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0); выдает строку вместо ожидаемого пустого набора результатов.

binki 23.08.2016 19:30

Это очень плохое поведение SQL-сервера, потому что, если он ожидает NULL-сравнения с использованием «IS NULL», он должен расширить предложение IN до того же поведения, а не глупо применять к себе неправильную семантику.

OzrenTkalcecKrznaric 15.09.2016 12:47

@binki, у вас запрос выполняется, если запускать здесь rextester.com/l/sql_server_online_compiler, но не работает, если запускать здесь sqlcourse.com/cgi-bin/interpreter.cgi.

Istiaque Ahmed 10.11.2017 16:57

@IstiaqueAhmed Вы уверены, что sqlcourse.com подходит для любого запроса? Я не могу найти то, что он принимает ...

binki 10.11.2017 21:47

@binki, было бы полезно, если бы вы могли подробнее рассказать о том, что вы сказали

Istiaque Ahmed 10.11.2017 23:14

@IstiaqueAhmed Я почти уверен, что sqlcourse просто не работает: imgur.com/a/zXJle. Так что у моего кода нет причин работать с ним, если он не поддерживает T-SQL.

binki 12.11.2017 00:54

@ Ян Не совсем так. Если вы интерпретируете значение null как «какое-то неизвестное значение», то семантика согласуется: 3 in (1, 2, 3, unknown) истинно, потому что вы знать, что 3 находится в группе. 3 not in (1, 2, unknown) не является ни истинным, ни ложным, потому что он может быть в группе. «Учитывая 1, 2 и то, что нам неизвестно, фактическое значение 3 отсутствует в этом наборе?» Я не уверен.

Hau 07.09.2018 07:42

Одна потенциальная путаница заключается в том, что case cast(null to boolean) then 'yes' else 'no' end приводит к no. С другой стороны, case cast(null to boolean) = false then 'yes' else 'no' end также приводит к no ... case cast(null to boolean) is null then 'yes' else 'no' end приводит к yes.

Hau 07.09.2018 07:58

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

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

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

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

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

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

Когда вы используете 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)

Оба запроса не дадут результатов

Если НЕИЗВЕСТНО было то же самое, что и ЛОЖЬ, тогда, предполагая, что первый запрос дал бы вам ЛОЖЬ, второй должен был бы оценить ИСТИНА, поскольку это было бы то же самое, что НЕ (ЛОЖЬ) .
Это не относится к делу.

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

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

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

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

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

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

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

Rostand Abear 23.06.2009 15:20

выберите party_code из abc как где party_code не в (выберите party_code из xyz, где party_code не равно нулю), но удачи, если вы забыли, что поле допускает нули, что часто бывает

Rostand Abear 24.06.2009 10:42

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

SQL NOT IN constraint and NULL values

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

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

When the predicate evaluates to UNKNOWN you don't get any rows.

Хотя это так и для 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 where clause

The result of the is a table of those rows of T for which the result of the search condition is true.

4.10 Integrity constraints

A table check constraint is satisfied if and only if the specified search condition is not false for any row of a table.

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

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

В SQL DDL (т.е. ограничениях) строки не удаляются из результата, когда они оцениваются как UNKNOWN, потому что делает удовлетворяет условию «is not false».

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

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

Честно говоря, мне нечего было сказать по этому поводу. Интересно.

Jamie Ide 24.09.2011 23:54

@Jamie Ide: На самом деле у меня есть еще один ответ на эту тему: поскольку NOT IN (subquery) с использованием нулей может дать неожиданные результаты, заманчиво полностью избегать IN (subquery) и всегда использовать NOT EXISTS (subquery) (как я когда-то!), Потому что кажется, что он всегда правильно обрабатывает нули. Однако бывают случаи, когда NOT IN (subquery) дает ожидаемый результат, тогда как NOT EXISTS (subquery) дает неожиданные результаты! Я могу еще успеть написать это, если найду свои заметки по этой теме (нужны заметки, потому что это не интуитивно понятно!) Однако вывод тот же: избегайте нулей!

onedaywhen 26.09.2011 11:18

@oneday, когда меня смущает ваше утверждение, что NULL должен быть в специальном корпусе, чтобы иметь согласованное поведение (внутренне согласованное, не соответствующее спецификации). Разве недостаточно изменить 4.10 следующим образом: «Ограничение проверки таблицы выполняется тогда и только тогда, когда указанное условие поиска истинно»?

DylanYoung 14.08.2019 22:35

@DylanYoung: Нет, спецификация сформулирована таким образом по важной причине: SQL страдает от логики трех значений, где эти значения - TRUE, FALSE и UNKNOWN. Я полагаю, что в 4.10 можно было бы читать: «Ограничение проверки таблицы выполняется тогда и только тогда, когда указанное условие поиска ИСТИНА или НЕИЗВЕСТНО для каждой строки таблицы» - обратите внимание на мое изменение в конце предложения, которое вы пропустили - - от «для любого» до «для всех». Я чувствую необходимость использовать логические значения с большой буквы, потому что значения «истина» и «ложь» в естественном языке обязательно должны относиться к классической двузначной логике.

onedaywhen 15.08.2019 10:22

@onedaywhen Ваше изменение вновь вызывает внутреннюю несогласованность, о которой вы упомянули выше. У меня вопрос, почему является ограничение удовлетворяется, когда результат НЕИЗВЕСТНЫЙ. Или, в качестве альтернативы, почему не возвращается строка результата, когда результат является UNKNOWN. Вы не объяснили причину этого несоответствия. Просто он существует в спецификации. Логическая трехзначная логика (нечеткая логика) внутренне непротиворечива (по крайней мере, в пределах, установленных Годелем и др.), Так почему же трехзначная логика SQL не является?

DylanYoung 15.08.2019 18:57

Ах, но я думаю, что понял. Это искусственное ограничение, позволяющее учитывать ожидаемое поведение пользователя (уникальные ограничения не применяются к значениям NULL). Однако, согласно логике трех значений, такое поведение на самом деле неверно: если каждый ноль представляет неизвестное значение, тогда нет ничего, что могло бы сказать, что, когда эти значения известны, они не нарушат ограничение. И, конечно же, теперь я понимаю, что вы подразумеваете под специальным регистром NULL.

DylanYoung 15.08.2019 19:03

Хотя я до сих пор не понимаю (пока), почему нельзя сделать противоположное изменение: вернуть все строки, которые являются либо НЕИЗВЕСТНЫМИ, либо ИСТИННЫМИ ... Я уверен, что это нарушает что-то еще, я просто не могу точно определить какие. Дай мне знать, если сможешь что-нибудь придумать!

DylanYoung 15.08.2019 19:06

Я бы сказал наоборот: наши нынешние ментальные модели вычислений страдают от культурной близорукости двухзначной логики (мы умеем мыслить только двоичной логикой и двоичными значениями). Проблема часто возникает и в статистике, где почти все НЕИЗВЕСТНО. Дзен-коаны могут быть хорошим упражнением, чтобы преодолеть эту близорукость.

DylanYoung 15.08.2019 19:15

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

onedaywhen 20.08.2019 17:39

@DylanYoung: обратите внимание, что SQL DML несовместим с нулями. Учтите, что в приведенной выше таблице есть несколько строк, некоторые, но не все из которых имеют значение NULL в столбце b: (1) SELECT COUNT( b ) FROM T возвращает число больше 0. (2) SELECT SUM( b ) FROM T возвращает значение NULL. Я думаю, что комитет принял во внимание, как существующие продукты SQL обрабатывали каждый случай и что большинство пользователей сочли бы более полезными, и решил быть непоследовательным. Возможно, они сделали это неохотно, но это не было недосмотром. Есть и другие «особые случаи», которые стоит изучить!

onedaywhen 20.08.2019 17:48

@oneday, когда я получу эту роль. Вопрос в том, почему DML не может быть согласован с DDL? т.е. путем возврата всех строк, для которых условие where оценивается как True или Unknown. Случай COUNT, SUM вообще не кажется несогласованным (int (NULL) == UNKNOWN, тогда как len ([null]) == 1; операторы, участвующие в подсчете и сумме, различны)

DylanYoung 21.08.2019 22:01

Вупс. Теперь я вижу несоответствие в COUNT (думаю, не при чем в SUM). Он рассматривает NULL как несуществующий !!! Это настоящий зингер :( Интересно, что он не обрабатывает (NULL, NULL) таким же образом. SUM кажется прекрасным; в PostgreSQL он выдает ошибку, когда передается NULL, что примерно эквивалентно возврату NULL. Я подозреваю, что это уступка реальности, как вы говорите, но лучше было бы исправить плохую практику программирования: если вы не хотите подсчитывать NULL, вы должны явно исключить их из своего запроса.

DylanYoung 21.08.2019 22:20

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

Рассмотрим таблицу sp для представления поставщиков (sno), которые, как известно, поставляют детали (pno) в количестве (qty). В настоящее время таблица содержит следующие значения:

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

Обратите внимание, что количество допускает значение NULL, т.е. чтобы иметь возможность зафиксировать факт, что поставщик поставляет детали, даже если неизвестно, в каком количестве.

Задача состоит в том, чтобы найти поставщиков, которым известен номер детали поставки «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;

Мой Бог. Спасибо, что написали это ... это сводило меня с ума ...

Govind Rai 13.09.2016 20:24

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

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

У меня была проблема с запросом внешнего соединения, который не возвращал никаких записей в особых ситуациях. Поэтому проверил это решение как для сценария Null, так и для сценария существующих записей, и оно сработало для меня. Если возникнут другие проблемы, я буду упоминать здесь, большое спасибо.

QMaster 24.02.2018 01:00

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⁽¹⁾  | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN⁽²⁾            | UNKNOWN⁽³⁾                  |

Заметить, что:

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

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

Другие вопросы по теме