Gdy chcesz pokazać wszystkie rekordy z jednej tabeli, nawet wtedy, gdy druga nie ma dla nich dopasowania, zwykły INNER JOIN nie wystarczy. Właśnie wtedy przydaje się RIGHT JOIN, czyli prawe złączenie zewnętrzne. Poniżej pokazuję, jak działa, jak pisać takie zapytania, gdzie pojawia się wartość NULL oraz kiedy lepiej odwrócić kolejność tabel i użyć LEFT JOIN.
Najważniejsze zasady działania prawego złączenia w SQL
- Wszystkie wiersze z prawej tabeli pozostają w wyniku, nawet bez dopasowania.
- Brak powiązanego rekordu po lewej stronie jest oznaczany przez NULL.
- Warunek łączenia zapisuje się najczęściej po słowie ON.
- RIGHT OUTER JOIN i RIGHT JOIN oznaczają tę samą operację.
- W praktyce często czytelniej odwrócić tabele i użyć LEFT JOIN.
Czym jest prawe złączenie i jakie wiersze zwraca
Prawe złączenie zewnętrzne zachowuje każdy wiersz z tabeli zapisanej po prawej stronie JOIN. Dołącza do niego dane z lewej tabeli, ale tylko wtedy, gdy warunek łączenia znajdzie pasujący rekord.
Jeżeli dopasowania nie ma, kolumny pochodzące z lewej tabeli przyjmują wartość NULL. To nie jest błąd ani pusty tekst. NULL oznacza, że dla konkretnego wiersza nie znaleziono odpowiedniej wartości.
Przykładowo możemy połączyć tabelę pracowników z tabelą działów. Jeżeli po prawej stronie umieścimy działy, wynik pokaże również te działy, w których nie pracuje obecnie żadna osoba. Przy połączeniu wewnętrznym takie rekordy zostałyby pominięte.
Określenia „lewa” i „prawa” odnoszą się wyłącznie do kolejności tabel w zapytaniu. Tabela po słowie FROM jest lewa, a tabela występująca po słowie JOIN jest prawa. To właśnie druga z nich ma zostać zachowana w całości.
Jak napisać zapytanie z RIGHT OUTER JOIN
Podstawowa składnia wygląda następująco:
SELECT kolumny
FROM tabela_lewa
RIGHT OUTER JOIN tabela_prawa
ON tabela_lewa.klucz = tabela_prawa.klucz;
Słowo OUTER można pominąć, ponieważ w tym przypadku jest opcjonalne. Obie formy wykonują to samo prawe złączenie. Ja zwykle używam krótszego zapisu, a pełną wersję zostawiam w kodzie, gdy chcę szczególnie mocno podkreślić typ operacji.
Najważniejsza część zapytania znajduje się po ON. To tam wskazujemy, które kolumny opisują relację między tabelami. Najczęściej jest to klucz główny jednej tabeli i klucz obcy drugiej, ale warunek może też obejmować kilka kolumn albo dodatkowe ograniczenia.
SELECT
p.imie,
p.nazwisko,
d.nazwa AS dzial
FROM pracownicy AS p
RIGHT OUTER JOIN dzialy AS d
ON p.dzial_id = d.id;
W tym przykładzie tabela dzialy znajduje się po prawej stronie. Dlatego wynik zawiera wszystkie działy. Jeżeli dział nie ma przypisanego pracownika, pola imie i nazwisko będą miały wartość NULL.
Praktyczny przykład z działami i pracownikami
Załóżmy, że dane wyglądają tak:
| pracownicy | dzialy |
|---|---|
| Anna, dział 1 | Sprzedaż, id 1 |
| Piotr, dział 2 | IT, id 2 |
| brak rekordu | Marketing, id 3 |
Zapytanie z prawym złączeniem zwróci także dział Marketing, mimo że nie ma on przypisanego pracownika. Rezultat może wyglądać tak:
| imie | nazwisko | dzial |
|---|---|---|
| Anna | Kowalska | Sprzedaż |
| Piotr | Nowak | IT |
| NULL | NULL | Marketing |
Ten wzorzec przydaje się przy raportach, w których trzeba pokazać pełną listę jednostek nadrzędnych, nawet jeśli część z nich nie ma jeszcze danych podrzędnych. Spotykam go między innymi przy zestawieniach działów, kategorii produktów, kursów bez zapisanych uczestników oraz lokalizacji bez przypisanych punktów.
Dobrze działa również przy agregowaniu danych. Jeżeli chcemy policzyć produkty w każdej kategorii, także w tych pustych, możemy napisać:
SELECT
k.nazwa,
COUNT(p.id) AS liczba_produktow
FROM produkty AS p
RIGHT OUTER JOIN kategorie AS k
ON p.kategoria_id = k.id
GROUP BY k.id, k.nazwa;
W tym przypadku celowo użyłem COUNT(p.id), a nie COUNT(*). Dla kategorii bez produktu SQL nadal utworzy jeden wiersz z wartościami NULL po lewej stronie, dlatego COUNT(*) zwróciłby 1, choć faktycznie produktów nie ma. Liczenie konkretnej kolumny z lewej tabeli zwróci poprawne zero.
RIGHT JOIN a LEFT JOIN i INNER JOIN
Najłatwiej zrozumieć prawe złączenie, porównując je z innymi podstawowymi rodzajami JOIN. Różnica nie dotyczy tylko składni. Decyduje o tym, które rekordy mają przetrwać brak dopasowania.
| Rodzaj złączenia | Co zachowuje | Typowe zastosowanie |
|---|---|---|
INNER JOIN |
Wyłącznie rekordy pasujące po obu stronach | Lista pracowników mających przypisany dział |
LEFT JOIN |
Wszystkie rekordy z lewej tabeli | Wszyscy klienci, także bez zamówień |
RIGHT OUTER JOIN |
Wszystkie rekordy z prawej tabeli | Wszystkie kategorie, także bez produktów |
FULL OUTER JOIN |
Wszystkie rekordy z obu tabel | Porównanie dwóch kompletnych zbiorów danych |
Prawe złączenie jest logicznym odpowiednikiem lewego złączenia po zamianie kolejności tabel. Te dwa zapytania zwrócą ten sam zestaw powiązań:
SELECT *
FROM pracownicy AS p
RIGHT OUTER JOIN dzialy AS d
ON p.dzial_id = d.id;
SELECT *
FROM dzialy AS d
LEFT JOIN pracownicy AS p
ON p.dzial_id = d.id;
W codziennej pracy częściej wybieram LEFT JOIN, ponieważ wiele zapytań czyta się wtedy od głównego zbioru danych do jego uzupełnienia. Nie oznacza to jednak, że prawe złączenie jest gorsze. Ma sens, gdy istniejące zapytanie ma już ustaloną kolejność tabel albo gdy zachowanie prawej tabeli lepiej opisuje intencję raportu.
Najczęstsze błędy przy prawym złączeniu
Pomylenie prawej tabeli
Najczęstsza pomyłka polega na założeniu, że „prawa” tabela to ta ważniejsza biznesowo. SQL nie zna takiej reguły. Liczy się wyłącznie tabela stojąca po słowie JOIN, dlatego przed uruchomieniem zapytania sprawdzam, czy właśnie ona ma być zachowana w całości.
Filtrowanie NULL w klauzuli WHERE
Dużo problemów powoduje filtr umieszczony w niewłaściwym miejscu. Takie zapytanie:
SELECT d.nazwa, p.imie
FROM pracownicy AS p
RIGHT OUTER JOIN dzialy AS d
ON p.dzial_id = d.id
WHERE p.aktywny = 1;
usunie działy bez pracowników, ponieważ dla nich p.aktywny ma wartość NULL. W praktyce prawe złączenie zacznie zachowywać się podobnie do połączenia wewnętrznego. Jeżeli chcemy zachować wszystkie działy i pobrać tylko aktywnych pracowników, warunek trzeba przenieść do ON:
SELECT d.nazwa, p.imie
FROM pracownicy AS p
RIGHT OUTER JOIN dzialy AS d
ON p.dzial_id = d.id
AND p.aktywny = 1;
To drobna zmiana, ale ma duży wpływ na wynik. Warunki w ON ograniczają dopasowanie, natomiast warunki w WHERE filtrują już gotowy rezultat.
Niepoprawne klucze i duplikaty
Jeżeli łączone kolumny nie są kluczami o oczekiwanej unikalności, jeden rekord może połączyć się z wieloma innymi. W efekcie liczba wierszy wzrośnie, a raport może wyglądać tak, jakby dane zostały powielone. Przed użyciem JOIN sprawdzam, czy relacja jest typu jeden do jednego, jeden do wielu czy może przypadkiem wiele do wielu.
Przeczytaj również: SQL Self Join - Jak łączyć tabelę z samą sobą?
Brak indeksów przy dużych tabelach
Sam wybór prawego albo lewego złączenia nie gwarantuje lepszej wydajności. Przy większych zbiorach znaczenie mają przede wszystkim indeksy na kolumnach używanych w ON, selektywność filtrów oraz plan wykonania zapytania. Warto też unikać SELECT *, gdy raport potrzebuje tylko kilku kolumn.
Kiedy odwrócić tabele i użyć LEFT JOIN
Jeśli możesz swobodnie zaprojektować zapytanie od początku, lewy wariant często będzie bardziej czytelny. Zamiast myśleć „zachowaj prawą tabelę”, zaczynasz od tabeli, która ma być kompletna, i zapisujesz ją po FROM. Dla wielu osób taki układ jest po prostu łatwiejszy do czytania.
Przykład z kategoriami można zapisać tak:
SELECT
k.nazwa,
COUNT(p.id) AS liczba_produktow
FROM kategorie AS k
LEFT JOIN produkty AS p
ON p.kategoria_id = k.id
GROUP BY k.id, k.nazwa;
Rezultat będzie równoważny wersji z prawym złączeniem, ale intencja jest od razu widoczna. Tabela kategorii jest głównym zbiorem, a produkty są tylko danymi uzupełniającymi. Wybór LEFT JOIN zamiast RIGHT JOIN jest najczęściej decyzją o czytelności, a nie o możliwościach SQL.
Prawe złączenie zostawiłbym wtedy, gdy jego zapis naturalnie pasuje do istniejącego zapytania albo gdy modyfikacja kolejności wielu połączonych tabel zwiększyłaby ryzyko błędu. W rozbudowanych raportach każda zmiana układu może wpłynąć na aliasy, kolejność kolumn i dodatkowe warunki, więc nie zawsze opłaca się przepisywać działający kod tylko dla preferowanego stylu.
Co sprawdzić przed uruchomieniem prawego złączenia
Najpierw wskaż tabelę, której wszystkie rekordy muszą znaleźć się w wyniku. Potem upewnij się, że stoi ona po prawej stronie JOIN, a warunek w ON łączy właściwe klucze. Na końcu sprawdź, czy filtry w WHERE nie usuwają przypadkiem wierszy zawierających NULL.
Jeśli wynik ma pokazywać elementy bez dopasowania, przetestuj osobno co najmniej jeden taki przypadek. To najlepszy sposób, aby szybko wykryć pomylone strony złączenia, niepoprawny warunek albo użycie COUNT(*) zamiast liczenia konkretnej kolumny.
Najprostsza zasada brzmi więc tak: zachowaj po prawej stronie to, czego nie wolno zgubić. Jeżeli taki zapis utrudnia czytanie zapytania, odwróć tabele i użyj LEFT JOIN. Obie drogi prowadzą do tego samego typu wyniku, o ile świadomie kontrolujesz kolejność tabel, wartości NULL i miejsce stosowania filtrów.