BAZY DANYCH · INF.03
JOIN — łączenie tabel
Dane o uczniach, książkach i wypożyczeniach leżą w osobnych tabelach. JOIN składa je z powrotem w jeden czytelny obraz: kto co wypożyczył, z której klasy pochodzi i kiedy ma oddać książkę.
INNER JOIN — łączenie tabel po kluczach
Tabela ksiazki nie przechowuje nazwy kategorii ani nazwy wydawnictwa. Zamiast tego ma kolumny kategoria_id i wydawnictwo_id, czyli numery wskazujące wiersze w innych tabelach. Żeby w raporcie pokazać czytelnikowi „Fantastyka” zamiast liczby 2, trzeba połączyć dwie tabele. Do tego służy JOIN.
INNER JOIN zwraca tylko te pary wierszy, które spełniają warunek połączenia. Warunek zapisujemy po słowie ON i najczęściej porównujemy klucz obcy z kluczem podstawowym. Jeśli książka ma kategoria_id = 2, a w tabeli kategorie istnieje wiersz o id = 2, para zostanie dopasowana. Jeśli numer nie istnieje, wiersz nie pojawi się w wyniku.
SELECT k.tytul, kat.nazwa AS kategoria, k.cena
FROM ksiazki k
JOIN kategorie kat ON k.kategoria_id = kat.id
WHERE kat.kod = 'FAN'
ORDER BY k.cena DESC;
Litery k i kat to aliasy tabel. Dzięki nim zapis jest krótszy, a kolumny o tych samych nazwach można jednoznacznie wskazać. W obu tabelach występuje na przykład kolumna id; zapis k.id oznacza identyfikator książki, a kat.id identyfikator kategorii. Bez aliasów baza nie wiedziałaby, o którą kolumnę chodzi.
JOIN można łączyć w łańcuch. Raport wypożyczeń wymaga uczniów, książek i pracowników: każde wypożyczenie wskazuje ucznia, książkę i osobę obsługującą. Każde kolejne JOIN dokłada kolumny do bieżącego wiersza, więc po trzech połączeniach mamy w jednym wierszu dane z czterech tabel. Kolejność łączenia nie zmienia wyniku, ale wpływa na czytelność zapytania.
SELECT u.nazwisko, ks.tytul, w.termin_zwrotu
FROM wypozyczenia w
JOIN uczniowie u ON w.uczen_id = u.id
JOIN ksiazki ks ON w.ksiazka_id = ks.id
WHERE w.status = 'przeterminowane'
ORDER BY w.termin_zwrotu, u.nazwisko;
Relacja wiele-do-wielu wymaga tabeli pośredniej. Książka może mieć kilku autorów, a autor może napisać wiele książek. Tabela ksiazki_autorzy przechowuje pary ksiazka_id i autor_id, a kolumna rola mówi, czy dana osoba jest autorem, czy współautorem. Aby pokazać autorów książki, trzeba połączyć trzy tabele: ksiazki, ksiazki_autorzy i autorzy.
SELECT ks.tytul, a.imie, a.nazwisko, ka.rola
FROM ksiazki ks
JOIN ksiazki_autorzy ka ON ks.id = ka.ksiazka_id
JOIN autorzy a ON ka.autor_id = a.id
WHERE ks.id = 4
ORDER BY ka.rola, a.nazwisko;
| Tabela | Łączymy z | Warunek | Po co |
|---|---|---|---|
ksiazki |
kategorie |
ksiazki.kategoria_id = kategorie.id |
nazwa kategorii zamiast numeru |
ksiazki |
wydawnictwa |
ksiazki.wydawnictwo_id = wydawnictwa.id |
nazwa i miasto wydawcy |
ksiazki |
ksiazki_autorzy |
ksiazki.id = ksiazki_autorzy.ksiazka_id |
autorzy i role |
wypozyczenia |
uczniowie, ksiazki |
wypozyczenia.uczen_id = uczniowie.id, wypozyczenia.ksiazka_id = ksiazki.id |
kto co wypożyczył |
FROM ksiazki JOIN kategorie bez ON połączy każdą książkę z każdą kategorią. Przy 24 książkach i 8 kategoriach da 192 wiersze, z których większość nie ma sensu. Warunek ON jest obowiązkowy, jeśli chcemy pary po kluczu.
Przykłady z życia
1. Katalog biblioteczny. Czytelnik widzi tytuł i nazwę kategorii, choć w tabeli książek zapisany jest tylko numer kategorii. JOIN tłumaczy numer na nazwę w chwili wyświetlania, więc zmiana nazwy kategorii wymaga poprawienia jednego wiersza, a nie wszystkich książek.
2. Plan lekcji. Lekcja łączy nauczyciela, przedmiot, klasę i salę. Żadna z tych informacji nie powinna być wpisywana wielokrotnie w jednym wierszu planu. JOIN zbiera je z tabel słownikowych po identyfikatorach, podobnie jak biblioteka łączy książkę z autorem.
3. Zamówienie w sklepie. Pozycja zamówienia wskazuje produkt i zamówienie. Raport „co zamówił klient” wymaga połączenia tabeli pozycji z produktami i zamówieniami. Bez JOIN widzielibyśmy tylko numery, a nie nazwy produktów.
Geografia: mapa cyfrowa łączy warstwę miejscowości z warstwą gmin po identyfikatorze gminy. Nazwa gminy jest przechowywana raz, a miejscowości wskazują ją kluczem — dokładnie jak książki wskazują kategorię. JOIN pozwala nałożyć na siebie obie warstwy bez kopiowania nazw.
W module 04 filtrowałeś i grupowałeś jedną tabelę. Tutaj dołączasz do niej dane z innych tabel, a w module 06 użyjesz JOIN wewnątrz podzapytań i widoków. W module 07 zobaczysz, jak JOIN współpracuje z usuwaniem i aktualizacją danych, a w module 09 cały raport złożysz w zadaniu egzaminacyjnym.
Informatyka: w aplikacji internetowej lista zamówień zwykle nie pobiera nazw produktów z jednej wielkiej tabeli. Serwis łączy tabelę zamówień z tabelą produktów po kluczu, tak samo jak SQL robi to w bazie. Dzięki temu zmiana nazwy produktu jest widoczna od razu we wszystkich zamówieniach.
Biblioteka publikuje krótki katalog dla rodziców. Dla książek z kategorii Literatura piękna i Fantastyka, dostępnych i tańszych niż 60 zł, pokaż tytuł, nazwę kategorii, nazwę wydawnictwa i cenę. Posortuj wynik rosnąco po nazwie kategorii, a w obrębie kategorii malejąco po cenie. Napisz zapytanie z dwoma połączeniami i wyjaśnij, po jakich kluczach łączysz tabele.
Pokaż przykładowe rozwiązanie i kryteria
Przykład: SELECT k.tytul, kat.nazwa AS kategoria, w.nazwa AS wydawnictwo, k.cena FROM ksiazki k JOIN kategorie kat ON k.kategoria_id = kat.id JOIN wydawnictwa w ON k.wydawnictwo_id = w.id WHERE kat.kod IN ('LIT', 'FAN') AND k.cena < 60 AND k.dostepna = 1 ORDER BY kat.nazwa, k.cena DESC, k.tytul; Pierwsze połączenie tłumaczy numer kategorii na nazwę, drugie numer wydawnictwa na nazwę wydawcy.
Kryteria: (1) połączenia — użyto dwóch JOIN-ów po właściwych kluczach; (2) filtr — uwzględniono kategorię, cenę i dostępność; (3) wynik — wybrano cztery kolumny i poprawnie posortowano; (4) uzasadnienie — uczeń wskazuje klucz obcy i klucz podstawowy w każdym połączeniu.
LEFT JOIN i RIGHT JOIN — wiersze bez pary
INNER JOIN pokazuje tylko dopasowane pary. To dobre pytanie „które książki mają kategorię”, ale złe pytanie „które kategorie nie mają żadnej książki”. Jeśli po prawej stronie nie ma pasującego wiersza, INNER JOIN po cichu go pomija. Wtedy wynik wygląda na kompletny, choć brakuje w nim najważniejszej informacji: braku.
LEFT JOIN zachowuje wszystkie wiersze z tabeli po lewej stronie, nawet jeśli nie mają pary po prawej. Dla niepasującego wiersza kolumny prawej tabeli przyjmują wartość NULL. Dzięki temu można zobaczyć zarówno uczniów, którzy wypożyczyli książkę, jak i tych, którzy nie wypożyczyli nic. Wiersz ucznia bez wypożyczeń ma w kolumnach wypożyczenia same wartości NULL.
SELECT u.imie, u.nazwisko, w.id AS wypozyczenie
FROM uczniowie u
LEFT JOIN wypozyczenia w ON u.id = w.uczen_id
ORDER BY u.nazwisko;
Wynik ma 47 wierszy: 45 wypożyczeń oraz dwóch uczniów bez żadnego wypożyczenia. Każdy z tych dwóch uczniów pojawia się raz, z NULL w kolumnie wypozyczenie. To właśnie ten NULL pozwala odróżnić „nie ma wypożyczenia” od „wypożyczenie ma numer zero”, którego w bazie nie używamy.
Filtr w WHERE a filtr w ON. To najczęstsza pułapka outer joinów. Warunek dopisany do ON decyduje, które wiersze prawej tabeli mogą się dopasować; warunek w WHERE sprawdzamy już po połączeniu i może odrzucić wiersze z NULL. Jeśli chcesz zachować wszystkich aktywnych uczniów i tylko oznaczyć tych z aktywnym wypożyczeniem, status umieść w ON.
SELECT u.imie, u.nazwisko, w.id AS aktywne_wypozyczenie
FROM uczniowie u
LEFT JOIN wypozyczenia w
ON u.id = w.uczen_id AND w.status = 'w trakcie'
WHERE u.aktywny = 1
ORDER BY u.nazwisko;
Gdyby warunek w.status = 'w trakcie' przenieść do WHERE, uczniowie bez aktywnego wypożyczenia mieliby NULL w kolumnie statusu, a porównanie z NULL odrzuciłoby ich wiersze. LEFT JOIN zamieniłby się w praktyce w INNER JOIN, choć w zapisie nadal widnieje słowo LEFT.
RIGHT JOIN jest lustrzanym odbiciem LEFT JOIN. Zachowuje wszystkie wiersze z tabeli po prawej stronie. Zamiast pisać FROM wypozyczenia w RIGHT JOIN uczniowie u, można zamienić tabele miejscami i napisać FROM uczniowie u LEFT JOIN wypozyczenia w — wynik będzie ten sam, a zapytanie czytelniejsze. W praktyce częściej używa się LEFT JOIN, bo łatwiej czyta się je od tabeli, którą chcemy zachować w całości.
SELECT u.nazwisko, w.id AS wypozyczenie
FROM wypozyczenia w
RIGHT JOIN uczniowie u ON w.uczen_id = u.id
ORDER BY u.nazwisko;
FROM uczniowie u LEFT JOIN wypozyczenia w — daje ten sam wynik i jest czytelniejszy.
| Rodzaj połączenia | Które wiersze zostają | Przykład w bibliotece | Liczba wierszy |
|---|---|---|---|
INNER JOIN |
tylko pary z obu tabel | wypożyczenia z przypisanym uczniem | 45 |
LEFT JOIN |
wszyscy z lewej plus dopasowania | uczniowie i ich wypożyczenia, także brak | 47 |
RIGHT JOIN |
wszyscy z prawej plus dopasowania | to samo co LEFT JOIN po zamianie tabel | 47 |
COUNT(w.id), policzysz tylko dopasowane wypożyczenia. COUNT(*) policzy także wiersze bez dopasowania, czyli wszystkich uczniów. Wybór funkcji zmienia sens raportu.
Przykłady z życia
1. Lista uczestników wycieczki. Wychowawca ma pełną listę uczniów i osobno zapisane zgody. LEFT JOIN pokazuje wszystkich uczniów, a brak zgody jako NULL. Dzięki temu od razu widać, kto jeszcze nie oddał podpisu, zamiast zgubić te osoby w wyniku INNER JOIN.
2. Klienci bez zamówień. Sklep chce wysłać ofertę osobom, które nigdy nic nie kupiły. LEFT JOIN klientów z zamówieniami i warunek zamowienie.id IS NULL daje właśnie taką listę. To klasyczny wzorzec „kto nie ma powiązanego rekordu”.
3. Awarie bez zgłoszenia. Serwis ma listę maszyn i listę zgłoszeń. LEFT JOIN pokazuje także maszyny, które nie mają żadnego zgłoszenia. Brak zgłoszenia może oznaczać, że wszystko działa, albo że nikt nie zauważył problemu — dlatego raport jest potrzebny.
Geografia: lista stacji meteorologicznych połączona z pomiarami przez LEFT JOIN pokaże także stacje, które nie przesłały danych. INNER JOIN ukryłby je, a właśnie brak pomiaru jest często najważniejszą informacją w monitoringu środowiska.
W poprzedniej sekcji łączyłeś tylko dopasowane wiersze. Tutaj uczysz się widzieć brak dopasowania. W module 04 liczyłeś już agregaty, a w module 06 znajdziesz uczniów bez wypożyczeń także przez NOT IN i NOT EXISTS. W module 07 zobaczysz, co się dzieje z powiązaniami przy usuwaniu danych.
Informatyka: panel administracyjny często pokazuje „użytkownicy bez aktywnej subskrypcji”. To zapytanie z LEFT JOIN i warunkiem IS NULL. Bez outer joinu programista musiałby wykonać dwa osobne zapytania i porównać listy w kodzie, co łatwo prowadzi do błędów.
Zapytania wielotabelowe — raport wypożyczeń
Prawdziwy raport rzadko mieści się w jednej tabeli. Biblioteka chce pokazać nazwisko ucznia, jego klasę, tytuł książki, kategorię i kwotę kary. Te informacje są rozłożone na pięć tabel: uczniowie, klasy, ksiazki, kategorie i wypozyczenia. Zapytanie wielotabelowe łączy je w jeden czytelny wiersz.
Zaczynamy od tabeli zdarzeń, czyli wypozyczenia. Każdy jej wiersz wskazuje ucznia, książkę i pracownika. Dołączamy uczniów po w.uczen_id = u.id, klasy po u.klasa_id = kl.id, książki po w.ksiazka_id = ks.id, a kategorie po ks.kategoria_id = kat.id. Każde JOIN dokłada kolumny do bieżącego wyniku, więc po całym łańcuchu mamy w jednym wierszu dane z pięciu tabel.
SELECT u.nazwisko, kl.nazwa AS klasa, ks.tytul,
kat.nazwa AS kategoria, w.kara
FROM wypozyczenia w
JOIN uczniowie u ON w.uczen_id = u.id
JOIN klasy kl ON u.klasa_id = kl.id
JOIN ksiazki ks ON w.ksiazka_id = ks.id
JOIN kategorie kat ON ks.kategoria_id = kat.id
WHERE w.kara > 0
ORDER BY w.kara DESC, u.nazwisko;
Kolejność łączenia nie jest przypadkowa. Zaczynamy od tabeli, która ma najwięcej wierszy w raporcie, i dołączamy do niej tabele słownikowe. Dzięki temu łatwo zauważyć, którego klucza brakuje. Warto też od razu nadawać aliasy i kwalifikować kolumny: w kilku tabelach występuje id i nazwa, więc zapis bez aliasu byłby niejednoznaczny.
Połączenie tabel a grupowanie to dwa etapy. Najpierw JOIN tworzy pełne wiersze, a dopiero potem GROUP BY zbiera je w grupy. Raport „ile wypożyczeń ma każda klasa” wymaga połączenia klas, uczniów i wypożyczeń, a następnie grupowania po nazwie klasy. COUNT(w.id) policzy wypożyczenia, a SUM(w.kara) sumę kar w danej klasie.
SELECT kl.nazwa AS klasa,
COUNT(w.id) AS wypozyczenia,
SUM(w.kara) AS suma_kar
FROM klasy kl
JOIN uczniowie u ON kl.id = u.klasa_id
JOIN wypozyczenia w ON u.id = w.uczen_id
GROUP BY kl.nazwa
ORDER BY wypozyczenia DESC, kl.nazwa;
Raport przekrojowy grupuje po dwóch kolumnach. Zestawienie klas i statusów wypożyczeń pokazuje, ile w każdej klasie jest zwrotów, książek w trakcie i przeterminowań. Każda para klasa–status tworzy osobną grupę, więc wynik ma więcej wierszy niż liczba klas. To nadal jedno zapytanie, choć tabela wygląda jak kilka raportów zestawionych obok siebie.
SELECT kl.nazwa AS klasa, w.status, COUNT(*) AS liczba
FROM klasy kl
JOIN uczniowie u ON kl.id = u.klasa_id
JOIN wypozyczenia w ON u.id = w.uczen_id
GROUP BY kl.nazwa, w.status
ORDER BY kl.nazwa, w.status;
| Kolumna raportu | Skąd pochodzi | Przez jakie połączenie |
|---|---|---|
| nazwisko ucznia | uczniowie.nazwisko |
wypozyczenia.uczen_id = uczniowie.id |
| nazwa klasy | klasy.nazwa |
uczniowie.klasa_id = klasy.id |
| tytuł książki | ksiazki.tytul |
wypozyczenia.ksiazka_id = ksiazki.id |
| nazwa kategorii | kategorie.nazwa |
ksiazki.kategoria_id = kategorie.id |
ksiazki_autorzy, książka z dwoma autorami pojawi się dwa razy. Wtedy COUNT(*) policzy autorów, a nie książki. W MySQL do liczenia unikalnych książek możesz użyć COUNT(DISTINCT ks.id); w edytorze kursu policz książki w zapytaniu bez dołączania autorów. Sumy kar również wymagają starannie dobranych kolumn. Zawsze sprawdź, czy dodatkowe połączenie nie zmienia ziarna raportu.
Przykłady z życia
1. Zebranie z rodzicami. Wychowawca chce jednym raportem pokazać uczniów, ich klasy i liczbę wypożyczonych książek. Bez łączenia tabel musiałby otwierać kilka arkuszy i przepisywać dane ręcznie. Jedno zapytanie z JOIN i GROUP BY daje gotową tabelę do wydruku.
2. Raport sprzedaży. Firma analizuje sprzedaż według kategorii produktów i regionów. Tabele zamówień, pozycji, produktów i regionów łączy się po kluczach, a następnie grupuje po dwóch wymiarach. To ten sam schemat co raport klas i statusów wypożyczeń.
3. Turniej szkolny. Tabela meczów wskazuje drużyny i sędziów. Raport „punkty według drużyny i rundy” wymaga połączenia meczów z drużynami oraz grupowania po dwóch kolumnach. Bez JOIN widzielibyśmy tylko numery drużyn, a nie ich nazwy.
Ekonomia: analityk biznesowy mówi o „ziarnie raportu” — o tym, co opisuje jeden wiersz. W raporcie wypożyczeń ziarnem jest jedno wypożyczenie, dopóki nie dołączymy autorów. Dodanie tabeli autorów zmienia ziarno na parę książka–autor, więc sumy trzeba liczyć ostrożnie. To pojęcie wraca w każdym raporcie przekrojowym.
W module 04 nauczyłeś się grupować jedną tabelę, a w poprzednich sekcjach łączyć tabele. Tutaj oba narzędzia pracują razem. W module 06 zobaczysz podzapytania i widoki, które potrafią zapisać taki raport na stałe. W module 08 raporty połączysz z funkcjami i bezpieczeństwem, a w module 09 wykonasz pełne zadanie egzaminacyjne.
Geografia: raport „liczba szkół według gminy i typu” łączy tabelę szkół z gminami i słownikiem typów. Grupowanie po dwóch kolumnach daje tabelę krzyżową, z której widać, gdzie brakuje danego typu placówki. To dokładnie ten sam mechanizm co klasy i statusy wypożyczeń.
Biblioteka chce raportu, który pokaże dla każdej kategorii liczbę wypożyczeń i sumę kar. Uwzględnij tylko kategorie z co najmniej pięcioma wypożyczeniami i posortuj wynik malejąco po sumie kar, a przy równych sumach — malejąco po liczbie wypożyczeń. Napisz zapytanie z połączeniem trzech tabel i wyjaśnij, dlaczego nie wystarczy samo GROUP BY na tabeli wypozyczenia.
Pokaż przykładowe rozwiązanie i kryteria
Przykład: SELECT kat.nazwa AS kategoria, COUNT(w.id) AS wypozyczenia, SUM(w.kara) AS suma_kar FROM kategorie kat JOIN ksiazki ks ON kat.id = ks.kategoria_id JOIN wypozyczenia w ON ks.id = w.ksiazka_id GROUP BY kat.nazwa HAVING COUNT(w.id) >= 5 ORDER BY suma_kar DESC, wypozyczenia DESC; Tabela wypożyczeń nie zna nazwy kategorii, więc trzeba dołączyć książki i kategorie. Dopiero po połączeniu można grupować po nazwie kategorii.
Kryteria: (1) połączenia — użyto tabel kategorie, ksiazki i wypozyczenia po właściwych kluczach; (2) agregaty — policzono liczbę wypożyczeń i sumę kar; (3) filtr — HAVING COUNT(w.id) >= 5 działa na grupach; (4) wynik — zastosowano dwa poziomy sortowania i czytelne aliasy.
Ćwiczenia SQL
Zadania do samodzielnego wykonania w edytorze SQL.
- 1Katalog dla rodzicówPołącz książki z kategoriami i wydawnictwami, aby przygotować katalog literatury pięknej i fantastyki.Zadanie i podpowiedzi →
- 2Aktywne wypożyczenia do przypomnieniaPołącz wypożyczenia z uczniami, klasami i książkami, aby przygotować listę książek w trakcie.Zadanie i podpowiedzi →
- 3Kto jeszcze nic nie wypożyczyłUżyj LEFT JOIN, aby znaleźć aktywnych uczniów bez żadnego wypożyczenia.Zadanie i podpowiedzi →
- 4Ranking autorówPołącz autorów z książkami przez tabelę pośrednią i pokaż autorów z co najmniej dwiema książkami.Zadanie i podpowiedzi →