Значення 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 offвикликає B повернення результату.

Відповіді:


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- це істина, тому предикат оцінюється як істинний, і ви отримуєте рядок.


11
Хтось колись вказував, що перетворення NOT INна серію <> andзмінює смислову поведінку не в цій множині на щось інше?
Ян Бойд

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

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

НЕВІДОМЛЕНО не те саме, що ЛІЖ, ви можете легко перевірити його, зателефонувавши:

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

Обидва запити не дадуть результатів

Якщо UNKNOWN був таким самим, як FALSE, тоді припускаючи, що перший запит дасть вам FALSE, другий повинен був би оцінити TRUE, оскільки це було б те саме, що NOT (FALSE).
Це не так.

На цій темі в SqlServerCentral є дуже хороша стаття .

Весь випуск NULL і трицільової логіки спочатку може бути трохи заплутаним, але це важливо зрозуміти, щоб написати правильні запити в TSQL

Інша стаття, яку я рекомендував би - це функції SQL Aggregate Functions і NULL .


33

NOT IN повертає 0 записів у порівнянні з невідомим значенням

Оскільки NULLце невідомо, NOT INзапит, що містить a NULLабо NULLs у списку можливих значень, завжди повертатиме 0записи, оскільки немає способу бути впевненим, що NULLзначення не є тестованим значенням.


3
Це відповідь у двох словах. Мені це було легше зрозуміти навіть без прикладу.
Говінд Рай

18

Порівняння з null не визначено, якщо ви не використовуєте IS NULL.

Отже, порівнюючи 3 з NULL (запит A), він повертається невизначеним.

Тобто SELECT 'true', де 3 in (1,2, null) і SELECT 'true', де 3 not in (1,2, null)

призведе до того ж результату, оскільки НЕ (НЕ ВКАЗАНО) все ще не визначено, але не ІСТИНА


Чудова точка. виберіть 1, де null in (null) не повертає рядки (ansi).
crokusek

9

Назва цього питання на момент написання -

SQL NOT IN обмеження та NULL значення

З тексту запитання випливає, що проблема виникала в SELECTзапиті SQL DML , а не в 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;

Відповідно до відповіді @ Браннона, перше обмеження (використання IN) оцінюється як ІСТИНА, а друге обмеження (використання NOT IN) оцінюється НЕВІДОМО. Однак вставка вдається! Тому в цьому випадку не зовсім коректно сказати: "Ви не отримуєте жодних рядків", тому що ми насправді отримали рядок в результаті.

Вищевказаний ефект справді правильний стосовно стандарту SQL-92. Порівняйте та порівняйте наступний розділ із специфікацією SQL-92

7.6, де застереження

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

4.10 Обмеження цілісності

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

Іншими словами:

У SQL DML рядки видаляються з результату, коли WHEREоцінюється НЕВІДОМО, оскільки він не відповідає умові "вірно".

У SQL DDL (тобто обмеження), рядки не видаляються з результату , коли вони оцінюють Невідомий , тому що це дійсно задовольняє умова «перестав бути хибним».

Хоча ефекти в SQL DML і SQL DDL відповідно можуть здаватися суперечливими, є практична причина, щоб дати UNKNOWN результатам «користь сумнівів», дозволяючи їм задовольняти обмеження (правильніше, дозволяючи їм не задовольняти обмеження) : без такої поведінки кожне обмеження повинно було б чітко обробляти нулі, і це було б незадовільно з точки зору дизайну мови (не кажучи вже про правильний біль для кодерів!)

ps, якщо вам здається, що дотримуватися такої логіки, як "невідомо не не вдається задовольнити обмеження", ви вважаєте, що, як я це пишу, ви можете відмовитись у всьому цьому, уникаючи нульових стовпців у SQL DDL і нічого в SQL DML, який створює нулі (наприклад, зовнішнє з'єднання)!


Я, чесно кажучи, не думав, що є що сказати на цю тему. Цікаво.
Jamie Ide

2
@Jamie Ide: Насправді, у мене є ще одна відповідь на цю тему: оскільки NOT IN (subquery)залучення нулів може дати несподівані результати, спокусливо уникати IN (subquery)повністю і завжди використовувати NOT EXISTS (subquery)(як я колись робив!), Тому що, здається, він завжди обробляє нулі правильно. Однак є випадки, коли NOT IN (subquery)дає очікуваний результат, тоді як NOT EXISTS (subquery)дає несподівані результати! Я можу взятись до написання цього питання, але якщо я зможу знайти свої замітки з цього приводу (потрібні нотатки, оскільки це не інтуїтивно зрозуміло!) Висновок такий же, проте: уникайте нулів!
день, коли

@onedaywhen Мене бентежить ваше твердження про те, що NULL потребує спеціальних обставин, щоб мати послідовну поведінку (внутрішньо послідовне, не узгоджується зі специфікацією). Чи не буде достатньо змінити 4.10, щоб прочитати "Обмеження перевірки таблиці задовольняється, якщо і лише в тому випадку, якщо вказана умова пошуку істинна"?
DylanYoung

@DylanYoung: Ні, специфікація так сформульована з вирішальної причини: SQL страждає від трьох логічних значень, де ці значення є TRUE, FALSEі UNKNOWN. Я припускаю, що 4.10 міг би прочитати: "Обмеження для перевірки таблиці задовольняється, якщо і лише у тому випадку, якщо вказана умова пошуку є ПРАВИЛЬНОЮ або НЕВІДОМЛЕНОЮ для кожного рядка таблиці" - відзначте мою зміну до кінця речення - яку ви пропустили - - від "для будь-якого" до "для всіх". Я відчуваю необхідність використання великих логічних значень, оскільки значення "істинного" і "помилкового" в природній мові, безумовно, повинні посилатися на класичну двозначну логіку.
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 просто намагався бути прагматичним.
день, коли

7

У А 3 випробовується рівність проти кожного члена набору, поступаючись (FALSE, FALSE, TRUE, UNKNOWN). Оскільки одним із елементів є ІСТИНА, умова - ІСТИНА. (Можливо також, що тут відбувається деяке коротке замикання, тому воно фактично зупиняється, як тільки потрапляє на першу ІСТИНУ і ніколи не оцінює 3 = NULL.)

В B я думаю, що вона оцінює умову як NOT (3 in (1,2, null)). Тестування 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;

1
О Боже мій. Дякую, що насправді це написали .... це зводило мене з розуму ..
Говінд Рай

6

Нуль означає і відсутність даних, тобто це невідомо, не значення даних нічого. Люди з програмування дуже легко переплутати це, оскільки в мовах типу C при використанні покажчиків null насправді нічого.

Отже, у першому випадку 3 дійсно знаходиться у множині (1,2,3, null), тому істина повертається

У другому, однак ви можете зменшити його

виберіть "true", де 3 not in (null)

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


6

ЯКЩО ви хочете фільтрувати з NOT IN для підзапросу, що містить NULLs, просто встановіть прапорець для ненулевого

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

У мене виникли проблеми із зовнішнім запитом приєднання, який не повертав жодних записів у спеціальних ситуаціях, тому перевірив це рішення як для сценарію 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);

це працює незалежно від налаштувань ансі


для початкового запитання: B: виберіть "true", де 3, не в (1, 2, null), слід виконати спосіб видалення нулів, наприклад, виберіть "true", де 3 не в (1, 2, isnull (null, 0) ) загальна логіка полягає в тому, що якщо NULL є причиною, то знайдіть спосіб видалити значення NULL на якомусь етапі запиту.

виберіть party_code від abc як де 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)

Ось як оцінюється пункт де:

| 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то жоден рядок не збігатиметься.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.