W poprzednim laboratorium doprowadziliśmy nasze tabele do drugiej postaci normalnej (2NF), eliminując zależności częściowe od klucza głównego. Jednak nasze dane wciąż mogą zawierać ukryte problemy, a mianowicie zależności przechodnie. Trzecia postać normalna (3NF) koncentruje się właśnie na ich eliminacji.
Zależność przechodnia występuje, gdy atrybut niekluczowy zależy od innego atrybutu niekluczowego, a nie bezpośrednio od klucza głównego. Schematycznie wygląda to tak: Klucz główny -> atrybut A -> atrybut B. Zmiana w atrybucie A może wymusić zmianę w wielu wierszach, co prowadzi do anomalii.
Tabela jest w trzeciej postaci normalnej (3NF), jeśli:
Eliminacja redundancji danych autorów poprzez wydzielenie ich do osobnej tabeli. Chociaż w tabeli `ksiazki` nie ma klasycznej zależności przechodniej, powtarzanie danych autora przy każdej jego książce jest problemem, który rozwiązuje się w procesie normalizacji do 3NF.
Analizujemy tabelę `ksiazki` z poprzedniego laboratorium. Obecnie imię i nazwisko autora są przechowywane jako tekst w każdym rekordzie książki. Jeśli jeden autor napisał wiele książek, jego dane są wielokrotnie powtarzane. Co więcej, jeśli zechcemy dodać więcej informacji o autorze (np. datę urodzenia), musielibyśmy dodać nowe kolumny do tabeli `ksiazki`, co prowadziłoby do jeszcze większej redundancji. Aby rozwiązać ten problem, stworzymy nową tabelę `autorzy`.
Spójrzmy na strukturę tabeli `ksiazki`:
+-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | id_ksiazki | int(11) | NO | PRI | NULL | auto_increment | | tytul | varchar(255) | NO | | NULL | | | autor | varchar(255) | NO | | NULL | | | rok_wydania | int(11) | YES | | NULL | | | isbn | varchar(13) | YES | UNI | NULL | | +-------------+--------------+------+-----+---------+----------------+
Problem (anomalie) widoczne w tej strukturze:
-- Krok 1: Tworzenie tabeli autorzy
CREATE TABLE autorzy (
id_autora INT AUTO_INCREMENT PRIMARY KEY,
imie VARCHAR(100),
nazwisko VARCHAR(150) NOT NULL
);
-- Krok 2: Wypełnienie tabeli autorzy unikalnymi wartościami
INSERT INTO autorzy (imie, nazwisko)
SELECT DISTINCT
SUBSTRING_INDEX(autor, ' ', 1),
SUBSTRING_INDEX(autor, ' ', -1)
FROM ksiazki;
-- Krok 3: Modyfikacja tabeli ksiazki
ALTER TABLE ksiazki ADD COLUMN id_autora_fk INT;
ALTER TABLE ksiazki ADD FOREIGN KEY (id_autora_fk) REFERENCES autorzy(id_autora);
-- Krok 4: Aktualizacja kluczy obcych w tabeli ksiazki
UPDATE ksiazki k
JOIN autorzy a ON k.autor = CONCAT(a.imie, ' ', a.nazwisko)
SET k.id_autora_fk = a.id_autora;
-- Krok 5: Usunięcie starej kolumny autor
ALTER TABLE ksiazki DROP COLUMN autor;
Tabela `autorzy`:
+-----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+---------+----------------+ | id_autora | int(11) | NO | PRI | NULL | auto_increment | | imie | varchar(100) | YES | | NULL | | | nazwisko | varchar(150) | NO | | NULL | | +-----------+--------------+------+-----+---------+----------------+
Tabela `ksiazki` (po modyfikacji):
+--------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+---------+----------------+ | id_ksiazki | int(11) | NO | PRI | NULL | auto_increment | | tytul | varchar(255) | NO | | NULL | | | rok_wydania | int(11) | YES | | NULL | | | isbn | varchar(13) | YES | UNI | NULL | | | id_autora_fk | int(11) | YES | MUL | NULL | | +--------------+--------------+------+-----+---------+----------------+
Wydzielenie autorów do osobnej tabeli wyeliminowało redundancję i związane z nią anomalie. Struktura bazy danych jest teraz bardziej elastyczna i gotowa na rozbudowę o dodatkowe informacje o autorach, bez wpływu na tabelę książek. To kluczowy krok w kierunku w pełni znormalizowanej i profesjonalnej bazy danych.
Zrozumienie i rozwiązanie problemu relacji wiele-do-wielu (N:M), gdzie jedna książka może mieć wielu autorów, a jeden autor może napisać wiele książek.
Model z zadania 1 zakłada, że każda książka ma tylko jednego autora (relacja jeden-do-wielu). W rzeczywistości jest to często nieprawda. Aby poprawnie zamodelować taką zależność, musimy wprowadzić trzecią tabelę, zwaną tabelą łączącą (lub asocjacyjną). Stworzymy tabelę `ksiazki_autorzy`, która będzie przechowywać pary `id_ksiazki` i `id_autora`, jednoznacznie łącząc książki z ich twórcami.
Bezpośrednie połączenie tabel `ksiazki` i `autorzy` kluczem obcym jest niemożliwe do zrealizowania w relacji N:M. Rozwiązaniem jest dekompozycja tej relacji na dwie relacje jeden-do-wielu za pomocą tabeli pośredniczącej.
+-----------------+ +----------------------+ +-------------------+
| autorzy | | ksiazki_autorzy | | ksiazki |
+-----------------+ +----------------------+ +-------------------+
| PK| id_autora |------>| FK| id_autora_fk |<------| PK| id_ksiazki |
| | imie | | FK| id_ksiazki_fk | | | tytul |
| | nazwisko | +----------------------+ | | rok_wydania |
+-----------------+ | | isbn |
+-------------------+
Tabela `ksiazki_autorzy` będzie miała złożony klucz główny składający się z dwóch kluczy obcych: `id_autora_fk` i `id_ksiazki_fk`. Taka struktura gwarantuje, że jeden autor może być przypisany do jednej książki tylko raz.
-- Krok 1: Usunięcie klucza obcego z tabeli ksiazki (jeśli istnieje)
ALTER TABLE ksiazki DROP FOREIGN KEY ksiazki_ibfk_1;
ALTER TABLE ksiazki DROP COLUMN id_autora_fk;
-- Krok 2: Tworzenie tabeli łączącej
CREATE TABLE ksiazki_autorzy (
id_ksiazki_fk INT,
id_autora_fk INT,
PRIMARY KEY (id_ksiazki_fk, id_autora_fk),
FOREIGN KEY (id_ksiazki_fk) REFERENCES ksiazki(id_ksiazki),
FOREIGN KEY (id_autora_fk) REFERENCES autorzy(id_autora)
);
-- Krok 3: Przykładowe wypełnienie danymi
-- Przypisanie książki o ID=1 do autorów o ID=1 i ID=2
INSERT INTO ksiazki_autorzy (id_ksiazki_fk, id_autora_fk) VALUES (1, 1), (1, 2);
-- 1. Znajdź wszystkich autorów książki o tytule "Władca Pierścieni"
-- Wymaga to złączenia trzech tabel: ksiazki -> ksiazki_autorzy -> autorzy.
SELECT a.imie, a.nazwisko
FROM autorzy a
JOIN ksiazki_autorzy ka ON a.id_autora = ka.id_autora_fk
JOIN ksiazki k ON ka.id_ksiazki_fk = k.id_ksiazki
WHERE k.tytul = 'Władca Pierścieni';
-- 2. Znajdź wszystkie książki napisane przez autora "J.R.R. Tolkien"
-- To zapytanie działa w drugą stronę, filtrując po autorze.
SELECT k.tytul
FROM ksiazki k
JOIN ksiazki_autorzy ka ON k.id_ksiazki = ka.id_ksiazki_fk
JOIN autorzy a ON ka.id_autora_fk = a.id_autora
WHERE a.nazwisko = 'Tolkien';
Wprowadzenie tabeli łączącej `ksiazki_autorzy` jest standardowym i poprawnym sposobem modelowania relacji wiele-do-wielu. Pozwala to na elastyczne przypisywanie dowolnej liczby autorów do dowolnej liczby książek, eliminując problemy z redundancją i anomaliami danych, które wystąpiłyby przy próbie rozwiązania tego problemu w inny sposób.
Zidentyfikowanie i usunięcie zależności przechodniej w danych adresowych tabeli `czytelnicy`.
Przyjrzymy się tabeli `czytelnicy` znormalizowanej do 2NF. Zawiera ona dane adresowe, w tym kod pocztowy i miasto. Zauważymy, że nazwa miasta jest funkcjonalnie zależna od kodu pocztowego. Oznacza to, że dla danego kodu pocztowego miasto jest zawsze takie samo. Jest to zależność przechodnia: id_czytelnika -> kod_pocztowy -> miasto. Taka struktura łamie zasady 3NF i prowadzi do problemów ze spójnością danych.
Oto struktura tabeli `czytelnicy`:
+---------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+----------------+ | id_czytelnika | int(11) | NO | PRI | NULL | auto_increment | | imie | varchar(100) | NO | | NULL | | | nazwisko | varchar(150) | NO | | NULL | | | kod_pocztowy | varchar(6) | YES | | NULL | | | miasto | varchar(100) | YES | | NULL | | | ulica | varchar(200) | YES | | NULL | | +---------------+--------------+------+-----+---------+----------------+
Problemy (anomalie) widoczne w tej strukturze:
-- Krok 1: Tworzenie tabeli kody_pocztowe
CREATE TABLE kody_pocztowe (
kod_pocztowy VARCHAR(6) PRIMARY KEY,
miasto VARCHAR(100) NOT NULL
);
-- Krok 2: Wypełnienie tabeli kody_pocztowe
INSERT INTO kody_pocztowe (kod_pocztowy, miasto)
SELECT DISTINCT kod_pocztowy, miasto FROM czytelnicy WHERE kod_pocztowy IS NOT NULL;
-- Krok 3: Modyfikacja tabeli czytelnicy
ALTER TABLE czytelnicy ADD CONSTRAINT fk_kod_pocztowy
FOREIGN KEY (kod_pocztowy) REFERENCES kody_pocztowe(kod_pocztowy);
ALTER TABLE czytelnicy DROP COLUMN miasto;
Tabela `kody_pocztowe`:
+--------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+---------+-------+ | kod_pocztowy | varchar(6) | NO | PRI | NULL | | | miasto | varchar(100) | NO | | NULL | | +--------------+--------------+------+-----+---------+-------+
Tabela `czytelnicy` (po modyfikacji):
+---------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+----------------+ | id_czytelnika | int(11) | NO | PRI | NULL | auto_increment | | imie | varchar(100) | NO | | NULL | | | nazwisko | varchar(150) | NO | | NULL | | | kod_pocztowy | varchar(6) | YES | MUL | NULL | | | ulica | varchar(200) | YES | | NULL | | +---------------+--------------+------+-----+---------+----------------+
Dzięki wydzieleniu kodów pocztowych i miast do osobnej tabeli, wyeliminowaliśmy zależność przechodnią i zapewniliśmy spójność danych adresowych. Każdy kod pocztowy jest teraz jednoznacznie powiązany z jednym miastem w całej bazie danych. To doskonały przykład na to, jak 3NF pomaga w utrzymaniu porządku i integralności danych.
Zidentyfikowanie i usunięcie zależności przechodniej między działem a jego kierownikiem w tabeli `pracownicy`.
Wracamy do tabeli `pracownicy`. W wersji przed normalizacją do 3NF, zawiera ona kolumny `dzial` i `kierownik_dzialu`. Kierownik działu nie jest bezpośrednią cechą pracownika, ale cechą działu, w którym pracownik jest zatrudniony. Mamy tu do czynienia z klasyczną zależnością przechodnią: id_pracownika -> dzial -> kierownik_dzialu. Aby to naprawić, musimy wydzielić informacje o działach do osobnej tabeli.
Spójrzmy na uproszczoną tabelę `pracownicy` przed normalizacją do 3NF:
+---------------+----------+--------------------+------------------+ | id_pracownika | nazwisko | dzial | kierownik_dzialu | +---------------+----------+--------------------+------------------+ | 1 | Nowak | Obsługa Czytelnika | Ewa Wiśniewska | | 2 | Kowalski | Obsługa Czytelnika | Ewa Wiśniewska | | 3 | Zieliński| Administracja | Jan Nowak | | 4 | Wójcik | Administracja | Jan Nowak | +---------------+----------+--------------------+------------------+
Problemy (anomalie) widoczne w tej strukturze:
-- Krok 1: Tworzenie tabeli dzialy
CREATE TABLE dzialy (
id_dzialu INT AUTO_INCREMENT PRIMARY KEY,
nazwa_dzialu VARCHAR(100) UNIQUE,
kierownik_dzialu VARCHAR(255)
);
-- Krok 2: Wypełnienie tabeli dzialy
INSERT INTO dzialy (nazwa_dzialu, kierownik_dzialu)
SELECT DISTINCT dzial, kierownik_dzialu FROM pracownicy_przed_3nf;
-- Krok 3: Modyfikacja tabeli pracownicy
-- Zakładamy, że tabela pracownicy istnieje i jest w 2NF
ALTER TABLE pracownicy ADD COLUMN id_dzialu_fk INT;
UPDATE pracownicy p
JOIN dzialy d ON p.dzial = d.nazwa_dzialu
SET p.id_dzialu_fk = d.id_dzialu;
ALTER TABLE pracownicy DROP COLUMN dzial;
ALTER TABLE pracownicy DROP COLUMN kierownik_dzialu;
ALTER TABLE pracownicy ADD FOREIGN KEY (id_dzialu_fk) REFERENCES dzialy(id_dzialu);
Tabela `dzialy`:
+------------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------------+--------------+------+-----+---------+----------------+ | id_dzialu | int(11) | NO | PRI | NULL | auto_increment | | nazwa_dzialu | varchar(100) | YES | UNI | NULL | | | kierownik_dzialu | varchar(255) | YES | | NULL | | +------------------+--------------+------+-----+---------+----------------+
Tabela `pracownicy` (po modyfikacji):
+-------------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------------+---------------+------+-----+---------+----------------+ | id_pracownika | int(11) | NO | PRI | NULL | auto_increment | | pesel | varchar(11) | NO | UNI | NULL | | | imie | varchar(100) | NO | | NULL | | | nazwisko | varchar(150) | NO | | NULL | | | stanowisko | varchar(100) | YES | | NULL | | | email | varchar(150) | YES | | NULL | | | telefon | varchar(20) | YES | | NULL | | | data_zatrudnienia | date | YES | | NULL | | | pensja | decimal(10,2) | YES | | NULL | | | id_dzialu_fk | int(11) | YES | MUL | NULL | | +-------------------+---------------+------+-----+---------+----------------+
Z sukcesem doprowadziłeś tabelę `pracownicy` do trzeciej postaci normalnej. Nauczyłeś się najważniejszej techniki osiągania 3NF: dekompozycji, czyli wydzielania atrybutów zależnych przechodnio do osobnej tabeli i łączenia ich za pomocą klucza obcego. Dzięki temu baza danych jest teraz bardziej elastyczna, spójna i odporna na anomalie. Zmiana kierownika działu wymaga teraz modyfikacji tylko jednego rekordu w tabeli `dzialy`.
Zrozumienie praktycznych korzyści płynących z posiadania bazy danych w 3NF poprzez tworzenie zapytań, które łączą dane z wielu tabel.
Twoja baza danych ma teraz znacznie lepszą strukturę. Składa się z oddzielnych, wyspecjalizowanych tabel. Aby uzyskać pełny obraz danych, musisz nauczyć się je łączyć. Twoim zadaniem jest napisanie zapytań SQL z użyciem klauzuli `JOIN`, aby tworzyć raporty i odpowiadać na pytania biznesowe. To ćwiczenie pokaże Ci, że chociaż normalizacja wymaga podziału danych, to dzięki relacjom i złączeniom (`JOIN`) nadal mamy pełen i spójny dostęp do wszystkich informacji.
-- 1. Pełna lista pracowników z danymi działu
SELECT p.imie, p.nazwisko, d.nazwa_dzialu, d.kierownik_dzialu
FROM pracownicy p
JOIN dzialy d ON p.id_dzialu_fk = d.id_dzialu;
-- 2. Lista książek z pełnymi danymi autora
SELECT k.tytul, k.rok_wydania, a.imie, a.nazwisko
FROM ksiazki k
JOIN autorzy a ON k.id_autora_fk = a.id_autora;
-- 3. Lista czytelników z miastem pobranym z osobnej tabeli
SELECT c.imie, c.nazwisko, c.ulica, kp.kod_pocztowy, kp.miasto
FROM czytelnicy c
JOIN kody_pocztowe kp ON c.kod_pocztowy = kp.kod_pocztowy;
-- 4. Liczba pracowników w działach kierowanych przez Ewę Wiśniewską
SELECT d.nazwa_dzialu, COUNT(p.id_pracownika) AS liczba_pracownikow
FROM dzialy d
LEFT JOIN pracownicy p ON d.id_dzialu = p.id_dzialu_fk
WHERE d.kierownik_dzialu = 'Ewa Wiśniewska'
GROUP BY d.nazwa_dzialu;
-- 5. Aktualizacja kierownika i weryfikacja zmiany
UPDATE dzialy SET kierownik_dzialu = 'Jan Nowak' WHERE nazwa_dzialu = 'Administracja';
-- Uruchom ponownie zapytanie nr 1, aby zobaczyć efekt. Zmiana jest widoczna dla wszystkich pracowników tego działu.
To zadanie udowodniło, że dobrze znormalizowana baza danych, mimo podziału na wiele tabel, jest niezwykle potężna i elastyczna. Nauczyłeś się używać złączeń (`JOIN`), aby swobodnie łączyć dane i tworzyć złożone raporty. Zobaczyłeś też w praktyce, jak eliminacja redundancji i zależności przechodnich (dzięki 3NF) sprawia, że aktualizacja danych jest prosta, szybka i bezpieczna – zmiana w jednym miejscu automatycznie i spójnie propaguje się w całej bazie danych.
Utrwalenie wiedzy poprzez tworzenie zapytań o różnym stopniu skomplikowania na w pełni znormalizowanej bazie danych. Zadanie pokazuje, jak efektywnie pobierać dane z wielu powiązanych ze sobą tabel.
Po zakończeniu procesu normalizacji nasza baza danych składa się z wielu wyspecjalizowanych tabel. Aby w pełni wykorzystać jej potencjał, musimy umieć łączyć zawarte w nich informacje. W tym zadaniu przeanalizujemy schemat całej bazy danych i stworzymy 10 zapytań SQL – od najprostszych, odpytujących pojedyncze tabele, po złożone, które wykorzystują złączenia (`JOIN`) do agregacji danych z różnych części systemu.
Poniższy schemat ASCII przedstawia wszystkie tabele w naszej znormalizowanej bazie danych oraz relacje (klucze obce) między nimi.
+-----------------+ +-------------------+ +-------------------+
| autorzy | | ksiazki | | kody_pocztowe |
+-----------------+ +-------------------+ +-------------------+
| PK| id_autora |<-------| FK| id_autora_fk | | PK| kod_pocztowy |
| | imie | | PK| id_ksiazki | | | miasto |
| | nazwisko | | | tytul | +-------------------+
+-----------------+ | | rok_wydania | ^
| | isbn | |
+-------------------+ |
|
+-------------------+ +-------------------+ +-------------------+
| dzialy | | pracownicy | | czytelnicy |
+-------------------+ +-------------------+ +-------------------+
| PK| id_dzialu |<-------| FK| id_dzialu_fk | | FK| kod_pocztowy |
| | nazwa_dzialu | | PK| id_pracownika |------->| PK| id_czytelnika |
| | kierownik... | | | pesel | | | imie |
+-------------------+ | | imie | | | nazwisko |
| nazwisko | | | ulica |
| ... | +-------------------+
+-------------------+
-- 1. Wyświetl listę wszystkich autorów, posortowaną alfabetycznie wg nazwiska.
-- To proste zapytanie odpytuje tylko jedną tabelę (`autorzy`), aby pobrać podstawowe informacje. Sortowanie (`ORDER BY`) pozwala na czytelne przedstawienie wyników.
SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko, imie;
-- 2. Znajdź pracowników zatrudnionych po 1 stycznia 2020 roku.
-- To zapytanie również działa na jednej tabeli (`pracownicy`), ale używa klauzuli `WHERE` do filtrowania wyników na podstawie daty zatrudnienia.
SELECT imie, nazwisko, data_zatrudnienia FROM pracownicy WHERE data_zatrudnienia > '2020-01-01';
-- 3. Wyświetl listę wszystkich książek wraz z imieniem i nazwiskiem ich autora.
-- To klasyczny przykład użycia `JOIN`. Łączymy tabelę `ksiazki` z tabelą `autorzy` za pomocą klucza obcego `id_autora_fk`, aby uzyskać pełne informacje.
SELECT k.tytul, a.imie, a.nazwisko
FROM ksiazki k
JOIN autorzy a ON k.id_autora_fk = a.id_autora;
-- 4. Znajdź wszystkich czytelników mieszkających w Krakowie.
-- Tutaj łączymy tabelę `czytelnicy` z `kody_pocztowe`, aby móc filtrować po nazwie miasta, która znajduje się w drugiej tabeli.
SELECT c.imie, c.nazwisko, c.ulica
FROM czytelnicy c
JOIN kody_pocztowe kp ON c.kod_pocztowy = kp.kod_pocztowy
WHERE kp.miasto = 'Kraków';
-- 5. Policz, ilu pracowników pracuje w każdym dziale.
-- To zapytanie łączy `pracownicy` z `dzialy`, a następnie używa funkcji agregującej `COUNT()` i klauzuli `GROUP BY`, aby zgrupować pracowników po nazwie działu i ich policzyć.
SELECT d.nazwa_dzialu, COUNT(p.id_pracownika) AS liczba_pracownikow
FROM pracownicy p
JOIN dzialy d ON p.id_dzialu_fk = d.id_dzialu
GROUP BY d.nazwa_dzialu;
-- 6. Znajdź tytuły książek napisanych przez autora o nazwisku "Sienkiewicz".
-- Podobnie jak w zapytaniu 3, łączymy tabele, ale tym razem filtrujemy wyniki po nazwisku autora, aby znaleźć tylko interesujące nas pozycje.
SELECT k.tytul, k.rok_wydania
FROM ksiazki k
JOIN autorzy a ON k.id_autora_fk = a.id_autora
WHERE a.nazwisko = 'Sienkiewicz';
-- 7. Wyświetl 5 działów z najwyższą średnią pensją pracowników.
-- To zaawansowane zapytanie. Najpierw liczymy średnią pensję (`AVG(pensja)`) dla każdego działu, grupując wyniki. Następnie sortujemy je malejąco (`DESC`) i za pomocą `LIMIT` wybieramy 5 najlepszych.
SELECT d.nazwa_dzialu, AVG(p.pensja) AS srednia_pensja
FROM pracownicy p
JOIN dzialy d ON p.id_dzialu_fk = d.id_dzialu
GROUP BY d.nazwa_dzialu
ORDER BY srednia_pensja DESC
LIMIT 5;
-- 8. Znajdź autorów, którzy napisali więcej niż jedną książkę.
-- Używamy tutaj klauzuli `HAVING`, która działa podobnie do `WHERE`, ale na wynikach grupowania. Najpierw grupujemy książki po autorze i liczymy je, a następnie `HAVING` filtruje tych autorów, którzy mają liczbę książek większą niż 1.
SELECT a.imie, a.nazwisko, COUNT(k.id_ksiazki) AS liczba_ksiazek
FROM autorzy a
JOIN ksiazki k ON a.id_autora = k.id_autora_fk
GROUP BY a.id_autora
HAVING COUNT(k.id_ksiazki) > 1;
-- 9. Wyświetl listę pracowników wraz z nazwą działu i nazwiskiem kierownika.
-- To zapytanie jest proste w swojej strukturze (`JOIN`), ale pokazuje siłę normalizacji: informacja o pracowniku, dziale i kierowniku jest pobierana z dwóch oddzielnych tabel, co zapewnia spójność danych.
SELECT p.imie, p.nazwisko, d.nazwa_dzialu, d.kierownik_dzialu
FROM pracownicy p
JOIN dzialy d ON p.id_dzialu_fk = d.id_dzialu;
-- 10. Znajdź czytelników, którzy mieszkają w miastach, w których kody pocztowe zaczynają się od "30-".
-- To zapytanie pokazuje, jak można łączyć filtrowanie w obu tabelach. Używamy `JOIN` do połączenia danych, a następnie `WHERE` z operatorem `LIKE` do znalezienia pasujących kodów pocztowych.
SELECT c.imie, c.nazwisko, kp.miasto, kp.kod_pocztowy
FROM czytelnicy c
JOIN kody_pocztowe kp ON c.kod_pocztowy = kp.kod_pocztowy
WHERE kp.kod_pocztowy LIKE '30-%';
W tym zadaniu przećwiczyłeś tworzenie zapytań na w pełni znormalizowanej bazie danych. Zobaczyłeś, jak za pomocą złączeń (`JOIN`), grupowania (`GROUP BY`) i filtrowania (`WHERE`, `HAVING`) można efektywnie wyciągać i analizować dane, nawet jeśli są one przechowywane w wielu różnych tabelach. To fundament pracy z relacyjnymi bazami danych.