SQL „wybierz, gdzie nie ma w podzapytaniu” nie zwraca żadnych wyników

134

Zastrzeżenie: rozwiązałem problem (chyba), ale chciałem dodać ten problem do przepełnienia stosu, ponieważ nie mogłem (łatwo) go nigdzie znaleźć. Poza tym ktoś mógłby mieć lepszą odpowiedź niż ja.

Mam bazę danych, w której do jednej tabeli „Common” odwołuje się kilka innych tabel. Chciałem zobaczyć, które rekordy w tabeli wspólnej zostały osierocone (tj. Nie miały odniesień z żadnej innej tabeli).

Uruchomiłem to zapytanie:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

Wiem, że są osierocone rekordy, ale żadne rekordy nie zostały zwrócone. Dlaczego nie?

(To jest SQL Server, jeśli ma to znaczenie).

Jeremy Stein
źródło
Ten stackoverflow.com/a/129152/1667619 odpowiada całkiem dobrze na pytanie DLACZEGO.
Ruchan

Odpowiedzi:

243

Aktualizacja:

Te artykuły na moim blogu bardziej szczegółowo opisują różnice między metodami:


Takie zapytanie można wykonać na trzy sposoby:

  • LEFT JOIN / IS NULL:

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
    
  • NOT EXISTS:

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
    
  • NOT IN:

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )
    

Gdy table1.common_idnie dopuszcza wartości null, wszystkie te zapytania są semantycznie takie same.

Kiedy jest dopuszczalna wartość null, NOT INjest różna, ponieważ IN(i dlatego NOT IN) zwraca, NULLgdy wartość nie pasuje do niczego na liście zawierającej NULL.

Może to być mylące, ale może stać się bardziej oczywiste, jeśli przypomnimy sobie alternatywną składnię tego:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Wynik tego warunku jest logicznym iloczynem wszystkich porównań na liście. Oczywiście pojedyncza NULLwartość daje NULLwynik, który renderuje również cały wynik NULL.

Nigdy nie możemy z całą pewnością stwierdzić, że common_idnie jest to coś z tej listy, ponieważ przynajmniej jedna z wartości to NULL.

Załóżmy, że mamy te dane:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLi NOT EXISTSzwróci 3, NOT INnie zwróci nic (ponieważ zawsze będzie zwracać wartość albo FALSElub NULL).

W MySQLprzypadku, gdy kolumna nie dopuszcza wartości null LEFT JOIN / IS NULLi NOT INsą nieco (kilka procent) wydajniejsze niż NOT EXISTS. Jeśli kolumna dopuszcza wartość null, NOT EXISTSjest najbardziej wydajna (znowu niewiele).

W programie Oraclewszystkie trzy zapytania dają takie same plany (an ANTI JOIN).

W SQL Server, NOT IN/ NOT EXISTSsą bardziej wydajne, ponieważ LEFT JOIN / IS NULLnie mogą być zoptymalizowane do an ANTI JOINprzez jego optymalizator.

W PostgreSQL, LEFT JOIN / IS NULLi NOT EXISTSsą bardziej skuteczne niż NOT INsinus są zoptymalizowane Anti Join, podczas NOT INzastosowania hashed subplan(lub nawet gładkie subplanczy podkwerendę jest zbyt duża, aby hash)

Quassnoi
źródło
8
Świetna odpowiedź! Dzięki!
StevenMcD
to jest niesamowite i bardzo pomocne
kavun
1
+1, ponieważ po czterech i pół roku ta odpowiedź pomogła mi rozwiązać problem, który mnie zaskoczył!
Carson63000
@ Carson63000 Snap! Myślałem, że oszaleję, zanim zobaczyłem tę odpowiedź
Bobby
1
@IstiaqueAhmed: zwraca NOT EXISTSwartość TRUE, jeśli zapytanie wewnątrz niego zwraca jakiekolwiek wiersze. SELECT NULLrównie dobrze mógłby być SELECT *lub SELECT 1cokolwiek innego, NOT EXISTSpredykat nie sprawdza wartości wierszy, tylko je zlicza.
Quassnoi
39

Jeśli chcesz, aby świat był miejscem logicznym o dwóch wartościach, musisz sam zapobiec przypadkowi zerowemu (trzecia wartość).

Nie pisz klauzul IN, które dopuszczają wartości null po stronie listy. Odfiltruj je!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Amy B.
źródło
6
wartości null na liście w klauzulach są częstym powodem braku wyników zapytania.
Amy B
„W porównaniu z wartością null odpowiedź jest nieznana” - z odpowiedzi @Jeremy Stein. To common_id not inznaczy od nadal możemy mieć common_idwartość NULL. Czy zatem problem braku wyników nadal występuje?
Istiaque Ahmed
5

Tabela1 lub Tabela2 ma pewne wartości null dla common_id. Zamiast tego użyj tego zapytania:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
Jeremy Stein
źródło
1
Co się stanie, jeśli w jednej tabeli są dane, a w drugiej nie? Czy chcesz „i” lub „lub„ tam ”?
Philip Kelley,
1
Szukam rekordów, do których nie ma odniesienia w żadnej tabeli, więc chcę ORAZ. Wyjaśnię pytanie.
Jeremy Stein
4
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
patmortech
źródło
4

Tylko z czubka mojej głowy ...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

Przeprowadziłem kilka testów i oto moje wyniki z odpowiedzią @ patmortech i komentarzem @ rexem.

Jeśli tabela 1 lub tabela 2 nie jest indeksowana na podstawie commonID, otrzymasz skanowanie tabeli, ale zapytanie @ patmortech jest nadal dwukrotnie szybsze (dla tabeli głównej o wartości 100 000 wierszy).

Jeśli żadne z nich nie jest indeksowane na commonID, otrzymasz dwa skanowanie tabeli, a różnica jest nieistotna.

Jeśli oba są indeksowane na commonID, zapytanie „nie istnieje” jest wykonywane w 1/3 czasu.

Austin Salonen
źródło
1
Powinien to być AND w klauzuli where. W przeciwnym razie to działa.
Jeremy Stein
1
zmieniony na Twój komentarz. Znak „lub” wybiera sieroty z obu tabel.
Austin Salonen,
1
Tak lepiej. Nawiasem mówiąc, czy jest jakiś powód, dla którego powinienem używać złączeń zewnętrznych zamiast podzapytania?
Jeremy Stein
3
Czytelność jest najważniejsza. Podejrzewam, że zostałby wygenerowany lepszy plan wykonania, ale bez planu zapytania nie mogę potwierdzić.
Austin Salonen,
2
To podejście jest gorsze niż użycie opcji NIE ISTNIEJE - sprzężenie powoduje pobranie większej liczby wierszy niż jest to potrzebne, a następnie porównanie wyników dla kolumn o wartości null. A NOT EXISTS jest bardziej czytelny do uruchomienia.
OMG Kucyki
3
SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL
manji
źródło
1
To podejście jest gorsze niż użycie opcji NIE ISTNIEJE - sprzężenie powoduje pobranie większej liczby wierszy niż jest to potrzebne, a następnie porównanie wyników dla kolumn o wartości null. Działa, ale wydajność nie będzie tak dobra - być może gorsza niż użycie IN z skorelowanymi podzapytaniami.
OMG Kucyki
3

Załóżmy, że te wartości dla common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Chcemy, aby wiersz Common zwrócił, ponieważ nie istnieje w żadnej innej tabeli. Jednak zero rzuca kluczem do małpy.

Przy tych wartościach zapytanie jest równoważne z:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

To jest równoważne z:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

Tu zaczyna się problem. W porównaniu z wartością null odpowiedź jest nieznana . Więc zapytanie ogranicza się do

select *
from Common
where not (false)
and not (false or unkown)

fałszywe lub nieznane jest nieznane:

select *
from Common
where true
and not (unknown)

prawdziwe i nie nieznane jest również nieznane:

select *
from Common
where unknown

Warunek gdzie nie zwraca rekordów, w których wynik jest nieznany, więc nie otrzymujemy żadnych rekordów.

Jednym ze sposobów radzenia sobie z tym jest użycie operatora exist, a nie in. Exists nigdy nie zwraca „unkown”, ponieważ działa na wierszach, a nie na kolumnach. (Wiersz istnieje lub go nie ma; żadna z tych zerowych niejednoznaczności na poziomie wiersza!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)
Jeremy Stein
źródło
2

to zadziałało dla mnie :)

wybierz * z Common

gdzie

common_id not in (wybierz ISNULL (common_id, 'dummy-data') z Tabeli1)

a common_id nie ma w (wybierz ISNULL (common_id, „dummy-data”) z tabeli 2)

łukowaty
źródło
@marlar, zapytania podrzędne zawsze zwracają 1 lub 0, a nie listę wartości. Jak więc NOT INtam wystąpi?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Donga jayesh
źródło
0

Miałem przykład, w którym patrzyłem w górę i ponieważ jedna tabela zawierała wartość jako podwójną, a druga jako ciąg, nie pasowałyby (lub nie pasowałyby bez rzutowania). Ale tylko NOT IN . Ponieważ SELECT ... IN ... zadziałało. Dziwne, ale pomyślałem, że podzielę się nim na wypadek, gdyby ktoś inny napotkał tę prostą poprawkę.

okupy
źródło
0

Postępuj zgodnie z poniższym przykładem, aby zrozumieć powyższy temat:

Możesz również odwiedzić poniższy link, aby dowiedzieć się, że funkcja Anti Join

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

Ale jeśli używamy NOT INw tym przypadku, nie otrzymujemy żadnych danych.

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

nie znaleziono danych

Dzieje się tak, gdy ( select department_id from hr.employees) zwraca wartość null, a całe zapytanie jest oceniane jako fałszywe. Możemy to zobaczyć, jeśli zmienimy SQL nieco jak poniżej i obsłużymy wartości null za pomocą funkcji NVL.

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

Teraz otrzymujemy dane:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

Ponownie otrzymujemy dane, ponieważ obsłużyliśmy wartość null za pomocą funkcji NVL.

Rajesh Sarkar
źródło
Wyniki SQl nie pojawiają się w formie tabelarycznej, proszę o zapoznanie się ze mną.
Rajesh Sarkar