Laboratorium 1.3: normalizacja do trzeciej postaci normalnej (3NF)

Wprowadzenie do trzeciej postaci normalnej (3NF)

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.

Teoria: trzecia postać normalna (3NF)

Tabela jest w trzeciej postaci normalnej (3NF), jeśli:

  1. Jest w drugiej postaci normalnej (2NF).
  2. Wszystkie jej atrybuty zależą tylko od klucza głównego, a nie od innych atrybutów niekluczowych (brak zależności przechodnich).

Zadanie 1: normalizacja tabeli `ksiazki` do 3NF

Cel

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.

Scenariusz

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`.

Analiza problemu

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:

Kroki do wykonania

  1. Zaprojektuj i utwórz nową tabelę `autorzy`, która będzie przechowywać unikalne informacje o autorach.
  2. Wypełnij tabelę `autorzy` danymi z istniejącej tabeli `ksiazki`.
  3. Zmodyfikuj tabelę `ksiazki`, usuwając kolumnę `autor` i dodając klucz obcy `id_autora_fk`.
  4. Zaktualizuj rekordy w tabeli `ksiazki`, aby poprawnie wskazywały na autorów w nowej tabeli.

Przykłady poleceń

-- 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;

Nowy schemat bazy danych

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    |                |
+--------------+--------------+------+-----+---------+----------------+

Podsumowanie

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.

Zadanie 1a: rozwiązanie problemu relacji wiele-do-wielu (N:M)

Cel

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.

Scenariusz

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.

Analiza problemu i rozwiązanie

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.

Kroki do wykonania

  1. Usuń klucz obcy `id_autora_fk` z tabeli `ksiazki`.
  2. Zaprojektuj i utwórz nową tabelę `ksiazki_autorzy`.
  3. Wypełnij tabelę `ksiazki_autorzy` danymi, tworząc powiązania.

Przykłady poleceń

-- 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);

Przykładowe zapytania na nowej strukturze

-- 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';

Podsumowanie

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.

Zadanie 2: normalizacja tabeli `czytelnicy` do 3NF

Cel

Zidentyfikowanie i usunięcie zależności przechodniej w danych adresowych tabeli `czytelnicy`.

Scenariusz

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.

Analiza problemu

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:

Kroki do wykonania

  1. Zaprojektuj i utwórz nową tabelę `kody_pocztowe`, która będzie przechowywać unikalne pary kod pocztowy-miasto.
  2. Wypełnij tabelę `kody_pocztowe` unikalnymi danymi z tabeli `czytelnicy`.
  3. Zmodyfikuj tabelę `czytelnicy`, usuwając kolumnę `miasto` i przekształcając `kod_pocztowy` w klucz obcy.

Przykłady poleceń

-- 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;

Nowy schemat bazy danych

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    |                |
+---------------+--------------+------+-----+---------+----------------+

Podsumowanie

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.

Zadanie 3: normalizacja tabeli `pracownicy` do 3NF

Cel

Zidentyfikowanie i usunięcie zależności przechodniej między działem a jego kierownikiem w tabeli `pracownicy`.

Scenariusz

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.

Analiza problemu

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:

Kroki do wykonania

  1. Zaprojektuj i utwórz nową tabelę `dzialy` do przechowywania informacji o działach i ich kierownikach.
  2. Wypełnij tabelę `dzialy` unikalnymi danymi z tabeli `pracownicy`.
  3. Zmodyfikuj tabelę `pracownicy`, usuwając nadmiarowe kolumny i dodając klucz obcy wskazujący na dział.

Przykłady poleceń

-- 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);

Nowy schemat bazy danych

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    |                |
+-------------------+---------------+------+-----+---------+----------------+

Podsumowanie

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`.

Zadanie 4: korzyści z 3NF w praktyce - zapytania na znormalizowanej bazie

Cel

Zrozumienie praktycznych korzyści płynących z posiadania bazy danych w 3NF poprzez tworzenie zapytań, które łączą dane z wielu tabel.

Scenariusz

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.

Przykłady poleceń

-- 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.

Podsumowanie laboratorium

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.

Zadanie 5: praktyczne zastosowanie zapytań na znormalizowanej bazie

Cel

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.

Scenariusz

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.

Schemat całej bazy danych i zależności

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         |
                             |    ...            |        +-------------------+
                             +-------------------+

Przykłady zapytań SQL

Zapytania na jednej tabeli

-- 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';

Zapytania łączące dwie tabele

-- 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';

Zapytania złożone

-- 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-%';

Podsumowanie

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.