Dane w bazie są rozdzielone na kilka tabel: osobno uczniowie, osobno oceny, osobno nauczyciele. JOIN skleja je z powrotem w jedną tabelę wynikową. Na tej stronie działa symulator MySQL z gotową bazą szkola. Wpisuj zapytania, klikaj Wykonaj (albo Ctrl+Enter) i porównuj wyniki. Kliknięcie nazwy tabeli w drzewku po lewej pokazuje jej zawartość.
W tabeli oceny nie zapisujemy imienia i nazwiska ucznia przy każdej ocenie, tylko jego numer uczen_id. Dzięki temu dane się nie powtarzają, a zmiana nazwiska wymaga poprawki w jednym miejscu. Ceną jest to, że żeby zobaczyć „Jan Kowalski – Matematyka – 5”, trzeba połączyć tabele po wspólnym polu. Do tego służy JOIN.
| uczen_id | imie | nazwisko | klasa_id |
|---|---|---|---|
| 1 | Jan | Kowalski | 1 |
| 2 | Anna | Nowak | 1 |
| 3 | Piotr | Wiśniewski | 2 |
| 4 | Ola | Zielińska | 2 |
| 5 | Kamil | Lewandowski | 3 |
| ocena_id | uczen_id | nauczyciel_id | przedmiot | ocena |
|---|---|---|---|---|
| 1 | 1 | 1 | Matematyka | 5 |
| 2 | 1 | 2 | Polski | 4 |
| 3 | 2 | 3 | Angielski | 5 |
| 4 | 2 | 1 | Matematyka | 3 |
| 5 | 4 | 2 | Polski | 6 |
| 6 | 4 | 1 | Matematyka | 2 |
| 7 | 1 | 3 | Angielski | 4 |
W bazie są jeszcze tabele nauczyciele (4 osoby, w tym Marek Kamiński, który nie wystawił żadnej oceny) oraz klasy (4A, 4B, 4C). Piotr i Kamil nie mają jeszcze ocen. Te braki przydadzą się przy LEFT i RIGHT JOIN.
Klucz główny w jednej tabeli (uczniowie.uczen_id) i klucz obcy w drugiej (oceny.uczen_id) mają te same wartości. Po nich łączymy tabele: wiersz oceny z uczen_id = 1 należy do ucznia nr 1.
JOIN to rozszerzenie zwykłego SELECT, więc najpierw przypomnij sobie jego budowę:
SELECT kolumny -- co pokazać (* = wszystko) FROM tabela -- skąd WHERE warunek -- które wiersze (opcjonalnie) ORDER BY kolumna; -- jak posortować (ASC rosnąco, DESC malejąco)
INNER JOIN dołącza drugą tabelę i paruje wiersze, dla których warunek ON jest spełniony. Wiersze bez pary nie pojawiają się w wyniku.
SELECT uczniowie.imie, uczniowie.nazwisko, oceny.przedmiot, oceny.ocena FROM uczniowie INNER JOIN oceny ON uczniowie.uczen_id = oceny.uczen_id;
SELECT … FROM tabela1 JOIN tabela2 ON tabela1.klucz = tabela2.klucz;
FROM – JOIN – ON. W ON zawsze stoi para: klucz główny = klucz obcy. Samo JOIN oznacza to samo co INNER JOIN.
Przykład pomocniczy: szafki szkolne. Niektóre szafki są wolne (uczen_id = NULL), a Anna nie ma szafki. Wybierz rodzaj JOIN i przechodź krok po kroku.
Teraz prawdziwa baza. Zwróć uwagę, że w wyniku nie ma Piotra ani Kamila, bo nie mają ocen:
Ile wierszy zwróci powyższy INNER JOIN?
Każda ocena ma dokładnie jednego ucznia, więc powstaje 7 par. Jan pojawia się 3 razy (ma 3 oceny), a Piotr i Kamil wcale, bo nie mają pary.
Kolumna uczen_id jest w obu tabelach. Gdy napiszesz ją bez nazwy tabeli, MySQL nie wie, o którą chodzi, i zgłasza błąd #1052 ambiguous (niejednoznaczna). Uruchom:
Żeby nie pisać za każdym razem długich nazw tabel, nadajemy im aliasy, czyli krótkie przezwiska, np. u i o. Kolumnom w wyniku też możesz nadać czytelne nazwy przez AS.
SELECT u.imie, o.ocena AS "Ocena" FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id;
Alias tabeli nadajesz w FROM/JOIN, a używasz go wszędzie, także w SELECT, który stoi wyżej. Po nadaniu aliasu pełna nazwa tabeli przestaje działać.
Po połączeniu tabel możesz filtrować i sortować wynik tak samo jak zwykle. Ważna jest tylko kolejność części zapytania:
SELECT … FROM … JOIN … ON … WHERE … GROUP BY … ORDER BY … LIMIT …
JOIN zawsze zaraz po FROM, przed WHERE. Najpierw sklejasz tabele, potem wybierasz wiersze, na końcu sortujesz.
LEFT JOIN zwraca wszystkie wiersze z lewej tabeli (tej z FROM), nawet jeśli nie mają pary. Brakujące dane z prawej tabeli zostają wypełnione wartością NULL.
Klasyczne zastosowanie, które często pojawia się w arkuszach: znajdź wiersze bez pary. Po LEFT JOIN bierzesz tylko te wiersze, w których kolumna z prawej tabeli jest NULL:
INNER = tylko pary. LEFT = cała lewa tabela, a puste miejsca to NULL. „Kto nie ma…?” to prawie zawsze LEFT JOIN + WHERE prawa.klucz IS NULL.
Ile wierszy zwróci uczniowie LEFT JOIN oceny?
7 par z ocenami plus po jednym wierszu z NULL dla Piotra i Kamila, którzy nie mają ocen. Razem 9.
RIGHT JOIN działa odwrotnie niż LEFT: zachowuje wszystkie wiersze z prawej tabeli (tej z JOIN). Każdy RIGHT JOIN można zapisać jako LEFT JOIN, zamieniając tabele miejscami. W praktyce i w odpowiedziach do arkuszy częściej spotkasz LEFT.
FULL JOIN (wszystko z obu stron) istnieje w standardzie SQL, ale MySQL i MariaDB go nie obsługują, więc w phpMyAdmin z XAMPP dostaniesz błąd. Ten sam wynik uzyskasz, sklejając LEFT JOIN i RIGHT JOIN słowem UNION:
| Rodzaj | Co zwraca | Brakujące dane |
|---|---|---|
| INNER JOIN | tylko pary pasujące w obu tabelach | – |
| LEFT JOIN | wszystko z lewej (FROM) + pasujące z prawej | NULL po prawej |
| RIGHT JOIN | wszystko z prawej (JOIN) + pasujące z lewej | NULL po lewej |
| FULL JOIN | wszystko z obu stron; w MySQL przez LEFT … UNION … RIGHT | NULL po obu stronach |
| CROSS JOIN | każdy wiersz z każdym (iloczyn kartezjański) | – |
Każdą kolejną tabelę dołączasz kolejnym JOIN … ON. Tabela oceny jest tu „łącznikiem”, bo ma klucze obce do obu pozostałych.
FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id JOIN nauczyciele n ON o.nauczyciel_id = n.nauczyciel_id
Ile tabel, tyle minus jeden warunków ON. 3 tabele to 2 JOIN-y.
W starszych materiałach i w wielu odpowiedziach do arkuszy egzaminacyjnych łączenie wygląda tak: tabele wypisane po przecinku, a warunek łączenia w WHERE. Wynik jest taki sam jak przy INNER JOIN.
Na egzaminie często trzeba coś policzyć dla każdej osoby albo grupy, np. liczbę ocen każdego ucznia czy średnią w każdej klasie. Łączysz tabele, a potem grupujesz przez GROUP BY:
COUNT(kolumna) pomija NULL, a COUNT(*) liczy wiersze. Przy LEFT JOIN licz kolumnę z prawej tabeli, wtedy osoby bez danych dostaną 0, a nie 1.
Warunek na wynik funkcji, np. „średnia powyżej 4”, piszesz w HAVING, a nie w WHERE.
| Kod | Co zobaczysz | Przyczyna i poprawka |
|---|---|---|
| #1052 | Column '…' in field list is ambiguous | Kolumna jest w dwóch tabelach. Dopisz tabelę lub alias: u.uczen_id. |
| #1054 | Unknown column 'uczniowie.imie' | Tabela ma alias, więc trzeba pisać u.imie. Inna możliwość: literówka albo tekst bez apostrofów. |
| #1066 | Not unique table/alias | Dwa razy ten sam alias albo ta sama tabela bez aliasów. |
| #1064 | … near 'JOIN …' | JOIN po WHERE albo FULL JOIN, którego MySQL nie ma. |
| #1111 | Invalid use of group function | COUNT/AVG w WHERE. Przenieś warunek do HAVING. |
| brak ON | dziwnie dużo wierszy | Iloczyn kartezjański: każdy z każdym. Dopisz ON a.klucz = b.klucz. |
| = NULL | pusty wynik | Do NULL porównujemy przez IS NULL / IS NOT NULL. |
Napisz zapytanie i kliknij Sprawdź. Sprawdzarka porówna Twój wynik z wynikiem wzorcowego zapytania: liczbę kolumn, wszystkie wiersze i tam, gdzie to wymagane, kolejność. Nazwy kolumn w nagłówku nie mają znaczenia, ale kolejność kolumn musi być taka jak w poleceniu.
Wyświetl imię i nazwisko ucznia oraz przedmiot i ocenę dla wszystkich wystawionych ocen.
SELECT u.imie, u.nazwisko, o.przedmiot, o.ocena FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id;
Wyświetl imię, nazwisko i ocenę uczniów, którzy mają ocenę z przedmiotu Matematyka. Posortuj od najwyższej oceny.
SELECT u.imie, u.nazwisko, o.ocena FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id WHERE o.przedmiot = 'Matematyka' ORDER BY o.ocena DESC;
Wyświetl imię i nazwisko każdego ucznia oraz jego oceny (przedmiot, ocena). Uczniowie bez ocen też mają się pojawić.
SELECT u.imie, u.nazwisko, o.przedmiot, o.ocena FROM uczniowie u LEFT JOIN oceny o ON u.uczen_id = o.uczen_id;
Wyświetl imię i nazwisko uczniów, którzy nie mają żadnej oceny.
SELECT u.imie, u.nazwisko FROM uczniowie u LEFT JOIN oceny o ON u.uczen_id = o.uczen_id WHERE o.ocena_id IS NULL;
Wyświetl: imię i nazwisko ucznia, przedmiot, ocenę oraz nazwisko nauczyciela, który ją wystawił.
SELECT u.imie, u.nazwisko, o.przedmiot, o.ocena, n.nazwisko FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id JOIN nauczyciele n ON o.nauczyciel_id = n.nauczyciel_id;
Wyświetl imię i nazwisko nauczycieli, którzy nie wystawili jeszcze żadnej oceny.
SELECT n.imie, n.nazwisko FROM nauczyciele n LEFT JOIN oceny o ON n.nauczyciel_id = o.nauczyciel_id WHERE o.ocena_id IS NULL;
Wyświetl imię, nazwisko i liczbę ocen każdego ucznia (uczniowie bez ocen mają mieć 0). Posortuj od największej liczby ocen.
SELECT u.imie, u.nazwisko, COUNT(o.ocena_id) AS liczba FROM uczniowie u LEFT JOIN oceny o ON u.uczen_id = o.uczen_id GROUP BY u.uczen_id, u.imie, u.nazwisko ORDER BY liczba DESC;
Wyświetl nazwę klasy i średnią ocen jej uczniów, tylko dla klas, w których są jakieś oceny. Posortuj alfabetycznie po nazwie klasy. Potrzebne będą trzy tabele: klasy, uczniowie, oceny.
SELECT k.nazwa, AVG(o.ocena) AS srednia FROM klasy k JOIN uczniowie u ON k.klasa_id = u.klasa_id JOIN oceny o ON u.uczen_id = o.uczen_id GROUP BY k.klasa_id, k.nazwa ORDER BY k.nazwa;
SELECT u.imie, o.ocena FROM uczniowie u JOIN oceny o ON u.uczen_id = o.uczen_id;
Tylko pary. ON: klucz główny = klucz obcy.
FROM uczniowie u LEFT JOIN oceny o ON u.uczen_id = o.uczen_id
Cała lewa tabela, brakujące dane to NULL.
LEFT JOIN oceny o ON … WHERE o.ocena_id IS NULL
„Kto nie ma…?” → LEFT + IS NULL.
FROM a JOIN b ON a.id = b.a_id JOIN c ON b.c_id = c.id
Liczba tabel − 1 = liczba warunków ON.
SELECT … FROM … JOIN … ON … WHERE … GROUP BY … HAVING … ORDER BY … LIMIT …
JOIN zaraz po FROM, przed WHERE.
SELECT u.imie, COUNT(o.ocena_id) FROM uczniowie u LEFT JOIN oceny o ON … GROUP BY u.uczen_id, u.imie
COUNT(kolumna) pomija NULL. Warunek na wynik funkcji → HAVING.
Na następnej lekcji: podzapytania i modyfikacja danych (UPDATE, DELETE z warunkiem).