2011-07-08 8 views
31

Theory pytanie tutaj:Korzystanie IS NULL lub IS NOT NULL na warunki przyłączenia - teoria zapytania

Dlaczego określając table.field IS NULL lub table.field IS NOT NULL nie pracować na warunkach przyłączenia (w lewo lub prawo przystąpić na przykład), ale tylko w warunku, gdzie?

dla Przykładu roboczego:

-to należy zwrócić wszelkie przemieszczenia wszelkich zwrotów (nie wartości zerowe) odfiltrowane. Zwraca jednak wszystkie przesyłki, jeśli coś spełnia polecenie [r.id jest puste].

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
    AND r.id is null 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 

PRZYKŁAD WYKONANIA:

-To zwraca odpowiednią ilość rzędów, które jest całkowite przemieszczenia, mniej każdy wiąże się z wartościami dla zwrotu (zerowe).

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 
    AND r.id is null 

Dlaczego tak się dzieje? Wszystkie inne warunki filtrowania między dwoma łączonymi tabelami działają dobrze, ale z jakiegoś powodu filtry IS NULL i IS NOT NULL nie działają, chyba że w instrukcji where.

Jaki jest tego powód?

Odpowiedz

69

Przykład z tabel A i B:

A (parent)  B (child)  
============ ============= 
id | name  pid | name 
------------ ------------- 
    1 | Alex   1 | Kate 
    2 | Bill   1 | Lia 
    3 | Cath   3 | Mary 
    4 | Dale  NULL | Pan 
    5 | Evan 

Jeśli chcesz odnaleźć rodziców i ich dzieci, to zrobić INNER JOIN:

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent INNER JOIN child 
    ON parent.id  = child.pid 

Powoduje to, że każdy mecz parent z lewej tabeli i pid z drugiej tabeli będzie wyświetlane jako wiersz w wyniku:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
+----+--------+------+-------+ 

Teraz powyższe nie pokazać rodziców bez dzieci (ponieważ ich identyfikatory nie mają odpowiednika w identyfikatory dziecka, więc co zrobić? Zamiast tego robisz zewnętrzne sprzężenie. Istnieją trzy rodzaje sprzężeń zewnętrznych, lewe, prawe i pełne sprzężenie zewnętrzne. Musimy lewa, jak chcemy „ekstra” wiersze z lewej tabeli (rodzic):

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

Powoduje to, że oprócz poprzednich meczów, wszyscy rodzice, że nie mają meczu (czytaj: nie mają kid) przedstawiono też:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Skąd wszystkich tych NULL pochodzi? Cóż, MySQL (lub jakikolwiek inny RDBMS, którego możesz użyć) nie będzie wiedział, co tam umieścić, ponieważ rodzice nie mają dopasowania (dziecko), więc nie ma pid ani child.name, aby dopasować się do tych rodziców. W związku z tym umieszcza tę wyjątkową wartość o nazwie NULL.

Chodzi mi o to, że te NULLs zostały utworzone (w zestawie wyników) podczas LEFT OUTER JOIN.


Tak więc, jeśli chcemy pokazać tylko rodzicom, że nie masz chłopaka, możemy dodać WHERE child.pid IS NULL do powyższego LEFT JOIN. Klauzula WHERE jest oceniana (sprawdzana) po wykonaniu JOIN.Tak, to wynika z powyższego wyniku, że tylko trzy ostatnie wiersze gdzie pid jest NULL zostaną pokazane:

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

WHERE child.pid IS NULL 

Wynik:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Teraz, co się dzieje, gdy poruszamy się, że IS NULL sprawdź od klauzuli WHERE do dołączenia ON?

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 
    AND child.pid IS NULL 

W tym przypadku baza danych próbuje znaleźć wiersze z dwóch tabel, które spełniają te warunki. To znaczy, wiersze, w których są , i . Ale może znaleźć żadnego takiego dopasowania ponieważ żadna child.pid może być równa czemuś (1, 2, 3, 4 lub 5) i mieć wartość NULL w tym samym czasie!

więc warunek:

ON parent.id = child.pid 
AND child.pid IS NULL 

odpowiada:

ON 1 = 0 

która zawsze False.

Dlaczego więc zwraca WSZYSTKIE wiersze z lewej tabeli? Bo to LEFT DOŁĄCZ! i lewej dołącza powrócić wiersze, które pasują (brak w tym przypadku) a także rzędy od lewej tabeli, które nie pasują czek (wszystko w tym przypadku):

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | NULL | NULL | 
| 2 | Bill | NULL | NULL | 
| 3 | Cath | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Mam nadzieję, że powyższe wyjaśnienie jest jasne.



Sidenote (nie związane bezpośrednio z pytaniem): Dlaczego na ziemi nie Pan ma pokazać się w żaden z naszych JOIN? Ponieważ jego pid jest NULL i NULL w (nie powszechnej) logice SQL nie jest równy czemukolwiek, więc nie może się równać z żadnym z nadrzędnych identyfikatorów (które są 1,2,3,4 i 5). Nawet jeśli był tam NULL, nadal nie pasowałoby, ponieważ NULL nie jest równe, nawet samo NULL (to bardzo dziwna logika!). Dlatego używamy czeku specjalnego IS NULL, a nie testu = NULL.

Czy pojawi się Pan, jeśli wykonamy RIGHT JOIN? Tak, to będzie! Ponieważ RIGHT JOIN pokaże wszystkie wyniki spełniające (pierwszy INNER JOIN my) oraz wszystkie wiersze z tabeli rację, że nie pasują do siebie (co w naszym przypadku jest tylko jedna, (NULL, 'Pan') rząd.

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent RIGHT JOIN child 
    ON parent.id  = child.pid 

Rezultat :

+------+--------+------+-------+ 
| id | parent | pid | child | 
+---------------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 

Niestety, MySQL nie posiada FULL JOIN.Można spróbować w innym RDBMSs, i pokaże:

+------+--------+------+-------+ 
| id | parent | pid | child | 
+------+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 
+0

Możesz sfałszować "FULL JOIN" w MySQL, biorąc związek pomiędzy 'LEFT JOIN' i' RIGHT JOIN', gdzie id to 'NULL'. Ma to ograniczenia - na przykład nie można aktualizować ani usuwać - i prawdopodobnie jest to większy problem niż jest to warte. – Duncan

6

Część NULL jest obliczana PO faktycznym sprzężeniu, dlatego musi znajdować się w klauzuli where.

+0

Więc jeśli dobrze rozumiem, oprogramowanie RDMS ignoruje zerowe obliczenia, chyba że są w klauzuli WHERE ale wykonuje inne dołączyć warunki w momencie tabela jest połączona ? – JoshG

+0

@ Joshu, myślę, że masz to poprawnie. Aby system RDMS mógł ustalić, czy wartość kolumny ma wartość NULL, zostanie najpierw połączone. Gdy już do nich dołączy, przyjrzy się klauzuli WHERE i na podstawie tego filtruje rekordy. Właśnie dlatego guru SQL twierdzi, że mądrze jest myśleć o swoich połączeniach i sprawdzić, czy istnieje jakaś część klauzuli WHERE, którą można przenieść do warunku JOIN, ponieważ w ten sposób połączenie będzie się odbywać na mniejszej liczbie rekordów i będzie szybsze. –

2

Klauzula WHERE jest oceniana po przetworzeniu warunków JOIN.

+0

Dzięki za odpowiedź. Dlaczego warunek łączenia "IS NULL" jest ignorowany, podczas gdy inne są przetwarzane? – JoshG

+2

@JoshG: Ponieważ stan NULL/NOT NULL nie istnieje do * po * JOIN jest oceniany. –

1

Twój plan wykonania powinien to wyjaśnić; JOIN ma pierwszeństwo, po czym wyniki są filtrowane.

+0

Dzięki za odpowiedź. A więc warunki filtrowania sprzężenia i wszystkich łączeń są obliczane, ale nie wartości Null w momencie łączenia? Każdy powód, dla którego zignorowałby filtr NULL, ale nie inne filtry? – JoshG

2

Wykonujesz LEFT OUTTER JOIN, co oznacza, że ​​chcesz, aby każda krotka z tabeli po LEWEJ instrukcji, niezależnie od tego, miała zgodny rekord w tabeli PRAWO. W tym przypadku wyniki są przycinane z tabeli PRAWO, ale kończysz z tymi samymi rezultatami, jak gdyby w ogóle nie zawierałeś AND w klauzuli ON.

Wykonanie klauzuli AND w WHERE powoduje, że przycinanie następuje po wykonaniu LEFT JOIN.

+0

Dzięki za odpowiedź. To ma sens, z tą różnicą, że ta logika zdaje się wpływać tylko na filtry IS NULL i IS NOT NULL, co jest dziwne. Mogę umieścić dowolny inny filtr na stanie złączenia i będzie działał dobrze. Jakiś pomysł, dlaczego tak jest? – JoshG

+0

Nieważność jest sprawdzana podczas łączenia; dlatego wszystko, co robisz, to sprawdzanie wierszy, które aktualnie istnieją w prawej tabeli, które mają id, który jest pusty. Nie wartość post join, która kończy się lewą tabelą + prawą tabelą tuple (w przypadku, gdy nie ma dopasowania w prawej tabeli, używana jest NULL tuple). Tak więc, wykonując polecenie r.id nie ma wartości NULL w klauzuli ON, TYLKO szukasz nieważności w istniejącej tabeli r. – Suroot

3

W rzeczywistości filtr NULL nie jest ignorowany. Tak właśnie działa połączenie dwóch tabel.

Postaram się zejść krokami wykonywanymi przez serwer bazy danych, aby go zrozumieć. Na przykład po wykonaniu kwerendy, które powiedziałeś, ignoruje warunek NULL. SELECT * OD przesyłek s LEFT OUTER JOIN powróci r
ON s.id = r.id I r.id jest null GDZIE s.day> = CURDATE() - przedział 10 DZIEŃ

Pierwszą rzeczą, jaka się wydarzyła, są wszystkie wiersze ze tabeli WYSYŁKI zostaną wybrane:

w następnym kroku serwer bazy danych rozpocznie wybieranie jednego po drugim z drugiej tabeli (ZWROTY).

na trzecim etapie rekord z tabeli RETURNS zostanie zakwalifikowany zgodnie z warunkami łączenia, które podałeś w zapytaniu, które w tym przypadku jest (s.id = r.id i r.id są równe NULL)

należy zauważyć, że ta kwalifikacja zastosowana na trzecim etapie decyduje o tym, czy serwer powinien zaakceptować lub odrzucić bieżący rekord tabeli ZWROTY, który ma zostać dołączony do wybranego wiersza tabeli PRZESYŁKI. Nie może w żaden sposób wpłynąć na wybór rekordu z tabeli SHIPMENT.

A gdy serwer zostanie zakończony łączeniem dwóch tabel, które zawierają wszystkie wiersze tabeli PRZESYŁKI i wybrane wiersze tabeli ZWRACA, stosuje klauzulę dokąd w wyniku pośrednim. , więc jeśli wstawisz (r.id jest NULL) warunek, w którym klauzula niż wszystkie rekordy od wyniku pośredniego z r.id = null zostanie odfiltrowany.

Powiązane problemy