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


Сравнить с нулевым значением не определено, если вы не используете IS NULL.
Итак, при сравнении 3 с NULL (запрос A) он возвращает undefined.
Т.е. ВЫБЕРИТЕ 'true', где 3 в (1,2, null) и ВЫБЕРИТЕ 'true', где 3 не в (1,2, null)
даст тот же результат, поскольку НЕ (НЕОПРЕДЕЛЕННО) все еще не определено, но не ИСТИНА
В A, 3 проверяется на равенство по отношению к каждому члену набора, давая (ЛОЖЬ, ЛОЖЬ, ИСТИНА, НЕИЗВЕСТНО). Поскольку один из элементов ИСТИНА, условие ИСТИНА. (Также возможно, что здесь происходит какое-то короткое замыкание, поэтому оно фактически останавливается, как только достигает первого значения ИСТИНА, и никогда не оценивает 3 = NULL.)
В B, я думаю, он оценивает условие как NOT (3 in (1,2, null)). Проверка 3 на равенство с набором дает (ЛОЖЬ, ЛОЖЬ, НЕИЗВЕСТНО), которое агрегировано до НЕИЗВЕСТНО. НЕ (НЕИЗВЕСТНО) означает НЕИЗВЕСТНО. Таким образом, в целом истинность условия неизвестна, что, по сути, трактуется как ЛОЖЬ.
NOT IN возвращает 0 записей при сравнении с неизвестным значениемПоскольку NULL неизвестен, запрос NOT IN, содержащий NULL или NULL в списке возможных значений, всегда будет возвращать записи 0, поскольку нет способа быть уверенным, что значение NULL не является проверяемым значением.
Это краткий ответ. Я обнаружил, что это легче понять даже без каких-либо примеров.
Запрос 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 - Похоже, что «A NOT IN ('X', 'Y')» на самом деле является псевдонимом для A <> 'X' AND A <> 'Y' в SQL. (Я вижу, что вы сами обнаружили это в stackoverflow.com/questions/3924694/…, но хотели убедиться, что ваше возражение было рассмотрено в этом вопросе.)
Думаю, это объясняет, почему SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0); выдает строку вместо ожидаемого пустого набора результатов.
Это очень плохое поведение SQL-сервера, потому что, если он ожидает NULL-сравнения с использованием «IS NULL», он должен расширить предложение IN до того же поведения, а не глупо применять к себе неправильную семантику.
@binki, у вас запрос выполняется, если запускать здесь rextester.com/l/sql_server_online_compiler, но не работает, если запускать здесь sqlcourse.com/cgi-bin/interpreter.cgi.
@IstiaqueAhmed Вы уверены, что sqlcourse.com подходит для любого запроса? Я не могу найти то, что он принимает ...
@binki, было бы полезно, если бы вы могли подробнее рассказать о том, что вы сказали
@IstiaqueAhmed Я почти уверен, что sqlcourse просто не работает: imgur.com/a/zXJle. Так что у моего кода нет причин работать с ним, если он не поддерживает T-SQL.
@ Ян Не совсем так. Если вы интерпретируете значение null как «какое-то неизвестное значение», то семантика согласуется: 3 in (1, 2, 3, unknown) истинно, потому что вы знать, что 3 находится в группе. 3 not in (1, 2, unknown) не является ни истинным, ни ложным, потому что он может быть в группе. «Учитывая 1, 2 и то, что нам неизвестно, фактическое значение 3 отсутствует в этом наборе?» Я не уверен.
Одна потенциальная путаница заключается в том, что 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.
Нулевое значение означает отсутствие данных, то есть неизвестное значение, а не нулевое значение данных. Людям, имеющим опыт программирования, очень легко запутать это, потому что в языках типа 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 на каком-то этапе запроса.
выберите party_code из abc как где party_code не в (выберите party_code из xyz, где party_code не равно нулю), но удачи, если вы забыли, что поле допускает нули, что часто бывает
Название этого вопроса на момент написания:
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: На самом деле у меня есть еще один ответ на эту тему: поскольку NOT IN (subquery) с использованием нулей может дать неожиданные результаты, заманчиво полностью избегать IN (subquery) и всегда использовать NOT EXISTS (subquery) (как я когда-то!), Потому что кажется, что он всегда правильно обрабатывает нули. Однако бывают случаи, когда NOT IN (subquery) дает ожидаемый результат, тогда как NOT EXISTS (subquery) дает неожиданные результаты! Я могу еще успеть написать это, если найду свои заметки по этой теме (нужны заметки, потому что это не интуитивно понятно!) Однако вывод тот же: избегайте нулей!
@oneday, когда меня смущает ваше утверждение, что NULL должен быть в специальном корпусе, чтобы иметь согласованное поведение (внутренне согласованное, не соответствующее спецификации). Разве недостаточно изменить 4.10 следующим образом: «Ограничение проверки таблицы выполняется тогда и только тогда, когда указанное условие поиска истинно»?
@DylanYoung: Нет, спецификация сформулирована таким образом по важной причине: SQL страдает от логики трех значений, где эти значения - TRUE, FALSE и UNKNOWN. Я полагаю, что в 4.10 можно было бы читать: «Ограничение проверки таблицы выполняется тогда и только тогда, когда указанное условие поиска ИСТИНА или НЕИЗВЕСТНО для каждой строки таблицы» - обратите внимание на мое изменение в конце предложения, которое вы пропустили - - от «для любого» до «для всех». Я чувствую необходимость использовать логические значения с большой буквы, потому что значения «истина» и «ложь» в естественном языке обязательно должны относиться к классической двузначной логике.
@onedaywhen Ваше изменение вновь вызывает внутреннюю несогласованность, о которой вы упомянули выше. У меня вопрос, почему является ограничение удовлетворяется, когда результат НЕИЗВЕСТНЫЙ. Или, в качестве альтернативы, почему не возвращается строка результата, когда результат является UNKNOWN. Вы не объяснили причину этого несоответствия. Просто он существует в спецификации. Логическая трехзначная логика (нечеткая логика) внутренне непротиворечива (по крайней мере, в пределах, установленных Годелем и др.), Так почему же трехзначная логика SQL не является?
Ах, но я думаю, что понял. Это искусственное ограничение, позволяющее учитывать ожидаемое поведение пользователя (уникальные ограничения не применяются к значениям NULL). Однако, согласно логике трех значений, такое поведение на самом деле неверно: если каждый ноль представляет неизвестное значение, тогда нет ничего, что могло бы сказать, что, когда эти значения известны, они не нарушат ограничение. И, конечно же, теперь я понимаю, что вы подразумеваете под специальным регистром NULL.
Хотя я до сих пор не понимаю (пока), почему нельзя сделать противоположное изменение: вернуть все строки, которые являются либо НЕИЗВЕСТНЫМИ, либо ИСТИННЫМИ ... Я уверен, что это нарушает что-то еще, я просто не могу точно определить какие. Дай мне знать, если сможешь что-нибудь придумать!
Я бы сказал наоборот: наши нынешние ментальные модели вычислений страдают от культурной близорукости двухзначной логики (мы умеем мыслить только двоичной логикой и двоичными значениями). Проблема часто возникает и в статистике, где почти все НЕИЗВЕСТНО. Дзен-коаны могут быть хорошим упражнением, чтобы преодолеть эту близорукость.
Рассмотрим: 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 просто попытался быть прагматичным.
@DylanYoung: обратите внимание, что SQL DML несовместим с нулями. Учтите, что в приведенной выше таблице есть несколько строк, некоторые, но не все из которых имеют значение NULL в столбце b: (1) SELECT COUNT( b ) FROM T возвращает число больше 0. (2) SELECT SUM( b ) FROM T возвращает значение NULL. Я думаю, что комитет принял во внимание, как существующие продукты SQL обрабатывали каждый случай и что большинство пользователей сочли бы более полезными, и решил быть непоследовательным. Возможно, они сделали это неохотно, но это не было недосмотром. Есть и другие «особые случаи», которые стоит изучить!
@oneday, когда я получу эту роль. Вопрос в том, почему DML не может быть согласован с DDL? т.е. путем возврата всех строк, для которых условие where оценивается как True или Unknown. Случай COUNT, SUM вообще не кажется несогласованным (int (NULL) == UNKNOWN, тогда как len ([null]) == 1; операторы, участвующие в подсчете и сумме, различны)
Вупс. Теперь я вижу несоответствие в COUNT (думаю, не при чем в SUM). Он рассматривает NULL как несуществующий !!! Это настоящий зингер :( Интересно, что он не обрабатывает (NULL, NULL) таким же образом. SUM кажется прекрасным; в PostgreSQL он выдает ошибку, когда передается NULL, что примерно эквивалентно возврату NULL. Я подозреваю, что это уступка реальности, как вы говорите, но лучше было бы исправить плохую практику программирования: если вы не хотите подсчитывать NULL, вы должны явно исключить их из своего запроса.
Из ответов здесь можно сделать вывод, что 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;
Мой Бог. Спасибо, что написали это ... это сводило меня с ума ...
ЕСЛИ вы хотите фильтровать с помощью NOT IN для подзапроса, содержащего NULL, просто проверьте, не является ли значение null
SELECT blah FROM t WHERE blah NOT IN
(SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
У меня была проблема с запросом внешнего соединения, который не возвращал никаких записей в особых ситуациях. Поэтому проверил это решение как для сценария Null, так и для сценария существующих записей, и оно сработало для меня. Если возникнут другие проблемы, я буду упоминать здесь, большое спасибо.
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⁽³⁾ |
Заметить, что:
NULL дает UNKNOWN.OR, где ни один из операндов не является TRUE и хотя бы один операнд является UNKNOWN, дает UNKNOWN (ссылка)NOT из UNKNOWN дает UNKNOWN (ссылка)Вы можете расширить приведенный выше пример до более чем двух значений (например, NULL, 1 и 2), но результат будет таким же: если одно из значений - NULL, то ни одна строка не будет соответствовать.
Отличный момент. выберите 1, где null в (null) не возвращает строки (ansi).