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