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).
sql
sql-server
tsql
Jeremy Stein
źródło
źródło
Odpowiedzi:
Aktualizacja:
Te artykuły na moim blogu bardziej szczegółowo opisują różnice między metodami:
NOT IN
vs.NOT EXISTS
vsLEFT JOIN / IS NULL
.:SQL Server
NOT IN
vs.NOT EXISTS
vsLEFT JOIN / IS NULL
.:PostgreSQL
NOT IN
vs.NOT EXISTS
vsLEFT JOIN / IS NULL
.:Oracle
NOT IN
vs.NOT EXISTS
vsLEFT JOIN / IS NULL
.:MySQL
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_id
nie dopuszcza wartości null, wszystkie te zapytania są semantycznie takie same.Kiedy jest dopuszczalna wartość null,
NOT IN
jest różna, ponieważIN
(i dlategoNOT IN
) zwraca,NULL
gdy wartość nie pasuje do niczego na liście zawierającejNULL
.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
NULL
wartość dajeNULL
wynik, który renderuje również cały wynikNULL
.Nigdy nie możemy z całą pewnością stwierdzić, że
common_id
nie jest to coś z tej listy, ponieważ przynajmniej jedna z wartości toNULL
.Załóżmy, że mamy te dane:
common -- 1 3 table1 -- NULL 1 2
LEFT JOIN / IS NULL
iNOT EXISTS
zwróci3
,NOT IN
nie zwróci nic (ponieważ zawsze będzie zwracać wartość alboFALSE
lubNULL
).W
MySQL
przypadku, gdy kolumna nie dopuszcza wartości nullLEFT JOIN / IS NULL
iNOT IN
są nieco (kilka procent) wydajniejsze niżNOT EXISTS
. Jeśli kolumna dopuszcza wartość null,NOT EXISTS
jest najbardziej wydajna (znowu niewiele).W programie
Oracle
wszystkie trzy zapytania dają takie same plany (anANTI JOIN
).W
SQL Server
,NOT IN
/NOT EXISTS
są bardziej wydajne, ponieważLEFT JOIN / IS NULL
nie mogą być zoptymalizowane do anANTI JOIN
przez jego optymalizator.W
PostgreSQL
,LEFT JOIN / IS NULL
iNOT EXISTS
są bardziej skuteczne niżNOT IN
sinus są zoptymalizowaneAnti Join
, podczasNOT IN
zastosowaniahashed subplan
(lub nawet gładkiesubplan
czy podkwerendę jest zbyt duża, aby hash)źródło
NOT EXISTS
wartość TRUE, jeśli zapytanie wewnątrz niego zwraca jakiekolwiek wiersze.SELECT NULL
równie dobrze mógłby byćSELECT *
lubSELECT 1
cokolwiek innego,NOT EXISTS
predykat nie sprawdza wartości wierszy, tylko je zlicza.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 )
źródło
common_id not in
znaczy od nadal możemy miećcommon_id
wartośćNULL
. Czy zatem problem braku wyników nadal występuje?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)
źródło
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)
źródło
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.
źródło
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
źródło
Załóżmy, że te wartości dla common_id:
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)
źródło
to zadziałało dla mnie :)
źródło
NOT IN
tam wystąpi?select *, (select COUNT(ID) from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun from CategoryMaster
źródło
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ę.
źródło
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;
Ale jeśli używamy
NOT IN
w 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 );
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:
Ponownie otrzymujemy dane, ponieważ obsłużyliśmy wartość null za pomocą funkcji NVL.
źródło