2009-09-10 10 views
100

Zrzeczenie się: Zorientowałem się problem (myślę), ale chciałem dodać ten problem do Stack Overflow, ponieważ nie mogłem (łatwo) znaleźć go w dowolnym miejscu. Poza tym ktoś może mieć lepszą odpowiedź niż ja.SQL "wybierz gdzie nie w podzapytanie" nie zwraca wyników

Mam bazę danych, do której jedna tabela "Wspólne" odwołuje się do kilku innych tabel. Chciałem zobaczyć, które rekordy w tabeli wspólnej zostały osierocone (tj. Nie miały żadnych odniesień z żadnej z pozostałych tabel).

wpadł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 istnieją osierocone rekordy, ale nie zostały zwrócone żadne zapisy. Dlaczego nie?

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

+0

To https://stackoverflow.com/a/129152/1667619 odpowiada na pytanie DLACZEGO całkiem dobrze. – Ruchan

Odpowiedz

186

Aktualizacja:

te artykuły w blogu opisać różnice między metodami w bardziej szczegół:


Istnieją trzy sposoby, aby zrobić takie zapytanie:

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

Kiedy table1.common_id nie jest pustych, wszystkie te pytania są semantycznie samo.

Gdy jest pustych, NOT IN jest inna, ponieważ IN (a zatem NOT IN) zwracają NULL gdy wartość nie pasuje do niczego w wykazie zawierającym NULL.

To może 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 
) 

Efektem tego stanu jest wartością logiczną iloczyn wszystkich porównań w obrębie listy. Oczywiście, pojedyncza wartość NULL daje wynik NULL, który również oddaje cały wynik NULL.

Nigdy nie możemy powiedzieć jednoznacznie, że common_id nie jest równe cokolwiek z tej listy, ponieważ przynajmniej jedna z wartości to NULL.

Załóżmy, że mamy następujące dane:

common 

-- 
1 
3 

table1 

-- 
NULL 
1 
2 

LEFT JOIN/IS NULL i NOT EXISTS powróci 3, NOT IN powróci nic (ponieważ zawsze będzie oceniać albo FALSE lub NULL).

W MySQL w przypadku na niż wartości pustych kolumnie LEFT JOIN/IS NULL i NOT IN są trochę (kilka procent) bardziej skuteczny niż NOT EXISTS. Jeśli kolumna jest zerowa, najbardziej efektywna jest wersja NOT EXISTS (znowu niewiele).

W przypadku wszystkich trzech zapytań są wyświetlane te same plany (ANTI JOIN).

W SQL Server, NOT IN/NOT EXISTS są bardziej skuteczne, gdyż LEFT JOIN/IS NULL nie mogą być optymalizowane do ANTI JOIN jego Optimizer.

W PostgreSQL, LEFT JOIN/IS NULL i NOT EXISTS są bardziej skuteczne niż NOT IN sinus są zoptymalizowane do Anti Join, a NOT IN wykorzystuje hashed subplan (lub nawet gładkie subplan jeśli podkwerendę jest zbyt duża, aby hash)

+7

Świetna odpowiedź! Dzięki! – StevenMcD

+0

Świetne wyjaśnienie! – lud0h

+0

to jest niesamowite i bardzo pomocne – kavun

4

Tabela 1 Tabela 2 lub ma pewne wartości null dla common_id. Użyj tej kwerendy Zamiast:

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) 
+1

Co zrobić, jeśli w jednej tabeli znajdują się dane, a inne nie? Czy chcesz "i" lub "lub" tam? –

+1

Szukam rekordów niewymienionych w żadnej tabeli, więc chcę AND. Wyjaśnię to pytanie. –

3

Tuż przy mojej głowie ...

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 

wpadłem kilka testów i tutaj były moje wyniki w.r.t. Odpowiedzi @ patmortech i komentarze @ rexem.

Jeśli tabela 1 lub tabela 2 nie jest indeksowana na identyfikatorze commonID, otrzymasz skanowanie tabeli, ale zapytanie @ patmortech jest nadal dwa razy szybsze (w przypadku tabeli głównej wiersza 100K).

Jeśli żaden z nich nie jest indeksowany na CommonID, otrzymasz dwa skany tabel, a różnica jest znikoma.

Jeśli oba są indeksowane na identyfikatorze commonID, zapytanie "nie istnieje" jest wykonywane za 1/3 czasu.

+1

Powinno to być klauzula AND w miejscu. W przeciwnym razie to działa. –

+1

zmieniono za komentarz. "Lub" wybiera sieroty w obu tabelach. –

+1

To lepiej. Przy okazji, czy jest jakiś powód, dla którego powinienem używać zewnętrznych sprzężeń, a nie podzapytania? –

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 
+1

To podejście jest gorsze niż użycie NOT EXISTS - połączenie powoduje pobranie większej liczby wierszy, niż jest to konieczne, 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ż przy użyciu IN ze skorelowanymi podzapytaniami. –

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) 
+0

+1 - zobacz moją odpowiedź na pytanie dlaczego. –

30

Jeśli chcesz, aby świat być dwu-wartościową logiczną miejsce, trzeba zapobiec null (trzecia wartość) Sprawa siebie.

Nie zapisuj 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 
) 
+0

Źle zrozumiałeś pytanie - przeczytaj ponownie. –

+5

Wartości null w liście "in-klauzula" są częstym powodem braku wyników zapytania. –

+3

Właściwie, myślę, że David B rozumiał to pytanie lepiej niż ktokolwiek inny. Chciałem wiedzieć, DLACZEGO żadne wyniki nie zostały zwrócone. Rewizja. –

3

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

Common - 1 
Table1 - 2 
Table2 - 3, null 

Chcemy wiersz w Common wrócić, ponieważ nie istnieje w żadnym z innych tabel. Jednak wartość zerowa rzuca kluczem małpy.

Z tych wartości, kwerenda jest równoważne:

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

które jest równoważne:

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

to gdzie zaczyna się problem. Porównując z wartością zerową, the answer is unknown.Więc zapytanie redukuje do

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

fałszywe lub nieznane jest nieznana:

select * 
from Common 
where true 
and not (unknown) 

prawdziwe i nie unkown jest również unkown:

select * 
from Common 
where unknown 

WHERE warunek nie zwraca rekordy, w których wynik jest unkown, więc nie otrzymujemy żadnych nagrań.

Jednym ze sposobów radzenia sobie z tym problemem jest użycie operatora exist zamiast istniejącego. Exists nigdy nie zwraca unkown, ponieważ działa on w wierszach, a nie w kolumnach. (A wiersz albo istnieje, albo nie; żadna z tych niejasności zerowej na poziomie rzędu!)

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

ten pracował przez me :)

select * from Wspólnej

gdzie

common_id nie (wybór ISNULL (common_id, 'obojętne-data') z tabeli 1.)

nie common_id w (wybierz ISNULL (common_id 'obojętne-data') z Table2)

+0

Bardzo łatwe obejście. Dzięki! – marlar

+0

@marlar, zapytania podrzędne zawsze zwracają 1 lub 0, a nie listę wartości. Więc jak tam będą "NOT IN"? –

0
select *, 
(select COUNT(ID) from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster 
Powiązane problemy