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
where
ograniczenia, a drugie left join
. Tabela w not in
ograniczeniu 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 off
powoduje, że B zwraca wynik.
NOT IN
w szereg<> and
zmian zachowania semantycznego nie w tym zestawie na coś innego?SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);
zwraca wiersz zamiast pustego zestawu wyników, którego się spodziewałem.Ilekroć używasz NULL, naprawdę masz do czynienia z logiką trójwartościową.
Twoje pierwsze zapytanie zwraca wyniki, gdy klauzula WHERE ocenia:
Drugie:
NIEZNANY nie jest tym samym, co FAŁSZ, można go łatwo przetestować, dzwoniąc:
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 .
źródło
NOT IN
zwraca 0 rekordów w porównaniu z nieznaną wartościąPonieważ
NULL
jest to nieznane,NOT IN
zapytanie zawierające aNULL
lubNULL
s na liście możliwych wartości zawsze zwróci0
rekordy, ponieważ nie ma sposobu, aby upewnić się, żeNULL
wartość nie jest testowaną wartością.źródło
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
źródło
Tytuł tego pytania w momencie pisania brzmi
Z tekstu pytania wynika, że problem występował w
SELECT
zapytaniu SQL DML , a nie w SQL DDLCONSTRAINT
.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)
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):
Zgodnie z odpowiedzią @ Brannona, pierwsze ograniczenie (użycie
IN
) zwraca wartość PRAWDA, a drugie ograniczenie (użycieNOT 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
Innymi słowy:
W SQL DML wiersze są usuwane z wyniku, gdy
WHERE
wartość 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)!
źródło
NOT IN (subquery)
włączenie wartości zerowych może dać nieoczekiwane rezultaty, kuszące jest unikanie ichIN (subquery)
całkowicie i zawsze używanieNOT EXISTS (subquery)
(jak kiedyś!), Ponieważ wydaje się, że zawsze poprawnie obsługuje wartości zerowe. Są jednak przypadki, w którychNOT IN (subquery)
daje oczekiwany wynik, aNOT 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.TRUE
,FALSE
iUNKNOWN
. 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.CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );
- chodzi tutaj o to,b
aby albo była równa,a
albo zerowa. Gdyby ograniczenie miało wynik TRUE, aby je spełnić, musielibyśmy zmienić ograniczenie, aby jawnie obsługiwać wartości zerowe, npCHECK( a = b OR b IS NULL )
. Tak więc każde ograniczenie musiałoby mieć...OR IS NULL
logikę 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.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.
źródło
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 wynikNOT EXISTS
.Rozważ tabelę
sp
przedstawiają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: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 IN
do prawidłowej identyfikacji wyłącznie dostawcy „S2”:Jednak poniższe zapytanie wykorzystuje tę samą ogólną strukturę, ale z wynikiem,
NOT EXISTS
ale niepoprawnie zawiera w wynikach dostawcę „S1” (tj. Dla którego ilość jest zerowa):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ęścispq
dostawcy znani z dostarczania części w znanych ilościachNależy zauważyć, że prawdopodobnie ograniczenie klucz obcy gdzie
spq
referencjesp
.Wynik można następnie uzyskać za pomocą operatora relacyjnego „minus” (będącego
EXCEPT
słowem kluczowym w Standard SQL) npźródło
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.
źródło
JEŚLI chcesz filtrować za pomocą NOT IN dla podzapytania zawierającego wartości NULL, po prostu zaznacz opcję non null
źródło
to jest dla chłopca:
działa to niezależnie od ustawień ansi
źródło
SQL używa logiki trójwartościowej dla wartości prawdy. The
IN
Zapytanie produkuje oczekiwany wynik:Ale dodanie
NOT
nie odwraca wyników:Wynika to z faktu, że powyższe zapytanie jest równoważne z następującą:
Oto jak oceniana jest klauzula where:
Zauważ, że:
NULL
wydajnościUNKNOWN
OR
Wyrażenie gdzie żaden z operandówTRUE
a co najmniej jeden operandUNKNOWN
wydajnościUNKNOWN
( ref )NOT
ZUNKNOWN
wydajnoś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
NULL
inna, żaden wiersz nie będzie pasował.źródło
może to również być przydatne do poznania logicznej różnicy między złączeniem, istnieje oraz w http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
źródło