Wartości NULL wewnątrz klauzuli NOT IN

245

Ten problem pojawił się, gdy otrzymałem różne rekordy dla tego, co uważałem za identyczne zapytanie, jedno z użyciem not in whereograniczenia, a drugie left join. Tabela w not inograniczeniu miała jedną wartość zerową (złe dane), co spowodowało, że zapytanie zwróciło liczbę 0 rekordów. Rozumiem dlaczego, ale mogłem skorzystać z pomocy, by w pełni zrozumieć tę koncepcję.

Mówiąc wprost, dlaczego zapytanie A zwraca wynik, a B nie?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

To było na SQL Server 2005. Odkryłem również, że wywołanie set ansi_nulls offpowoduje, że B zwraca wynik.

Jamie Ide
źródło

Odpowiedzi:

283

Zapytanie A jest takie samo jak:

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

Ponieważ 3 = 3to prawda, otrzymujesz wynik.

Zapytanie B jest takie samo jak:

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

Gdy ansi_nullsjest włączony, 3 <> nulljest NIEZNANY, więc predykat zwraca wartość NIEZNANY i nie otrzymujesz żadnych wierszy.

Kiedy ansi_nullsjest wyłączone, 3 <> nulljest prawdziwe, więc predykat zwraca wartość true i otrzymujesz wiersz.

Brannon
źródło
11
Czy ktoś kiedykolwiek zauważył, że przekształcenie NOT INw szereg <> andzmian zachowania semantycznego nie w tym zestawie na coś innego?
Ian Boyd
8
@Ian - Wygląda na to, że „A NOT IN („ X ”,„ Y ”)” faktycznie jest aliasem dla A <> „X” i A <> „Y” w SQL. (Widzę, że sam to odkryłeś na stackoverflow.com/questions/3924694/… , ale chciałem się upewnić, że Twój sprzeciw został rozwiązany w tym pytaniu.)
Ryan Olson
Myślę, że to wyjaśnia, dlaczego SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);zwraca wiersz zamiast pustego zestawu wyników, którego się spodziewałem.
binki
2
Jest to bardzo słabe zachowanie serwera SQL, ponieważ jeśli oczekuje porównania NULL za pomocą „IS NULL”, to powinien rozwinąć klauzulę IN do tego samego zachowania i nie głupio stosować niewłaściwej semantyki do siebie.
OzrenTkalcecKrznaric
@binki, zapytanie jest wykonywane, jeśli jest uruchomione tutaj rextester.com/l/sql_server_online_compiler, ale nie działa, jeśli uruchomiono tutaj sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed,
53

Ilekroć używasz NULL, naprawdę masz do czynienia z logiką trójwartościową.

Twoje pierwsze zapytanie zwraca wyniki, gdy klauzula WHERE ocenia:

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

Drugie:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

NIEZNANY nie jest tym samym, co FAŁSZ, można go łatwo przetestować, dzwoniąc:

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

Oba zapytania nie dadzą żadnych wyników

Jeśli NIEZNANY byłby taki sam jak FAŁSZ, to przy założeniu, że pierwsze zapytanie dałoby FAŁSZ, drugie musiałoby ocenić na PRAWDA, ponieważ byłoby to to samo, co NIE (FAŁSZ).
Tak nie jest.

Jest bardzo dobry artykuł na ten temat na SqlServerCentral .

Cały problem wartości NULL i logiki trójwartościowej może początkowo być nieco mylący, ale konieczne jest zrozumienie, aby pisać poprawne zapytania w TSQL

Kolejny artykuł, który poleciłbym to SQL Aggregate Functions i NULL .

kristof
źródło
33

NOT IN zwraca 0 rekordów w porównaniu z nieznaną wartością

Ponieważ NULLjest to nieznane, NOT INzapytanie zawierające a NULLlub NULLs na liście możliwych wartości zawsze zwróci 0rekordy, ponieważ nie ma sposobu, aby upewnić się, że NULLwartość nie jest testowaną wartością.

YonahW
źródło
3
Oto odpowiedź w skrócie. Odkryłem, że jest to łatwiejsze do zrozumienia, nawet bez żadnego przykładu.
Govind Rai,
18

Porównaj z null jest niezdefiniowane, chyba że użyjesz IS NULL.

Porównując wartość 3 z wartością NULL (zapytanie A), zwraca wartość niezdefiniowaną.

Tj. WYBIERZ „prawda” gdzie 3 w (1,2, null) i WYBIERZ „prawda”, gdzie 3 nie w (1,2, null)

da taki sam wynik, ponieważ NIE (NIEZDEFINIOWANY) jest nadal niezdefiniowany, ale NIE PRAWDA

Sunny Milenov
źródło
Świetny punkt wybierz 1, gdzie null w (null) nie zwraca wierszy (ansi).
crokusek
9

Tytuł tego pytania w momencie pisania brzmi

Ograniczenie SQL NOT IN i wartości NULL

Z tekstu pytania wynika, że ​​problem występował w SELECTzapytaniu SQL DML , a nie w SQL DDL CONSTRAINT.

Jednak, zwłaszcza biorąc pod uwagę treść tytułu, chcę zauważyć, że niektóre stwierdzenia tutaj zawarte są potencjalnie wprowadzającymi w błąd stwierdzeniami, które są podobne do (parafrazowanie)

Gdy predykat ma wartość UNKNOWN, nie otrzymujesz żadnych wierszy.

Chociaż dotyczy to SQL DML, to przy rozważaniu ograniczeń efekt jest inny.

Rozważ tę bardzo prostą tabelę z dwoma ograniczeniami wziętymi bezpośrednio z predykatów w pytaniu (i uwzględnionych w doskonałej odpowiedzi przez @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;

Zgodnie z odpowiedzią @ Brannona, pierwsze ograniczenie (użycie IN) zwraca wartość PRAWDA, a drugie ograniczenie (użycie NOT IN) daje wartość NIEZNANE. Jednak wstawka się udaje! Dlatego w tym przypadku nie jest słuszne powiedzenie „nie dostajesz żadnych wierszy”, ponieważ rzeczywiście wstawiliśmy wiersz.

Powyższy efekt jest rzeczywiście poprawny w odniesieniu do standardu SQL-92. Porównaj i skontrastuj następującą sekcję ze specyfikacji SQL-92

7.6 gdzie klauzula

Wynik jest tabelą tych wierszy T, dla których wynik warunku wyszukiwania jest prawdziwy.

4.10 Ograniczenia integralności

Ograniczenie sprawdzania tabeli jest spełnione tylko wtedy, gdy określony warunek wyszukiwania nie jest fałszywy dla żadnego wiersza tabeli.

Innymi słowy:

W SQL DML wiersze są usuwane z wyniku, gdy WHEREwartość jest NIEZNANA, ponieważ nie spełnia warunku „prawda”.

W SQL DDL (tj ograniczeń), wiersze nie są usuwane z wyniku podczas oceny nieznanych, ponieważ nie spełnia warunku „nie jest fałszywa”.

Chociaż efekty odpowiednio w SQL DML i SQL DDL mogą wydawać się sprzeczne, istnieje praktyczny powód, aby nadać wynikom UNKNOWN „korzyść z wątpliwości”, umożliwiając im spełnienie ograniczenia (bardziej poprawnie, pozwalając im nie spełnić ograniczenia) : bez tego zachowania wszystkie ograniczenia musiałyby jawnie obsługiwać wartości zerowe, co byłoby bardzo niezadowalające z perspektywy projektowania języka (nie wspominając o odpowiednim bólu dla programistów!)

ps, jeśli uważasz, że przestrzeganie takiej logiki, jak „nieznane, nie jest w stanie spełnić ograniczenia”, jest dla mnie tak trudne, jak ja to piszę, to rozważ, że możesz zrezygnować z tego wszystkiego po prostu unikając zerowalnych kolumn w SQL DDL i wszystkiego w SQL DML, który generuje wartości zerowe (np. Sprzężenia zewnętrzne)!

oneedaywhen
źródło
Szczerze mówiąc, nie sądziłem, że pozostało już cokolwiek do powiedzenia na ten temat. Ciekawy.
Jamie Ide
2
@Jamie Ide: Właściwie mam inną odpowiedź na ten temat: ponieważ NOT IN (subquery)włączenie wartości zerowych może dać nieoczekiwane rezultaty, kuszące jest unikanie ich IN (subquery)całkowicie i zawsze używanie NOT EXISTS (subquery)(jak kiedyś!), Ponieważ wydaje się, że zawsze poprawnie obsługuje wartości zerowe. Są jednak przypadki, w których NOT IN (subquery)daje oczekiwany wynik, a NOT EXISTS (subquery)daje nieoczekiwane wyniki! Mogę jeszcze napisać to, jeśli mogę znaleźć moje notatki na ten temat (potrzebuję notatek, ponieważ nie są one intuicyjne!) Wniosek jest taki sam: unikaj zer.
poniedziałek
@oneday, kiedy jestem zdezorientowany przez twoje twierdzenie, że NULL musiałoby być specjalnie zapakowane, aby mieć spójne zachowanie (wewnętrznie spójne, niezgodne ze specyfikacją). Czy nie wystarczy zmienić 4.10 na „Ograniczenie sprawdzania tabeli jest spełnione wtedy i tylko wtedy, gdy określony warunek wyszukiwania jest spełniony”?
DylanYoung
@DylanYoung: Nie, specyfikacja jest sformułowana w ten sposób z ważnego powodu: SQL cierpi na logikę trzech wartości, gdzie te wartości są TRUE , FALSEi UNKNOWN. Podejrzewam, że 4.10 mógł przeczytać: „Ograniczenie sprawdzania tabeli jest spełnione wtedy i tylko wtedy, gdy podany warunek wyszukiwania jest PRAWDA lub NIEZNANY dla każdego wiersza tabeli” - zwróć uwagę na moją zmianę na końcu zdania - którą pominąłeś - - od „dla każdego” do „dla wszystkich”. Czuję potrzebę kapitalizacji wartości logicznych, ponieważ znaczenie „prawda” i „fałsz” w języku naturalnym musi z pewnością odnosić się do klasycznej logiki dwuwartościowej.
dniu
1
Zastanów się: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- chodzi tutaj o to, baby albo była równa, aalbo zerowa. Gdyby ograniczenie miało wynik TRUE, aby je spełnić, musielibyśmy zmienić ograniczenie, aby jawnie obsługiwać wartości zerowe, np CHECK( a = b OR b IS NULL ). Tak więc każde ograniczenie musiałoby mieć ...OR IS NULLlogikę dodaną przez użytkownika dla każdej zaangażowanej zerowalnej kolumny: większa złożoność, więcej błędów, gdy zapomniały to zrobić itp. Myślę więc, że komitet ds. Standardów SQL po prostu starał się być pragmatyczny.
jednego dnia
7

W A 3 jest testowane pod kątem równości z każdym członkiem zbioru, dając (FAŁSZ, FAŁSZ, PRAWDA, NIEZNANY). Ponieważ jeden z elementów ma wartość PRAWDA, warunek jest PRAWDA. (Możliwe jest również, że dochodzi tutaj do zwarcia, więc faktycznie zatrzymuje się, gdy tylko trafi na pierwszą PRAWDĘ i nigdy nie ocenia 3 = NULL.)

W B myślę, że ocenia warunek jako NIE (3 w (1,2, null)). Testowanie 3 pod kątem równości względem ustawionych wydajności (FAŁSZ, FAŁSZ, NIEZNANY), który jest agregowany do NIEZNANY. NIE (NIEZNANE) zwraca NIEZNANE. Tak więc ogólna prawda tego warunku jest nieznana, co na końcu jest zasadniczo traktowane jako FAŁSZ.

Dave Costa
źródło
7

Można wywnioskować z odpowiedzi, które NOT IN (subquery)nie obsługują poprawnie wartości zerowych i należy ich unikać na korzyść NOT EXISTS. Taki wniosek może być jednak przedwczesny. W poniższym scenariuszu, przypisanym Chrisowi Date (programowanie i projektowanie baz danych, tom 2 nr 9, wrzesień 1989), oznacza to, NOT INże poprawnie obsługuje wartości zerowe i zwraca poprawny wynik NOT EXISTS.

Rozważ tabelę spprzedstawiającą dostawców ( sno), o których wiadomo, że dostarczają części ( pno) w ilości ( qty). Tabela zawiera obecnie następujące wartości:

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

Należy pamiętać, że ilość jest zerowa, tj. Aby móc odnotować fakt, że dostawca jest znany z dostarczania części, nawet jeśli nie wiadomo, w jakiej ilości.

Zadanie polega na znalezieniu dostawców, którzy są znanymi dostawcami o numerze katalogowym „P1”, ale nie w ilości 1000.

Następujące zastosowania NOT INdo prawidłowej identyfikacji wyłącznie dostawcy „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'
                       );

Jednak poniższe zapytanie wykorzystuje tę samą ogólną strukturę, ale z wynikiem, NOT EXISTSale niepoprawnie zawiera w wynikach dostawcę „S1” (tj. Dla którego ilość jest zerowa):

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
                      );

Więc NOT EXISTS nie jest to srebrna kula, którą mogła się pojawić!

Oczywiście źródłem problemu jest obecność wartości zerowych, dlatego „prawdziwym” rozwiązaniem jest wyeliminowanie tych wartości zerowych.

Można to osiągnąć (między innymi możliwymi projektami) za pomocą dwóch tabel:

  • sp dostawcy znani z dostarczania części
  • spq dostawcy znani z dostarczania części w znanych ilościach

Należy zauważyć, że prawdopodobnie ograniczenie klucz obcy gdzie spqreferencjesp .

Wynik można następnie uzyskać za pomocą operatora relacyjnego „minus” (będącego EXCEPTsłowem kluczowym w Standard SQL) np

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;
oneedaywhen
źródło
1
O mój Boże. Dziękuję za napisanie tego ... doprowadzało mnie to do szału ...
Govind Rai,
6

Null oznacza i brak danych, to znaczy jest nieznany, a nie wartość danych o niczym. Ludziom ze środowiska programistycznego bardzo łatwo jest to pomylić, ponieważ w językach typu C przy użyciu wskaźników null jest niczym.

Dlatego w pierwszym przypadku 3 jest rzeczywiście w zbiorze (1,2,3, null), więc zwracana jest prawda

W drugim jednak możesz go zredukować do

wybierz „prawda”, gdzie 3 nie jest w (null)

Nic więc nie jest zwracane, ponieważ parser nic nie wie o zestawie, z którym go porównujesz - nie jest to zestaw pusty, ale zestaw nieznany. Użycie (1, 2, null) nie pomaga, ponieważ zestaw (1,2) jest oczywiście fałszywy, ale wtedy grasz przeciwko nieznanemu, co jest nieznane.

Cruachan
źródło
6

JEŚLI chcesz filtrować za pomocą NOT IN dla podzapytania zawierającego wartości NULL, po prostu zaznacz opcję non null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Mihai
źródło
Miałem problem z zewnętrznym zapytaniem o przyłączenie, które nie zwróciło żadnych rekordów w szczególnych sytuacjach, więc sprawdziłem to rozwiązanie zarówno dla scenariusza Null, jak i istniejącego rekordu i zadziałało dla mnie, jeśli pojawią się inne problemy, będę tu wspominać, dziękuję bardzo.
QMaster
1

to jest dla chłopca:

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

działa to niezależnie od ustawień ansi

CB
źródło
dla pierwotnego pytania: B: wybierz „prawda”, gdzie 3 nie jest w (1, 2, null), należy wykonać sposób na usunięcie null, np. wybierz „prawda”, gdzie 3 nie jest w (1, 2, isnull (null, 0) ) ogólna logika jest taka, że ​​jeśli przyczyną jest NULL, znajdź sposób na usunięcie wartości NULL na pewnym etapie zapytania.
wybierz kod_partycji z abc jako gdzie kod_partycji nie znajduje się w (wybierz kod_partycji z xyz, gdzie kod_partycji nie jest pusty), ale powodzenia, jeśli zapomniałeś, że pole pozwala na wartości zerowe, co często ma miejsce
1

SQL używa logiki trójwartościowej dla wartości prawdy. TheINZapytanie produkuje oczekiwany wynik:

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

Ale dodanie NOT nie odwraca wyników:

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

Wynika to z faktu, że powyższe zapytanie jest równoważne z następującą:

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

Oto jak oceniana jest klauzula 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)                 |

Zauważ, że:

  1. Porównanie dotyczące NULLwydajnościUNKNOWN
  2. ORWyrażenie gdzie żaden z operandów TRUEa co najmniej jeden operand UNKNOWNwydajności UNKNOWN( ref )
  3. NOTZ UNKNOWNwydajnością UNKNOWN( ref )

Możesz rozszerzyć powyższy przykład na więcej niż dwie wartości (np. NULL, 1 i 2), ale wynik będzie taki sam: jeśli jedna z wartości jest NULLinna, żaden wiersz nie będzie pasował.

Salman A.
źródło