1/60Normalizacja baz danych – 2NF: druga postać normalna

Od danych surowych do dobrze zorganizowanej struktury – krok drugi

Zakładamy znajomość 1NF (atomowość, brak powtarzających się grup, klucz główny).

W poprzedniej prezentacji doprowadziliśmy dane biblioteczne do 1NF. Teraz czas na drugi krok: eliminację częściowych zależności funkcyjnych.

1NF dała nam trzy rzeczy: atomowość, brak powtarzających się grup i klucz główny.

2NF – usuwamy zależności od części klucza, nie od całości
Slajd tytułowy – przejście od 1NF do 2NF z symbolem podziału tabeli na trzy części

Druga postać normalna stanowi naturalną kontynuację procesu normalizacji rozpoczętego w 1NF. Podczas gdy 1NF skupiała się na strukturze wewnątrz tabeli – atomowości, braku powtarzających się grup i kluczu głównym – 2NF zajmuje się relacjami pomiędzy kolumnami w obrębie tabeli. Aby zrozumieć 2NF, musisz najpierw opanować koncepcję zależności funkcyjnych, które opisują, w jaki sposób wartości w jednych kolumnach determinują wartości w innych.

W praktyce 2NF eliminuje jeden z czterech głównych problemów nieznormalizowanych danych: anomalię UPDATE. Dzięki wydzieleniu danych czytelników i książek do osobnych tabel, aktualizacja adresu czytelnika będzie wymagać modyfikacji tylko jednego wiersza, zamiast wielu. To samo dotyczy danych książki – zmiana tytułu czy autora będzie możliwa w jednym miejscu, a nie we wszystkich wypożyczeniach danej książki.

W tej prezentacji przekształcimy naszą pojedynczą tabelę Wypozyczenia w trzy osobne tabele: Czytelnicy, Ksiazki i Wypozyczenia. Każda z nich będzie przechowywać jeden rodzaj encji, co znacząco poprawi integralność danych i ułatwi zarządzanie systemem bibliotecznym. Proces ten jest kluczowy dla zrozumienia, jak projektować wydajne i bezpieczne bazy danych w praktyce.

2/60Agenda prezentacji – plan podróży przez 60 slajdów

Sześć części – od powtórki po podsumowanie

Prezentacja składa się z sześciu części:

  • Część 0: Powtórka z 1NF (slajdy 1-8) – przypomnienie najważniejszych koncepcji
  • Część I: Zależności funkcyjne (slajdy 9-16) – klucz do zrozumienia 2NF
  • Część II: Definicja 2NF (slajdy 17-24) – częściowe zależności
  • Część III: Dekompozycja (slajdy 25-35) – krok po kroku
  • Część IV: Implementacja w MariaDB (slajdy 36-50)
  • Część V: Podsumowanie i zapowiedź 3NF (slajdy 51-60)
2NF wymaga zrozumienia zależności funkcyjnych – poświęć czas na tę część
Mapa prezentacji – sześć części połączonych strzałkami

Prezentacja o 2NF została podzielona na sześć logicznych części, które prowadzą Cię od przypomnienia wiadomości z 1NF, przez wprowadzenie zależności funkcyjnych, definicję 2NF, praktyczną dekompozycję tabel, aż po implementację w MariaDB i podsumowanie. Taka struktura zapewnia płynne przejście od teorii do praktyki, co jest szczególnie ważne przy tak abstrakcyjnej koncepcji, jaką są zależności funkcyjne.

Część 0 stanowi powtórkę z 1NF, która jest niezbędna, ponieważ 2NF opiera się na założeniu, że tabela jest już zgodna z 1NF. Jeśli masz wątpliwości dotyczące atomowości, powtarzających się grup czy klucza głównego, warto wrócić do poprzedniej prezentacji przed przystąpieniem do nowego materiału. Części I i II wprowadzają definicje zależności funkcyjnych i reguły 2NF, które są najtrudniejszymi koncepcyjnie fragmentami tej prezentacji.

Najbardziej praktyczną częścią jest część III, w której krok po kroku przeprowadzimy dekompozycję tabeli Wypozyczenia na trzy osobne tabele. W części IV zaimplementujemy nową strukturę w MariaDB, a w części V podsumujemy zdobytą wiedzę i przygotujemy grunt pod 3NF. Zachęcam do aktywnego śledzenia każdej części i notowania pytań na bieżąco.

3/60Powtórka: co to jest 1NF?

Trzy zasady 1NF:

  • Atomowość – każda komórka zawiera dokładnie jedną, niepodzielną wartość
  • Brak powtarzających się grup – nie ma kolumn typu Autor1, Autor2, Autor3
  • Klucz główny – każdy wiersz jest unikalnie identyfikowalny

Bez 1NF nie ma dalszej normalizacji – to warunek konieczny.

Nasza tabela biblioteczna spełnia wszystkie trzy zasady.

1NF to fundament – bez niego nie ma 2NF. Tak jak bez przedszkola nie ma szkoły podstawowej.
Trzy złote zasady 1NF jako kamienie węgielne – atomowość, brak grup, klucz główny

Przypomnienie trzech złotych zasad 1NF jest dobrym punktem wyjścia do zrozumienia 2NF. Pierwsza zasada – atomowość – gwarantuje, że każda komórka zawiera dokładnie jedną, niepodzielną wartość, co umożliwia precyzyjne zapytania SQL bez użycia operatora LIKE. Druga zasada eliminuje powtarzające się grupy kolumn, takie jak Autor1, Autor2, Autor3, które uniemożliwiają elastyczne modelowanie danych o zmiennej liczbie wartości.

Trzecia zasada 1NF, nakazująca istnienie klucza głównego, jest szczególnie istotna dla 2NF. To właśnie analiza zależności funkcyjnych względem klucza głównego stanowi podstawę drugiej postaci normalnej. Jeśli atrybuty niebędące kluczem zależą tylko od części klucza głównego (a nie od całości), oznacza to naruszenie 2NF i konieczność dekompozycji tabeli.

Warto podkreślić, że 1NF jest warunkiem koniecznym, ale niewystarczającym dla 2NF. Innymi słowy, jeśli tabela nie jest w 1NF, nie ma sensu sprawdzać jej zgodności z wyższymi postaciami normalnymi. Dlatego przed przystąpieniem do 2NF należy upewnić się, że wszystkie trzy zasady 1NF są spełnione – nasza tabela Wypozyczenia je spełnia, co potwierdziliśmy w poprzedniej prezentacji.

4/60Powtórka: gdzie skończyliśmy w 1NF

Tabela Wypozyczenia z 17 kolumnami:

  • ID_Wypozyczenia (klucz główny)
  • Czytelnik, Adres, Miasto, KodPocztowy, Telefon, Email
  • Tytul, Autor, ISBN, Gatunek, RokWydania, Wydawnictwo
  • DataWyp, DataZwrotu, Kara, Status

Klucz główny: ID_Wypozyczenia.

Wszystkie dane w jednej tabeli – czytelnicy, książki i wypożyczenia razem.

Mamy jedną tabelę, która robi wszystko – ale robi to źle. Czas na podział.
Tabela Wypozyczenia z 17 kolumnami pogrupowanymi w trzy kategorie: czytelnik, książka, wypożyczenie

Stan po 1NF to sytuacja, w której tabela Wypozyczenia zawiera wszystkie dane w jednej strukturze, ale każda komórka przechowuje pojedynczą, atomową wartość. Mimo spełnienia warunków 1NF, wciąż mamy do czynienia z wymieszaniem trzech odrębnych encji: danych czytelnika (sześć kolumn), danych książki (sześć kolumn) oraz danych wypożyczenia (cztery kolumny plus klucz główny). To wymieszanie jest źródłem redundancji i anomalii.

W praktyce projektowej taka struktura świadczy o tym, że proces normalizacji nie został zakończony. Każda encja powinna być przechowywana w osobnej tabeli, co jest celem 2NF. W naszym przypadku oznacza to, że z jednej tabeli Wypozyczenia powstaną trzy: Czytelnicy (z danymi osobowymi), Ksiazki (z metadanymi książek) i Wypozyczenia (z informacjami o zdarzeniach, zawierające klucze obce do dwóch pozostałych tabel).

Warto w tym miejscu zwrócić uwagę na kolumny tabeli. Sześć kolumn dotyczy czytelnika, sześć książki, a pozostałe wypożyczenia. Taka proporcja wskazuje, że większość danych w tabeli to dane opisowe encji zależnych, które powinny być przechowywane osobno. Po dekompozycji tabela Wypozyczenia będzie zawierać zaledwie kilka kolumn: klucz główny, dwa klucze obce oraz atrybuty specyficzne dla zdarzenia wypożyczenia.

5/60Powtórka: czego 1NF nie naprawiła?

Mimo 1NF wciąż mamy problemy:

  • Adres czytelnika powtórzony przy każdym wypożyczeniu
  • Dane książki przy każdym wypożyczeniu
  • Anomalia INSERT – nie można dodać nowej książki bez wypożyczenia
  • Anomalia UPDATE – zmiana adresu = aktualizacja wielu wierszy
  • Anomalia DELETE – usunięcie ostatniego wypożyczenia = utrata danych czytelnika i książki

Wniosek: 1NF to dopiero pierwszy krok.

1NF to jak posprzątanie biurka – wygląda lepiej, ale segregacja dokumentów dopiero przed nami.
Biurko po sprzątaniu – pozornie czyste, ale papiery wciąż w jednym koszu

Mimo że 1NF została spełniona, w tabeli Wypozyczenia wciąż występują cztery poważne problemy, które negatywnie wpływają na integralność i wydajność systemu. Po pierwsze, dane czytelnika są powielane przy każdym jego wypożyczeniu, co prowadzi do redundancji i ryzyka niespójności. Po drugie, dodanie nowego czytelnika lub książki do systemu jest niemożliwe bez istnienia wypożyczenia, co stanowi anomalię INSERT.

Po trzecie, zmiana adresu czytelnika wymaga aktualizacji wszystkich wierszy zawierających jego dane, co jest anomalią UPDATE. Im więcej wypożyczeń ma czytelnik, tym więcej wierszy trzeba zaktualizować i tym większe ryzyko, że któryś z nich zostanie pominięty. Po czwarte, usunięcie ostatniego wypożyczenia czytelnika kasuje również wszystkie jego dane osobowe, co jest anomalią DELETE.

1NF rozwiązuje problemy na poziomie pojedynczej komórki – zapewnia, że każda wartość jest atomowa i że istnieje klucz główny. Nie rozwiązuje jednak problemów na poziomie relacji między wierszami, które wynikają z wymieszania różnych encji. 2NF zajmuje się właśnie tymi problemami, eliminując redundancję i anomalie poprzez podział tabeli na mniejsze, wyspecjalizowane jednostki.

6/60Powtórka: dane w naszej tabeli – konkretne wartości

Przypomnienie przykładowych danych:

  • Jan Kowalski, Kraków, 30-001 – 2 wypożyczenia (Przedwiośnie, Lalka)
  • Anna Nowak, Warszawa, 00-002 – 1 wypożyczenie (Pan Tadeusz)
  • Piotr Wiśniewski, Gdańsk, 80-003 – wiele książek
  • Maria Zalewska, Wrocław, 50-004 – periodyki
  • Tomasz Adamski, Poznań, 60-005 – książki naukowe

Każdy fakt (adres, dane książki) powtarza się wielokrotnie.

Zapamiętaj te dane – będziemy je przekształcać krok po kroku.
Pięciu czytelników z książkami – strzałki pokazujące powielone dane osobowe

Przypomnienie konkretnych danych przechowywanych w tabeli Wypozyczenia pomaga zrozumieć skalę problemu, z którym mierzy się 2NF. Mamy pięciu czytelników: Jana Kowalskiego z Krakowa, Annę Nowak z Warszawy, Piotra Wiśniewskiego z Gdańska, Marię Zalewską z Wrocławia oraz Tomasza Adamskiego z Poznania. Każdy z nich ma jedno lub więcej wypożyczeń, a ich dane osobowe są powielane przy każdym wypożyczeniu.

Jan Kowalski ma dwa wypożyczenia – Przedwiośnie Żeromskiego i Lalkę Prusa. Jego adres, miasto, kod pocztowy, telefon i email są zapisane dwukrotnie. Gdyby Jan miał pięćdziesiąt wypożyczeń, jego dane byłyby powielone pięćdziesiąt razy. W systemie uniwersyteckim obsługującym tysiące studentów, taka redundancja prowadzi do milionów nadmiarowych komórek danych w skali całej bazy.

Celem 2NF jest wyeliminowanie tej redundancji poprzez przeniesienie danych czytelników do osobnej tabeli Czytelnicy, danych książek do osobnej tabeli Ksiazki, a w tabeli Wypozyczenia pozostawienie tylko informacji specyficznych dla zdarzenia wypożyczenia oraz kluczy obcych łączących ją z dwiema pozostałymi tabelami. Po tej operacji dane każdego czytelnika i każdej książki będą przechowywane dokładnie jeden raz.

7/60Powtórka: zapytania SQL w 1NF

Zapytania SQL, które działają na 1NF

Przypomnienie składni: SELECT, FROM, WHERE, AND

-- Znajdź wypożyczenia opóźnione (DataZwrotu jest NULL mimo upływu czasu)
SELECT Czytelnik, Tytul, DataWyp
FROM Wypozyczenia
WHERE DataZwrotu IS NULL
  AND DataWyp < '2026-03-01';
-- Wynik: pokazuje, które książki są przetrzymane

NULL vs '' – NULL oznacza brak danych.

Te zapytania działają, bo tabela jest w 1NF – każda kolumna zawiera atomowe wartości.
Okno konsoli SQL z wynikiem zapytania – trzy książki przetrzymane

Zapytania SQL działające na tabeli zgodnej z 1NF są proste, ale często nieprecyzyjne. Przykład zapytania pokazany na slajdzie – wyszukanie wypożyczeń opóźnionych – działa poprawnie, ponieważ kolumny DataZwrotu i DataWyp są atomowe i mają określony typ DATE. Jednak to samo zapytanie, gdybyśmy chcieli dodać informacje o czytelniku (na przykład jego adres), wymagałoby pobrania danych z tej samej tabeli, co wiąże się z przetwarzaniem wszystkich wierszy.

Po przejściu do 2NF zapytania staną się bardziej złożone ze względu na konieczność łączenia tabel za pomocą JOIN, ale jednocześnie będą bardziej precyzyjne i wydajne. Na przykład zapytanie "pokaż adresy czytelników, którzy mają opóźnione wypożyczenia" będzie wymagać połączenia tabel Czytelnicy i Wypozyczenia, ale dane adresowe będą pobierane z małej tabeli Czytelnicy, która ma jeden wiersz na czytelnika, a nie z dużej tabeli Wypozyczenia z wieloma wierszami na czytelnika.

W praktyce różnica w wydajności między zapytaniem na tabeli nieznormalizowanej a zapytaniem z JOIN na tabelach znormalizowanych staje się widoczna dopiero przy dużych wolumenach danych. Dla tabel z milionami wierszy, zapytanie z JOIN na odpowiednio zindeksowanych kolumnach kluczy obcych może być nawet kilkadziesiąt razy szybsze niż zapytanie na jednej dużej tabeli z redundancją.

8/60Powtórka: referencje między danymi – ukryte powiązania

Ukryte powiązania w tabeli:

  • Jan Kowalski ma adres "ul. Polna 10, Kraków, 30-001"
  • ISBN 978-83-123-4567-1 to "Przedwiośnie" Żeromskiego
  • ISBN 978-83-123-4567-2 to "Lalka" Prusa

Te powiązania są ukryte w strukturze – niewidoczne dla bazy.

W 2NF: wydobędziemy je na światło dzienne poprzez podział na tabele.

Dane w jednej tabeli to jak wymieszane puzzle – widać obraz, ale wszystkie elementy są w jednym pudełku.
Puzzle wymieszane w jednym pudełku vs puzzle posegregowane w trzech pudełkach

Ukryte powiązania między danymi w tabeli Wypozyczenia to zależności, które są oczywiste dla człowieka, ale niewidoczne dla bazy danych. Każdy programista czytający tabelę wie, że Jan Kowalski zawsze mieszka w Krakowie, a ISBN 978-83-123-4567-1 zawsze odpowiada "Przedwiośniu" Żeromskiego. Jednak baza danych nie ma żadnej wiedzy o tych powiązaniach – dla niej są to po prostu wartości w kolumnach, które mogą być różne w każdym wierszu.

Celem 2NF jest wydobycie tych ukrytych powiązań na światło dzienne poprzez formalne zdefiniowanie ich jako zależności funkcyjnych i utworzenie osobnych tabel dla każdej grupy powiązanych atrybutów. W terminologii baz danych mówimy, że 2NF eliminuje częściowe zależności funkcyjne, czyli sytuacje, w których atrybuty niebędące kluczem zależą tylko od części klucza głównego, a nie od całości.

W naszej tabeli mamy dwie grupy częściowych zależności funkcyjnych. Po pierwsze, atrybuty czytelnika (Adres, Miasto, KodPocztowy, Telefon, Email) zależą wyłącznie od kolumny Czytelnik, która nie jest kluczem głównym. Po drugie, atrybuty książki (Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) zależą wyłącznie od kolumny ISBN. W 2NF te atrybuty zostaną przeniesione do osobnych tabel, a w tabeli Wypozyczenia pozostaną tylko klucze obce odwołujące się do tych tabel.

9/60Co to jest zależność funkcyjna? – definicja

Definicja – związek między atrybutami

Zależność funkcyjna (ang. functional dependency) – związek między dwoma atrybutami.

Oznaczenie: A → B (A wyznacza B, A determinuje B).

Znaczenie: jeśli znasz wartość A, możesz jednoznacznie określić wartość B.

Przykład z życia: PESEL → Imię, Nazwisko (znając PESEL, wiesz kim jest osoba).

Przykład z biblioteki: ISBN → Tytul (znając ISBN, wiesz jaki jest tytuł książki).

Zależność funkcyjna to jak relacja: 'powiedz mi A, a powiem ci B'. Jeśli dla tego samego A zawsze jest to samo B – mamy zależność.
Schemat – strzałka od A do B z napisem 'A → B' i przykładem ISBN → Tytul

Zależność funkcyjna to fundamentalna koncepcja w teorii relacyjnych baz danych, która opisuje związek między dwoma zbiorami atrybutów w tabeli. Formalnie mówimy, że atrybut Y jest funkcyjnie zależny od atrybutu X, co zapisujemy jako X → Y, jeśli każda wartość X jednoznacznie determinuje wartość Y. Oznacza to, że w całej tabeli nie mogą istnieć dwa wiersze o tej samej wartości X, ale różnych wartościach Y.

W praktyce zależności funkcyjne odzwierciedlają reguły biznesowe obowiązujące w modelowanej rzeczywistości. Na przykład w systemie bibliotecznym zależność ISBN → Tytul oznacza, że każdy numer ISBN przypisany jest do dokładnie jednego tytułu książki. Gdybyśmy mieli dwa wiersze z tym samym ISBN, ale różnymi tytułami, oznaczałoby to błąd danych – ten sam ISBN nie może oznaczać dwóch różnych książek.

Zrozumienie zależności funkcyjnych jest kluczowe nie tylko dla normalizacji, ale także dla ogólnego projektowania baz danych. Każda postać normalna, od 1NF do 5NF, definiuje konkretny typ zależności funkcyjnej, która powinna być wyeliminowana. W 2NF eliminujemy częściowe zależności funkcyjne, w 3NF zależności przechodnie, a w BCNF zależności wynikające z nakładających się kluczy kandydujących.

10/60Przykłady zależności funkcyjnych

W codziennym życiu:

  • Numer albumu → Imię, Nazwisko studenta
  • Kod pocztowy → Miasto (dla danego kodu, miasto jest stałe)
  • ID produktu → Cena (w sklepie, każdy produkt ma jedną cenę)

W naszej tabeli bibliotecznej:

  • ISBN → Tytul, Autor, Gatunek, RokWydania, Wydawnictwo
  • Czytelnik → Adres, Miasto, KodPocztowy, Telefon, Email
  • ID_Wypozyczenia → wszystkie kolumny (klucz główny)
Zależności funkcyjne są wszędzie – wystarczy się rozejrzeć. Każdy identyfikator wyznacza jakieś dane.
Trzy karty z przykładami – PESEL→osoba, kod pocztowy→miasto, ISBN→tytuł

Analiza zależności funkcyjnych w naszej tabeli Wypozyczenia pozwala zidentyfikować, które atrybuty są ze sobą powiązane i które z tych powiązań stanowią problem z punktu widzenia normalizacji. Główny klucz tabeli, ID_Wypozyczenia, funkcyjnie wyznacza wszystkie pozostałe atrybuty – to naturalna właściwość klucza głównego. Problem pojawia się, gdy atrybuty niebędące kluczem wyznaczają inne atrybuty niebędące kluczem.

W naszej tabeli mamy następujące zależności funkcyjne: Czytelnik → {Adres, Miasto, KodPocztowy, Telefon, Email} – każdy czytelnik ma jeden adres, miasto, kod pocztowy, telefon i email. ISBN → {Tytul, Autor, Gatunek, RokWydania, Wydawnictwo} – każdy ISBN odpowiada jednej książce z określonym tytułem, autorem, gatunkiem, rokiem wydania i wydawnictwem. Te zależności są oczywiste z biznesowego punktu widzenia, ale w obecnej strukturze prowadzą do redundancji.

Co ważne, zależność Czytelnik → Adres oznacza, że w całej tabeli nie mogą istnieć dwa wiersze z tym samym czytelnikiem, ale różnymi adresami. Jeśli Jan Kowalski ma w jednym wierszu adres "Kraków", a w innym "Warszawa", oznacza to naruszenie tej zależności funkcyjnej i błąd w danych. W 2NF tworzymy osobną tabelę Czytelnicy, gdzie adres jest przechowywany raz, co automatycznie egzekwuje tę zależność.

11/60Zależności funkcyjne w naszej tabeli – mapa

Mapa zależności dla tabeli Wypozyczenia:

  • ID_Wypozyczenia → WSZYSTKO (klucz główny wyznacza każdą kolumnę)
  • ISBN → Tytul, Autor, Gatunek, RokWydania, Wydawnictwo
  • Czytelnik → Adres, Miasto, KodPocztowy, Telefon, Email
  • (Czytelnik, ISBN, DataWyp) → Kara, DataZwrotu (zależy od konkretnego wypożyczenia)

Uwaga: ISBN i Czytelnik nie są kluczami – ale wyznaczają inne kolumny. To jest kluczowe spostrzeżenie dla 2NF!

Nie tylko klucz główny wyznacza dane – ISBN i Czytelnik też. To ukryte zależności, które 2NF ma wyeliminować.
Tabela z kolorowymi strzałkami – od ISBN do danych książki, od Czytelnik do danych osobowych

Wizualizacja zależności funkcyjnych w formie graficznej – strzałek między kolumnami – pomaga zrozumieć, które zależności są problematyczne. Zielone strzałki wychodzące od klucza głównego ID_Wypozyczenia do wszystkich pozostałych kolumn są naturalne i pożądane – to właśnie oznacza bycie kluczem głównym. Każda kolumna powinna być funkcyjnie zależna od klucza głównego, ponieważ klucz jednoznacznie identyfikuje wiersz.

Problemem są czerwone strzałki, które reprezentują zależności funkcyjne od kolumn niebędących kluczami. W naszej tabeli mamy dwie takie strzałki: od Czytelnik do adresu, miasta, kodu pocztowego, telefonu i emaila oraz od ISBN do tytułu, autora, gatunku, roku wydania i wydawnictwa. Te strzałki są problematyczne, ponieważ oznaczają, że wartość kolumny niebędącej kluczem determinuje wartości innych kolumn, co prowadzi do redundancji.

W 2NF celem jest usunięcie wszystkich czerwonych strzałek poprzez utworzenie osobnych tabel dla każdej grupy zależności. Po dekompozycji każda tabela będzie zawierać tylko zielone strzałki – od swojego klucza głównego do pozostałych atrybutów. Tabele będą połączone kluczami obcymi, które reprezentują relacje między encjami, ale nie stanowią już źródła zależności funkcyjnych prowadzących do redundancji.

12/60Dlaczego zależności od ISBN są problemem?

Dlaczego to problem?

  • ISBN wyznacza Tytul – tytuł książki zależy od ISBN
  • Ale ISBN to NIE jest klucz główny tabeli

Konsekwencje:

  • Jeśli książka o ISBN=123 ma tytuł "X" i jest wypożyczona 10 razy → tytuł "X" pojawia się 10 razy
  • Zmiana tytułu wymaga UPDATE 10 wierszy (anomalia UPDATE)
  • Nie można dodać nowej książki, dopóki nie zostanie wypożyczona (anomalia INSERT)

Problem: tytuł to dane książki, ale przechowujemy je w tabeli wypożyczeń.

Dane książki nie powinny być w tabeli wypożyczeń – one należą do książki, nie do wypożyczenia.
Podkreślone powtarzające się tytuły książek w tabeli – widać redundancję

Zależności funkcyjne od atrybutów niebędących kluczami są głównym źródłem redundancji i anomalii w tabelach zgodnych z 1NF. Gdy atrybut taki jak Czytelnik funkcyjnie wyznacza Adres, oznacza to, że w każdym wierszu dotyczącym tego samego czytelnika wartość Adres musi być taka sama. System nie ma jednak mechanizmu wymuszającego tę regułę – to programista musi pamiętać o wpisaniu tego samego adresu za każdym razem.

W tabeli z pięćdziesięcioma wypożyczeniami Jana Kowalskiego, adres "ul. Polna 10, Kraków" pojawi się pięćdziesiąt razy. Jeśli przy którymś wpisie programista przez pomyłkę wpisze "ul. Polna 10, Warszawa", baza danych nie zgłosi błędu – obie wartości są poprawne z punktu widzenia typu danych. Dopiero przy generowaniu raportu okaże się, że Jan Kowalski mieszka jednocześnie w dwóch miastach, co jest niemożliwe w rzeczywistości.

Rozwiązaniem jest przeniesienie danych osobowych do osobnej tabeli Czytelnicy z kluczem głównym ID_Czytelnika. W tej tabeli adres Jana Kowalskiego będzie przechowywany dokładnie jeden raz. Zależność funkcyjna ID_Czytelnika → Adres będzie automatycznie egzekwowana przez strukturę tabeli – zmiana adresu wymaga modyfikacji jednego wiersza. W tabeli Wypozyczenia pozostanie tylko ID_Czytelnika jako klucz obcy, bez redundancji danych adresowych.

13/60Wizualizacja zależności – strzałki na tabeli

Wizualizacja zależności – strzałki na tabeli:

  • Strzałka od ISBN do Tytul, Autor, Gatunek, RokWydania, Wydawnictwo
  • Strzałka od Czytelnik do Adres, Miasto, KodPocztowy, Telefon, Email
  • Strzałka od ID_Wypozyczenia do WSZYSTKIEGO

Widać, że niektóre strzałki zaczynają się od atrybutów, które nie są kluczem.

To są zbędne zależności – dane książki i czytelnika "wiszą" na nie-kluczach.

Każda strzałka od nie-klucza to sygnał, że dane są w złej tabeli.
Tabela z trzema kolorami strzałek – zielone od klucza, czerwone od nie-kluczy

Podsumowanie problematycznych zależności funkcyjnych w naszej tabeli to kluczowy moment do zrozumienia, dlaczego 2NF jest potrzebna. Mamy dwa rodzaje zależności od nie-kluczy: zależności od kolumny Czytelnik (determinującej dane adresowe i kontaktowe) oraz zależności od kolumny ISBN (determinującej metadane książki). Obie te zależności są naturalne z biznesowego punktu widzenia, ale ich występowanie w jednej tabeli prowadzi do problemów.

Warto zauważyć, że nie wszystkie zależności funkcyjne od nie-kluczy są problematyczne. Na przykład zależność Miasto → KodPocztowy (miasto wyznacza kod pocztowy) również występuje w naszej tabeli, ale nie jest bezpośrednim źródłem redundancji na poziomie 2NF. Taka zależność przechodnia zostanie wyeliminowana dopiero w 3NF. 2NF skupia się wyłącznie na zależnościach od części klucza głównego.

W naszym przypadku klucz główny to ID_Wypozyczenia. Ponieważ Czytelnik i ISBN nie są częścią klucza głównego, zależności od nich są zależnościami od nie-kluczy i kwalifikują się do eliminacji w 2NF. Gdybyśmy zmienili klucz główny na złożony (Czytelnik, ISBN, DataWyp), wówczas Czytelnik i ISBN stałyby się częścią klucza, a zależności od nich byłyby zależnościami częściowymi, które 2NF również eliminuje.

14/60Klucz złożony – co to jest i dlaczego jest ważny?

Klucz złożony (composite key)

Klucz złożony – klucz główny składający się z wielu kolumn.

W naszej tabeli mamy klucz prosty: ID_Wypozyczenia.

ALE: gdybyśmy nie mieli ID_Wypozyczenia, jaki byłby naturalny klucz?

  • (Czytelnik, ISBN, DataWyp) – naturalny klucz złożony
  • Czytelnik wypożycza konkretną książkę konkretnego dnia

Dlaczego to ważne? Bo zależności częściowe występują TYLKO przy kluczach złożonych.

Klucz złożony to sytuacja, w której 2NF ma sens. Przy kluczu prostym 2NF jest spełniona automatycznie.
Klucz składający się z trzech części (Czytelnik + ISBN + Data) połączonych w jeden

Klucze złożone (ang. composite keys) to klucze główne składające się z dwóch lub więcej kolumn, które razem jednoznacznie identyfikują każdy wiersz w tabeli. W praktyce projektowej klucze złożone są bardzo powszechne, szczególnie w tabelach asocjacyjnych, które modelują relacje wiele-do-wielu. Na przykład w tabeli Wypozyczenia naturalnym kluczem złożonym mogłoby być (Czytelnik, ISBN, DataWyp), ponieważ te trzy atrybuty razem jednoznacznie identyfikują konkretne wypożyczenie.

Wprowadzenie klucza złożonego ma istotne konsekwencje dla analizy 2NF. Gdy klucz główny składa się z wielu kolumn, pojawia się możliwość, że niektóre atrybuty niebędące kluczem zależą tylko od części klucza złożonego, a nie od całości. W naszym przykładzie, gdyby kluczem było (Czytelnik, ISBN, DataWyp), atrybuty takie jak Adres zależałyby tylko od Czytelnik (części klucza), a Tytul tylko od ISBN (części klucza).

Takie częściowe zależności funkcyjne są właśnie tym, co eliminuje 2NF. Rozwiązaniem jest dekompozycja tabeli na mniejsze tabele, w których każdy atrybut niebędący kluczem zależy od pełnego klucza głównego, a nie od jego części. W naszym przypadku oznacza to utworzenie osobnych tabel dla czytelników, książek i wypożyczeń, gdzie każda tabela ma swój własny klucz główny i atrybuty w pełni od niego zależne.

15/60Dlaczego zmieniamy klucz na złożony?

Zmieniamy klucz na złożony – cel dydaktyczny

W celach dydaktycznych: zmieniamy klucz z ID_Wypozyczenia na (Czytelnik, ISBN, DataWyp).

Uzasadnienie biznesowe: czytelnik może wypożyczyć tę samą książkę raz dziennie.

Teraz klucz = (Czytelnik, ISBN, DataWyp) – 3 kolumny.

Pojawiają się zależności częściowe:

  • Tytul zależy od ISBN (części klucza)
  • Adres zależy od Czytelnik (części klucza)
  • DataZwrotu zależy od WSZYSTKICH trzech (zależność pełna)
Zmiana klucza na złożony ujawnia problemy ukryte wcześniej przez ID_Wypozyczenia.
Klucz ID_Wypozyczenia przekreślony, obok nowy klucz złożony z trzech części

Zmiana klucza głównego z ID_Wypozyczenia na złożony klucz (Czytelnik, ISBN, DataWyp) jest zabiegiem czysto dydaktycznym, który ma na celu zilustrowanie mechanizmu częściowych zależności funkcyjnych. Ważne: w praktyce nie zmieniamy kluczy bez potrzeby – ID_Wypozyczenia pozostaje kluczem głównym. Gdyby jednak kluczem był zestaw (Czytelnik, ISBN, DataWyp), pojawiłyby się zależności częściowe, które 2NF eliminuje. Zmieniamy klucz tylko po to, aby zobaczyć, jakie problemy 2NF rozwiązuje – w rzeczywistym projekcie od razu projektujemy tabele w 2NF, nie przechodząc przez tę symulację.

Po zmianie klucza na (Czytelnik, ISBN, DataWyp) analiza zależności funkcyjnych wygląda następująco. Pełny klucz (Czytelnik, ISBN, DataWyp) wyznacza wszystkie atrybuty w tabeli. Jednak atrybuty Adres, Miasto, KodPocztowy, Telefon, Email zależą tylko od Czytelnik – części klucza. Podobnie Tytul, Autor, Gatunek, RokWydania, Wydawnictwo zależą tylko od ISBN – drugiej części klucza. To są częściowe zależności funkcyjne, które naruszają 2NF.

Rozwiązaniem jest podział tabeli na trzy części. Atrybuty zależne od Czytelnik trafiają do tabeli Czytelnicy z kluczem ID_Czytelnika. Atrybuty zależne od ISBN trafiają do tabeli Ksiazki z kluczem ID_Ksiazki lub ISBN. W tabeli Wypozyczenia pozostają tylko atrybuty zależne od pełnego klucza: DataWyp, DataZwrotu, Kara, Status oraz dwa klucze obce do tabel Czytelnicy i Ksiazki.

16/60Zależność pełna vs częściowa – kluczowa różnica

Różnica kluczowa dla 2NF

Zależność pełna: atrybut zależy od CAŁEGO klucza (wszystkich kolumn)

  • DataZwrotu zależy od (Czytelnik, ISBN, DataWyp) – cały klucz
  • Kara zależy od całego klucza

Zależność częściowa: atrybut zależy od CZĘŚCI klucza (niektórych kolumn)

  • Tytul zależy od ISBN (samego, bez Czytelnik i DataWyp)
  • Adres zależy od Czytelnik (samego, bez ISBN i DataWyp)

2NF eliminuje zależności częściowe.

Zależność częściowa = atrybut niepotrzebnie 'ciągnie się' za częścią klucza, a nie za całością.
Dwa schematy obok siebie – pełna zależność (strzałka przez cały klucz) vs częściowa (strzałka przez fragment)

Rozróżnienie między całkowitą a częściową zależnością funkcyjną jest kluczowe dla zrozumienia drugiej postaci normalnej. Całkowita zależność funkcyjna oznacza, że atrybut Y zależy od całego klucza głównego X – czyli od wszystkich kolumn wchodzących w skład klucza. Jeśli klucz jest złożony z kolumn A, B, C, to atrybut Y jest całkowicie zależny od (A, B, C), jeśli znajomość wszystkich trzech kolumn jest potrzebna do jednoznacznego określenia Y.

Częściowa zależność funkcyjna występuje wtedy, gdy atrybut Y zależy tylko od części klucza głównego, a nie od całości. Jeśli klucz to (A, B, C), a Y zależy tylko od A, to mamy do czynienia z częściową zależnością funkcyjną. W naszej tabeli z kluczem (Czytelnik, ISBN, DataWyp), atrybut Adres zależy tylko od Czytelnik, a nie od ISBN ani DataWyp, więc jest to częściowa zależność funkcyjna.

2NF wymaga, aby każdy atrybut niebędący kluczem był całkowicie zależny od całego klucza głównego. Jeśli tabela ma klucz prosty (jednokolumnowy), to automatycznie spełnia warunek 2NF, ponieważ nie ma możliwości częściowej zależności – pojedyncza kolumna nie ma "części", od których można by zależeć. Dlatego 2NF ma praktyczne znaczenie tylko dla tabel z kluczami złożonymi. W naszej tabeli z kluczem ID_Wypozyczenia 2NF jest automatycznie spełniona dla klucza prostego.

17/60Definicja formalna 2NF

Definicja formalna 2NF:

Relacja jest w 2NF, jeśli:

  1. Jest w 1NF (warunek konieczny – bez 1NF nie ma 2NF)
  2. Każdy atrybut niekluczowy jest w pełni zależny funkcyjnie od CAŁEGO klucza głównego

Inaczej: żaden atrybut niekluczowy nie może zależeć od części klucza złożonego.

Jeśli klucz jest prosty (jednokolumnowy) – 2NF jest automatycznie spełniona.

2NF eliminuje częściowe zależności funkcyjne – atrybuty muszą zależeć od CAŁEGO klucza, a nie od jego fragmentu.
Definicja 2NF jako dwóch warunków – (1) 1NF, (2) pełna zależność od klucza

Definicja formalna 2NF jest zwięzła, ale niesie ze sobą głębokie konsekwencje praktyczne. Pierwszy warunek wymaga, aby tabela była już w 1NF – co oznacza, że przed sprawdzaniem 2NF należy upewnić się, że wszystkie trzy zasady 1NF są spełnione. Drugi warunek stanowi, że każdy atrybut niebędący kluczem musi być w pełni zależny funkcyjnie od całego klucza głównego, a nie tylko od jego części.

W praktyce oznacza to, że jeśli tabela ma klucz prosty (jedną kolumnę jako klucz główny), to automatycznie spełnia warunek 2NF. Dzieje się tak, ponieważ nie istnieje możliwość częściowej zależności – pojedyncza kolumna nie ma części, od których można by zależeć. Dlatego też 2NF jest często uznawana za "automatycznie spełnioną" w przypadku tabel z sztucznym kluczem numerycznym ID.

W naszym przykładzie, gdy kluczem jest ID_Wypozyczenia, tabela jest automatycznie w 2NF. Zmiana klucza na złożony (Czytelnik, ISBN, DataWyp) jest zabiegiem dydaktycznym, który ujawnia ukryte zależności częściowe. W rzeczywistych projektach, jeśli projektant używa sztucznych kluczy, musi być świadomy, że 2NF jest spełniona automatycznie, ale nie oznacza to, że struktura tabeli jest optymalna – inne problemy, takie jak redundancja, mogą nadal występować.

18/60Warunek wstępny: 1NF

Warunek wstępny: 1NF

2NF wymaga 1NF – bez atomowości nie ma normalizacji.

Sprawdzenie: nasza tabela Wypozyczenia jest w 1NF (wiemy z poprzedniej prezentacji).

Czyli: każda komórka zawiera atomową wartość, brak powtarzających się grup, jest klucz główny.

Warunek wstępny spełniony – możemy normalizować do 2NF.

Bez 1NF nie ma 2NF – to hierarchia, której nie można przeskoczyć. 1NF → 2NF → 3NF → ...
Drabina – najpierw szczebel 1NF, potem 2NF, nie można przeskoczyć

Warunek wstępny 2NF, jakim jest zgodność z 1NF, jest absolutny i niepodlegający negocjacjom. Nie można "przeskoczyć" poziomu 1NF i przejść od razu do 2NF, ponieważ 2NF opiera się na założeniu, że tabela ma już zdefiniowany klucz główny, a wszystkie wartości w komórkach są atomowe. Bez tych fundamentów analiza zależności funkcyjnych, która jest sercem 2NF, nie ma sensu.

Nasza tabela Wypozyczenia spełnia warunek 1NF, co potwierdziliśmy w poprzedniej prezentacji poprzez systematyczną weryfikację każdej z trzech złotych zasad. Atomowość jest zachowana, ponieważ każda komórka zawiera dokładnie jedną wartość. Nie ma powtarzających się grup kolumn. Klucz główny ID_Wypozyczenia jednoznacznie identyfikuje każdy wiersz. Możemy zatem śmiało przejść do analizy 2NF.

Warto w tym miejscu przypomnieć, że zgodność z 1NF nie gwarantuje poprawności projektu. Nasza tabela, mimo zgodności z 1NF, wciąż wykazuje poważne problemy związane z redundancją i anomaliami. To dowodzi, że normalizacja jest procesem wieloetapowym i każda kolejna postać normalna wnosi nową jakość do projektu bazy danych. Bez systematycznego przechodzenia przez wszystkie etapy normalizacji ryzykujemy pozostawienie w strukturze ukrytych problemów.

19/60Identyfikacja zależności częściowych – krok 1

Identyfikacja zależności częściowych – krok 1:

Krok 1: Określamy klucz główny (złożony): (Czytelnik, ISBN, DataWyp).

Krok 2: Dla każdego atrybutu niekluczowego sprawdzamy, od czego zależy:

  • Tytul → zależy od ISBN → ZALEŻNOŚĆ CZĘŚCIOWA
  • Autor → zależy od ISBN → ZALEŻNOŚĆ CZĘŚCIOWA
  • Gatunek → zależy od ISBN → ZALEŻNOŚĆ CZĘŚCIOWA
  • RokWydania → zależy od ISBN → ZALEŻNOŚĆ CZĘŚCIOWA
  • Wydawnictwo → zależy od ISBN → ZALEŻNOŚĆ CZĘŚCIOWA
  • Adres → zależy od Czytelnik → ZALEŻNOŚĆ CZĘŚCIOWA
  • Miasto, KodPocztowy, Telefon, Email → zależą od Czytelnik → ZALEŻNOŚĆ CZĘŚCIOWA
Mamy 10 atrybutów z zależnościami częściowymi – one muszą 'wyprowadzić się' do osobnych tabel.
Tabela z czerwonymi znacznikami przy 10 atrybutach zależnych częściowo

Identyfikacja zależności częściowych w naszej tabeli to kluczowy moment całego procesu normalizacji do 2NF. Po zmianie klucza głównego na złożony (Czytelnik, ISBN, DataWyp) możemy przeanalizować każdy atrybut niebędący kluczem i określić, od której części klucza jest zależny. Wyniki tej analizy są jednoznaczne: dziesięć z trzynastu atrybutów niekluczowych wykazuje zależności częściowe.

Atrybuty książki – Tytul, Autor, Gatunek, RokWydania, Wydawnictwo – wszystkie zależą wyłącznie od ISBN, który jest tylko częścią klucza złożonego. Oznacza to, że te same dane książki będą powielane przy każdym wypożyczeniu danej książki. Jeśli książka "Przedwiośnie" zostanie wypożyczona sto razy, jej tytuł, autor i inne dane pojawią się sto razy w tabeli, co jest oczywistą redundancją.

Podobnie atrybuty czytelnika – Adres, Miasto, KodPocztowy, Telefon, Email – zależą wyłącznie od Czytelnik, który również jest tylko częścią klucza złożonego. Zatem dane osobowe czytelnika będą powielane przy każdym jego wypożyczeniu. Tylko trzy atrybuty – DataZwrotu, Kara i Status – wykazują zależność pełną od całego klucza złożonego, co oznacza, że są specyficzne dla konkretnego zdarzenia wypożyczenia i powinny pozostać w tabeli Wypozyczenia.

20/60Identyfikacja zależności częściowych – krok 2

Atrybuty z zależnościami PEŁNYMI:

  • DataZwrotu → zależy od (Czytelnik, ISBN, DataWyp) → ZALEŻNOŚĆ PEŁNA
  • Kara → zależy od (Czytelnik, ISBN, DataWyp) → ZALEŻNOŚĆ PEŁNA
  • Status → zależy od (Czytelnik, ISBN, DataWyp) → ZALEŻNOŚĆ PEŁNA

Te atrybuty zostają w tabeli Wypozyczenia – są poprawne.

AtrybutZależy odRodzajZostaje?
TytulISBNCzęściowaNIE
AutorISBNCzęściowaNIE
GatunekISBNCzęściowaNIE
AdresCzytelnikCzęściowaNIE
MiastoCzytelnikCzęściowaNIE
DataZwrotu(Czyt., ISBN, Data)PełnaTAK
Kara(Czyt., ISBN, Data)PełnaTAK
Atrybuty z zależnościami pełnymi zostają. Z częściowymi – wyprowadzają się do własnych tabel.
Tabela podsumowująca – zielone 'TAK' dla atrybutów zostających, czerwone 'NIE' dla wyprowadzanych

Krok drugi identyfikacji zależności częściowych potwierdza, które atrybuty powinny pozostać w tabeli Wypozyczenia, a które muszą zostać przeniesione do osobnych tabel. Atrybuty z zależnościami pełnymi – DataZwrotu, Kara i Status – są specyficzne dla konkretnego zdarzenia wypożyczenia i powinny pozostać w tabeli Wypozyczenia. To logiczne, ponieważ data zwrotu, kara i status są bezpośrednio związane z konkretnym wypożyczeniem, a nie z czytelnikiem czy książką jako takimi.

Atrybuty książki (Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) oraz atrybuty czytelnika (Adres, Miasto, KodPocztowy, Telefon, Email) wykazują zależności częściowe i muszą zostać przeniesione do osobnych tabel. W efekcie z jednej tabeli Wypozyczenia powstaną trzy tabele: Czytelnicy (sześć kolumn: ID_Czytelnika, Adres, Miasto, KodPocztowy, Telefon, Email), Ksiazki (sześć kolumn: ID_Ksiazki lub ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) oraz Wypozyczenia (siedem kolumn: ID_Wypozyczenia, ID_Czytelnika, ID_Ksiazki, DataWyp, DataZwrotu, Kara, Status).

Warto zwrócić uwagę, że nowa tabela Wypozyczenia będzie zawierać klucze obce (ID_Czytelnika, ID_Ksiazki) odwołujące się do kluczy głównych tabel Czytelnicy i Ksiazki. Dzięki temu relacje między tabelami są jawnie zdefiniowane na poziomie bazy danych, co umożliwia wykorzystanie mechanizmów integralności referencyjnej, takich jak klucze obce z regułami ON DELETE CASCADE lub ON DELETE RESTRICT.

21/60Konsekwencje zależności częściowych

Konsekwencje zależności częściowych:

  • Redundancja: Tytul powtarza się dla każdego wypożyczenia tej samej książki
  • Anomalia UPDATE: zmiana tytułu książki = aktualizacja wielu wierszy
  • Anomalia INSERT: nie można dodać nowej książki, dopóki nie zostanie wypożyczona
  • Anomalia DELETE: usunięcie ostatniego wypożyczenia = utrata danych książki

To są te same anomalie co w 1NF – bo 1NF ich nie eliminuje!

Zależności częściowe są źródłem trzech anomalii – 2NF eliminuje je wszystkie jednym ruchem.
Cztery ikony problemów – redundancja, UPDATE, INSERT, DELETE – wszystkie związane z zależnościami częściowymi

Konsekwencje zależności częściowych w praktyce systemowej są dokładnie takie same, jak problemy, które pozostały po 1NF. Redundancja danych książki i czytelnika, anomalia INSERT uniemożliwiająca dodanie nowej encji bez zdarzenia, anomalia UPDATE wymagająca modyfikacji wielu wierszy oraz anomalia DELETE powodująca utratę danych przy usunięciu ostatniego wypożyczenia – wszystkie te problemy mają swoje źródło w zależnościach częściowych.

Warto podkreślić, że 2NF nie tylko leczy objawy, ale usuwa przyczynę problemów. Przyczyną jest przechowywanie w jednej tabeli atrybutów, które zależą od różnych części klucza złożonego. Rozwiązaniem jest dekompozycja tabeli na mniejsze, wyspecjalizowane struktury, w których każda tabela przechowuje tylko atrybuty w pełni zależne od swojego klucza głównego.

Po dekompozycji do 2NF wszystkie cztery problemy zostają wyeliminowane. Redundancja znika, ponieważ dane czytelnika i książki są przechowywane raz w osobnych tabelach. Anomalia INSERT jest wyeliminowana, ponieważ czytelnik i książka mogą istnieć w systemie bez wypożyczenia. Anomalia UPDATE znika, ponieważ zmiana adresu czytelnika wymaga modyfikacji jednego wiersza w tabeli Czytelnicy. Anomalia DELETE jest wyeliminowana, ponieważ usunięcie wypożyczenia nie kasuje danych czytelnika ani książki.

22/60Wizualizacja: podział tabeli – koncepcyjnie

Podział tabeli – koncepcyjnie:

Mamy jedną tabelę Wypozyczenia z wieloma kolumnami.

Dzielimy ją na trzy:

  1. Ksiazki – dane o książkach (grupujemy atrybuty zależne od ISBN)
  2. Czytelnicy – dane o czytelnikach (grupujemy atrybuty zależne od Czytelnik)
  3. Wypozyczenia – tylko dane wypożyczeń (atrybuty zależne od całego klucza)

Każda nowa tabela ma JEDEN cel: opisywać jedną encję.

Z jednej wielkiej tabeli powstają trzy mniejsze – każda z jednym, jasno określonym celem.
Jedna duża tabela rozpadająca się na trzy mniejsze – strzałki rozchodzące się od centrum

Wizualizacja podziału tabeli przedstawia koncepcyjne przejście od jednej dużej struktury do trzech wyspecjalizowanych tabel. W tym momencie proces normalizacji nabiera realnych kształtów – przestajemy mówić o abstrakcyjnych regułach, a zaczynamy faktycznie przekształcać strukturę danych. Tabela Ksiazki przejmie wszystkie atrybuty związane z książkami, tabela Czytelnicy dane osobowe czytelników, a tabela Wypozyczenia zachowa tylko informacje specyficzne dla zdarzeń wypożyczenia.

Każda z nowo utworzonych tabel ma jeden, jasno określony cel. Tabela Ksiazki opisuje książki: każdy wiersz to jedna książka z jej metadanymi. Tabela Czytelnicy opisuje czytelników: każdy wiersz to jedna osoba z danymi adresowymi i kontaktowymi. Tabela Wypozyczenia opisuje zdarzenia: każdy wiersz to jedno wypożyczenie z datami i statusem. Dzięki takiemu podziałowi każda tabela jest odpowiedzialna za jeden rodzaj informacji.

Połączenie między tabelami realizowane jest za pomocą kluczy obcych. Tabela Wypozyczenia będzie zawierać ID_Czytelnika (klucz obcy do tabeli Czytelnicy) oraz ISBN lub ID_Ksiazki (klucz obcy do tabeli Ksiazki). Dzięki temu możliwe jest odtworzenie pełnej informacji o wypożyczeniu za pomocą zapytania JOIN łączącego trzy tabele. To rozwiązanie, choć wymaga bardziej złożonych zapytań, zapewnia integralność danych i eliminuje redundancję.

23/60Dekompozycja do 2NF – reguła ogólna

Dekompozycja do 2NF – reguła ogólna

Dekompozycja = rozkład na mniejsze tabele.

Reguła: dla każdej zależności częściowej tworzymy osobną tabelę.

  • Ksiazki – (ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) – klucz: ISBN
  • Czytelnicy – (Czytelnik, Adres, Miasto, KodPocztowy, Telefon, Email) – klucz: Czytelnik
  • Wypozyczenia – (Czytelnik, ISBN, DataWyp, DataZwrotu, Kara, Status) – klucz złożony: (Czytelnik, ISBN, DataWyp)

Klucze obce: Czytelnik → Czytelnicy, ISBN → Ksiazki.

Z jednej tabeli powstały trzy – każda reprezentuje jedną encję: książkę, czytelnika i wypożyczenie.
Trzy karty obok siebie – Ksiazki, Czytelnicy, Wypozyczenia – z kluczami i kluczami obcymi

Reguła dekompozycji do 2NF jest prosta: dla każdej zidentyfikowanej zależności częściowej tworzymy osobną tabelę zawierającą atrybuty objęte tą zależnością oraz klucz, od którego te atrybuty zależą. W naszym przypadku mamy dwie zależności częściowe: od ISBN (dla atrybutów książki) i od Czytelnik (dla atrybutów czytelnika). Tworzymy zatem trzy tabele: Ksiazki z kluczem ISBN, Czytelnicy z kluczem Czytelnik i Wypozyczenia z kluczem złożonym.

W tabeli Ksiazki kluczem głównym jest ISBN, który jest naturalnym kluczem – unikalnym identyfikatorem książki. W tabeli Czytelnicy używamy kolumny Czytelnik jako klucza głównego, zakładając, że nazwy czytelników są unikalne. W praktyce lepiej byłoby zastosować sztuczny klucz ID_Czytelnika, aby uniknąć problemów z duplikacją nazwisk. W tabeli Wypozyczenia kluczem pozostaje (Czytelnik, ISBN, DataWyp), a kolumny Czytelnik i ISBN stają się kluczami obcymi do tabel Czytelnicy i Ksiazki.

Taka struktura zapewnia, że każdy atrybut niebędący kluczem jest w pełni zależny od całego klucza swojej tabeli. W tabeli Ksiazki wszystkie atrybuty zależą od ISBN. W tabeli Czytelnicy wszystkie atrybuty zależą od Czytelnik. W tabeli Wypozyczenia atrybuty DataZwrotu, Kara i Status zależą od pełnego klucza złożonego. Nie ma już zależności częściowych, co oznacza, że wszystkie trzy tabele są w 2NF.

24/60Porównanie: przed i po 2NF
AspektPrzed 2NF (1 tabela)Po 2NF (3 tabele)
Dane książkiPowtórzone przy każdym wypożyczeniuJeden wiersz w Ksiazki
Dane czytelnikaPowtórzone przy każdym wypożyczeniuJeden wiersz w Czytelnicy
Nowa książkaNie można dodać bez wypożyczeniaMożna dodać do Ksiazki
Zmiana adresuUPDATE N wierszyUPDATE 1 wiersza w Czytelnicy
Usunięcie wypożyczeniaUtrata danych książki i czytelnikaTylko kasowanie w Wypozyczenia

Różnica fundamentalna: każdy fakt w jednym miejscu.

Po 2NF każda tabela ma jeden cel – książki o książkach, czytelnicy o czytelnikach, wypożyczenia o wypożyczeniach.
Dwie kolumny – lewa 'Przed' (jedna wielka tabela), prawa 'Po' (trzy małe)

Porównanie stanu przed i po 2NF unaocznia skalę poprawy, jaką wprowadza druga postać normalna. W kolumnie "Przed 2NF" widzimy wszystkie problemy, z którymi borykaliśmy się od początku: dane książki i czytelnika są powtarzane przy każdym wypożyczeniu, dodanie nowej książki bez wypożyczenia jest niemożliwe, zmiana adresu wymaga aktualizacji wielu wierszy, a usunięcie wypożyczenia grozi utratą danych czytelnika i książki. Wszystkie te problemy znikają w kolumnie "Po 2NF".

Kluczową zmianą jest przejście od zasady "jeden fakt w wielu miejscach" do zasady "jeden fakt w jednym miejscu". W nowej strukturze każda informacja o czytelniku przechowywana jest dokładnie raz w tabeli Czytelnicy. Każda informacja o książce przechowywana jest dokładnie raz w tabeli Ksiazki. Tabela Wypozyczenia zawiera tylko informacje specyficzne dla zdarzeń wypożyczenia oraz klucze obce łączące ją z pozostałymi tabelami.

W tabeli porównawczej widać również, że po 2NF możliwe jest dodanie nowej książki do systemu bez konieczności natychmiastowego wypożyczenia – wystarczy wstawić wiersz do tabeli Ksiazki. Podobnie można zarejestrować nowego czytelnika bez wypożyczenia. Zmiana adresu czytelnika wymaga aktualizacji dokładnie jednego wiersza w tabeli Czytelnicy, a usunięcie wypożyczenia nie wpływa na dane czytelnika ani książki, ponieważ są one przechowywane w osobnych tabelach.

25/60Krok 1: nowa tabela Ksiazki

Nowa tabela: Ksiazki

Przenosimy wszystkie atrybuty zależne od ISBN:

  • ISBN (klucz główny), Tytul, Autor, Gatunek, RokWydania, Wydawnictwo

ISBN to naturalny klucz główny – unikalny dla każdej książki.

Przykładowe dane:

ISBNTytulAutorGatunekRokWydaniaWydawnictwo
978-83-123-4567-1PrzedwiośnieStefan ŻeromskiPowieść1924Czytelnik
978-83-123-4567-2LalkaBolesław PrusPowieść1890Czytelnik
Każda książka ma jeden wiersz w tabeli Ksiazki. Tylko jeden. Koniec z powielaniem tytułów.
Tabela Ksiazki z 8 wierszami – każda książka występuje dokładnie raz

Tworzenie tabeli Ksiazki to pierwszy krok w praktycznej dekompozycji do 2NF. Z oryginalnej tabeli Wypozyczenia wyodrębniamy wszystkie kolumny, które zależą funkcyjnie od ISBN: Tytul, Autor, Gatunek, RokWydania, Wydawnictwo. ISBN staje się kluczem głównym nowej tabeli. Każda książka pojawia się w tej tabeli dokładnie raz, niezależnie od tego, ile razy została wypożyczona. Oznacza to koniec redundancji danych książki.

W przykładowych danych tabela Ksiazki będzie zawierać osiem wierszy odpowiadających ośmiu unikalnym książkom z naszego zbioru. Każdy wiersz to kompletny zestaw metadanych dla jednej książki. Dzięki temu zmiana tytułu książki (na przykład z powodu błędu w pisowni) wymaga modyfikacji dokładnie jednego wiersza w tabeli Ksiazki, a nie wszystkich wypożyczeń, w których ta książka występuje.

W praktyce projektowej warto rozważyć użycie sztucznego klucza głównego (ID_Ksiazki) zamiast naturalnego klucza ISBN. ISBN może być długi (13 znaków) i nieporęczny w użyciu jako klucz obcy, a także może ulec zmianie przy wznowieniu książki. Sztuczny klucz INTEGER z AUTO_INCREMENT jest bardziej wydajny przy łączeniu tabel i nie podlega zmianom w czasie.

26/60Krok 2: nowa tabela Czytelnicy

Nowa tabela: Czytelnicy

Przenosimy wszystkie atrybuty zależne od Czytelnik:

  • Czytelnik (klucz główny – na razie zakładamy unikalność nazwy)
  • Adres, Miasto, KodPocztowy, Telefon, Email

Uwaga: w realnym systemie zamiast nazwy użylibyśmy ID_Czytelnika.

Przykładowe dane:

CzytelnikAdresMiastoKodPocztowyTelefonEmail
Jan Kowalskiul. Polna 10Kraków30-001123-456-789jan@example.com
Anna Nowakul. Lipowa 5Warszawa00-002987-654-321anna@example.com
Każdy czytelnik ma jeden wiersz. Zmiana adresu = UPDATE jednego wiersza.
Tabela Czytelnicy z 5 wierszami – każdy czytelnik występuje dokładnie raz

Tworzenie tabeli Czytelnicy to drugi krok dekompozycji, analogiczny do tworzenia tabeli Ksiazki. Wydzielamy wszystkie kolumny zależne od Czytelnik: Adres, Miasto, KodPocztowy, Telefon, Email. W tabeli Czytelnicy każdy czytelnik występuje dokładnie raz, co całkowicie eliminuje redundancję danych osobowych, która wcześniej była głównym problemem naszej struktury.

W przykładowych danych tabela Czytelnicy będzie zawierać pięć wierszy odpowiadających pięciu czytelnikom. Każdy wiersz zawiera wszystkie dane osobowe danego czytelnika zapisane jeden raz. Jeśli Jan Kowalski zmieni adres, wystarczy zaktualizować jeden wiersz w tabeli Czytelnicy, a wszystkie wypożyczenia automatycznie "zobaczą" nowy adres poprzez relację klucza obcego.

Użycie kolumny Czytelnik jako klucza głównego jest rozwiązaniem tymczasowym na potrzeby dydaktyczne. W praktyce nazwy czytelników nie są unikalne – istnieje wielu Janów Kowalskich. Dlatego w rzeczywistym projekcie należy zastosować sztuczny klucz ID_Czytelnika z AUTO_INCREMENT. Dodatkowo, dane kontaktowe, takie jak telefon i email, mogą być przechowywane w osobnych tabelach w dalszych etapach normalizacji (4NF).

27/60Krok 3: nowa tabela Wypozyczenia (oczyszczona)

Nowa tabela Wypozyczenia (oczyszczona)

W oryginalnej tabeli zostawiamy tylko atrybuty zależne od całego klucza:

  • Czytelnik (klucz obcy → Czytelnicy)
  • ISBN (klucz obcy → Ksiazki)
  • DataWyp (część klucza)
  • DataZwrotu, Kara, Status

Klucz główny: (Czytelnik, ISBN, DataWyp). Klucze obce zapewniają integralność referencyjną.

Przykładowe dane:

CzytelnikISBNDataWypDataZwrotuKaraStatus
Jan Kowalski978-83-123-4567-12026-02-012026-02-150.00Zwrócone
Jan Kowalski978-83-123-4567-22026-03-01NULLNULLWypożyczone
Wypozyczenia zawierają tylko informacje o wypożyczeniu – reszta jest w innych tabelach.
Tabela Wypozyczenia po oczyszczeniu – tylko 6 kolumn zamiast 17

Krok trzeci polega na zdefiniowaniu nowej tabeli Wypozyczenia, która po dekompozycji zachowuje tylko atrybuty specyficzne dla zdarzenia wypożyczenia. Z oryginalnych siedemnastu kolumn zostaje sześć: klucz główny ID_Wypozyczenia, dwa klucze obce (ID_Czytelnika i ID_Ksiazki lub ISBN) oraz trzy atrybuty zdarzenia: DataWyp, DataZwrotu, Kara, Status. To radykalne uproszczenie struktury w porównaniu do wyjściowej tabeli.

W nowej strukturze tabela Wypozyczenia nie zawiera już bezpośrednio danych czytelnika ani książki – zamiast tego przechowuje klucze obce odwołujące się do odpowiednich wierszy w tabelach Czytelnicy i Ksiazki. Aby uzyskać pełną informację o wypożyczeniu, konieczne jest połączenie trzech tabel za pomocą zapytania JOIN. To dodatkowy krok w porównaniu do prostej tabeli 1NF, ale korzyści w postaci integralności danych są nieporównywalnie większe.

W przykładowej implementacji tabela Wypozyczenia będzie nadal zawierać dwanaście wierszy (po jednym dla każdego zdarzenia wypożyczenia), ale każdy wiersz będzie znacznie krótszy i będzie zawierać tylko identyfikatory czytelnika i książki zamiast pełnych danych. Dzięki temu zapytania SELECT na tabeli Wypozyczenia będą szybsze, a ryzyko niespójności danych zostanie wyeliminowane.

28/60Dlaczego Czytelnik jako klucz to zły pomysł?

Dlaczego Czytelnik jako klucz to zły pomysł?

Problem: Czytelnik (nazwa) nie jest unikalna – może być dwóch Janów Kowalskich.

Rozwiązanie: dodajemy ID_Czytelnika – sztuczny, numeryczny identyfikator.

Analogia: PESEL jest lepszy niż imię+nazwisko do identyfikacji osoby.

Zmieniamy strukturę Czytelnicy:

  • ID_Czytelnika (INT AUTO_INCREMENT PRIMARY KEY)
  • Imie, Nazwisko, Adres, Miasto, KodPocztowy, Telefon, Email

Zamiast jednej kolumny Czytelnik – dwie: Imie, Nazwisko.

Nazwa to zły klucz – lepiej użyć sztucznego ID. To jak wybór między PESEL-em a pseudonimem.
Dwie osoby o imieniu Jan Kowalski obok siebie – ten sam identyfikator nie działa

W trakcie dekompozycji napotykamy ważny problem praktyczny: użycie nazwy Czytelnik jako klucza głównego w tabeli Czytelnicy jest ryzykowne. Nazwy czytelników nie są unikalne w rzeczywistości – dwóch różnych Janów Kowalskich może być czytelnikami tej samej biblioteki. Ponadto nazwa może ulec zmianie (np. po ślubie), a klucz główny powinien być niezmienny przez cały cykl życia wiersza.

Rozwiązaniem jest zastosowanie sztucznego klucza głównego ID_Czytelnika typu INTEGER z AUTO_INCREMENT. Taki klucz jest unikalny, niezmienny i nie ma znaczenia biznesowego. W tabeli Wypozyczenia zamiast kolumny Czytelnik (tekstowej) używamy ID_Czytelnika (liczbowego) jako klucza obcego. To samo dotyczy książek – zamiast ISBN możemy użyć ID_Ksiazki jako sztucznego klucza głównego.

Stosowanie sztucznych kluczy (ang. surrogate keys) jest powszechnie uznaną dobrą praktyką w projektowaniu baz danych. Sztuczne klucze są wydajniejsze przy indeksowaniu i łączeniu tabel, nie podlegają zmianom biznesowym i nie ujawniają żadnych informacji o danych. W dalszej części kursu będziemy konsekwentnie stosować sztuczne klucze dla wszystkich tabel.

29/60Diagram zależności po 2NF – relacje między tabelami

Trzy tabele połączone kluczami obcymi:

  • Czytelnicy 1---* Wypozyczenia (jeden czytelnik → wiele wypożyczeń)
  • Ksiazki 1---* Wypozyczenia (jedna książka → wiele wypożyczeń)
  • Wypozyczenia *---1 Czytelnicy i *---1 Ksiazki

Klucze obce: FK_Czytelnik, FK_ISBN.

To jest schemat gwiazdy – centralna tabela faktów (Wypozyczenia) połączona z tabelami wymiarów.

Wypozyczenia to 'most' łączący czytelników z książkami. Bez mostu – żadnych wypożyczeń.
Diagram z trzema tabelami – Czytelnicy, Ksiazki, Wypozyczenia – połączone strzałkami

Schemat połączeń między tabelami po 2NF przypomina klasyczny schemat gwiazdy (ang. star schema) używany w hurtowniach danych. W centrum znajduje się tabela faktów Wypozyczenia, a wokół niej tabele wymiarów: Czytelnicy i Ksiazki. Tabela centralna zawiera klucze obce do tabel wymiarów oraz atrybuty numeryczne (daty, kwoty), co jest charakterystyczne dla schematów gwiaździstych.

W naszym przypadku tabela Wypozyczenia to tabela faktów, która rejestruje zdarzenia (wypożyczenia). Tabele Czytelnicy i Ksiazki to tabele wymiarów, które dostarczają kontekstu dla tych zdarzeń. Dzięki takiej strukturze możliwe jest wykonywanie złożonych analiz, na przykład: "pokaż wszystkie wypożyczenia książek z gatunku Powieść w mieście Kraków" – zapytanie łączy trzy tabele, ale jest szybkie i precyzyjne.

Warto zauważyć, że schemat gwiazdy jest zoptymalizowany pod kątem zapytań analitycznych (OLAP), a nie transakcyjnych (OLTP). W systemach transakcyjnych, takich jak system biblioteczny, stosuje się zazwyczaj schematy bardziej znormalizowane (snowflake schema), które są rozwinięciem schematu gwiazdy. Jednak dla celów dydaktycznych schemat gwiazdy doskonale ilustruje zasadę działania relacji między tabelami w znormalizowanej bazie danych.

30/60Weryfikacja 2NF – czy nasze tabele są w 2NF?

Sprawdźmy każdą tabelę:

  • Ksiazki: klucz = ISBN (prosty) → automatycznie 2NF
  • Czytelnicy: klucz = ID_Czytelnika (prosty) → automatycznie 2NF
  • Wypozyczenia: klucz = (ID_Czytelnika, ISBN, DataWyp) złożony
    • DataZwrotu → zależy od całego klucza? TAK → OK
    • Kara → zależy od całego klucza? TAK → OK
    • Status → zależy od całego klucza? TAK → OK
    • Żadnych zależności częściowych → 2NF spełniona

Wniosek: wszystkie trzy tabele są w 2NF.

Wszystkie trzy tabele spełniają 2NF – zależności częściowe zostały wyeliminowane.
Trzy zielone znaczniki kontrolne – każda tabela przechodzi test 2NF

Weryfikacja zgodności z 2NF po dekompozycji potwierdza, że wszystkie trzy tabele spełniają wymagania drugiej postaci normalnej. Dla tabeli Czytelnicy z kluczem ID_Czytelnika: każdy atrybut niebędący kluczem (Adres, Miasto, KodPocztowy, Telefon, Email) jest w pełni zależny od całego klucza głównego, którym jest ID_Czytelnika. Ponieważ klucz jest prosty, 2NF jest spełniona automatycznie.

Dla tabeli Ksiazki z kluczem ID_Ksiazki: analogicznie, wszystkie atrybuty (ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) są w pełni zależne od ID_Ksiazki. Klucz jest prosty, więc 2NF jest spełniona. Dla tabeli Wypozyczenia z kluczem ID_Wypozyczenia: atrybuty DataWyp, DataZwrotu, Kara, Status oraz klucze obce są w pełni zależne od ID_Wypozyczenia. Ponownie, klucz prosty, więc 2NF spełniona.

Warto podkreślić, że weryfikacja 2NF jest znacznie prostsza, gdy używamy sztucznych kluczy głównych. Sztuczny klucz eliminuje możliwość wystąpienia częściowych zależności funkcyjnych, ponieważ jest pojedynczą kolumną i nie ma "części", od których można by zależeć. To kolejny argument przemawiający za stosowaniem sztucznych kluczy w projektowaniu baz danych.

31/60Co 2NF eliminuje? – lista kontrolna

Co 2NF eliminuje? – lista kontrolna:

  • Redundancja danych książki – każda książka w jednym wierszu
  • Redundancja danych czytelnika – każdy czytelnik w jednym wierszu
  • Anomalia INSERT dla książek – można dodać książkę bez wypożyczenia
  • Anomalia INSERT dla czytelników – można dodać czytelnika bez wypożyczenia
  • Anomalia UPDATE dla danych książki – zmiana tytułu = jeden wiersz
  • Anomalia UPDATE dla danych czytelnika – zmiana adresu = jeden wiersz
  • Anomalia DELETE dla książek – usunięcie wypożyczenia nie kasuje książki
  • Anomalia DELETE dla czytelników – usunięcie wypożyczenia nie kasuje czytelnika
2NF eliminuje 8 konkretnych problemów – to ogromny postęp w stosunku do 1NF.
Lista z 8 odhaczonymi pozycjami – wszystkie problemy wyeliminowane

Podsumowując zalety 2NF, warto wymienić osiem konkretnych problemów, które zostały wyeliminowane przez dekompozycję. Redundancja danych książki i czytelnika całkowicie znika, ponieważ każda encja ma swoją własną tabelę. Anomalia INSERT dla książek i czytelników jest wyeliminowana – nową książkę można dodać do tabeli Ksiazki bez konieczności wypożyczenia. Anomalia UPDATE dla danych książki i czytelnika znika, ponieważ modyfikacja wymaga zmiany tylko jednego wiersza.

Anomalia DELETE dla książek i czytelników również zostaje wyeliminowana – usunięcie wypożyczenia nie kasuje danych książki ani czytelnika, ponieważ są one przechowywane w osobnych tabelach. Dodatkowo, zapytania SQL stają się bardziej precyzyjne, mimo że wymagają teraz łączenia tabel za pomocą JOIN. Operacje CRUD na każdej z trzech tabel są prostsze i bardziej bezpieczne niż na jednej, dużej tabeli 1NF.

Ta lista ośmiu wyeliminowanych problemów pokazuje, jak radykalną poprawę jakości bazy danych przynosi 2NF. W porównaniu z 1NF, która rozwiązała tylko trzy problemy strukturalne, 2NF eliminuje problemy związane z relacjami między encjami, co stanowi znaczący postęp w procesie normalizacji. W następnej części zobaczymy implementację nowej struktury w MariaDB.

32/60Ćwiczenie: zidentyfikuj zależności częściowe

Ćwiczenie: zidentyfikuj zależności częściowe

Tabela Oceny: (ID_Studenta, ID_Przedmiotu, Data, Ocena, NazwaPrzedmiotu, Prowadzacy)

Klucz złożony: (ID_Studenta, ID_Przedmiotu, Data)

Pytania:

  • Czy NazwaPrzedmiotu ma zależność częściową? TAK – zależy od ID_Przedmiotu
  • Czy Prowadzacy ma zależność częściową? TAK – zależy od ID_Przedmiotu
  • Czy Ocena ma zależność częściową? NIE – zależy od całego klucza

Rozwiązanie: wydzielić tabele Przedmioty (ID_Przedmiotu, Nazwa, Prowadzacy).

Spróbuj samodzielnie – to jak szukanie 'ukrytych' zależności. ID_Przedmiotu wyznacza Nazwę.
Tabela Oceny z zaznaczonymi zależnościami – strzałki od ID_Przedmiotu

Ćwiczenia praktyczne na tym etapie mają na celu sprawdzenie, czy potrafisz samodzielnie zastosować reguły 2NF do analizy tabel. W każdym ćwiczeniu otrzymujesz strukturę tabeli wraz z danymi i musisz określić, czy tabela jest w 2NF, zidentyfikować ewentualne zależności częściowe oraz zaproponować dekompozycję. To umiejętności, które będą Ci potrzebne w codziennej pracy z bazami danych.

Podczas rozwiązywania ćwiczeń pamiętaj o dwuetapowej procedurze. Po pierwsze, sprawdź, czy tabela jest w 1NF – jeśli nie, najpierw doprowadź ją do 1NF. Po drugie, określ klucz główny – jeśli jest prosty (jednokolumnowy), tabela jest automatycznie w 2NF. Jeśli klucz jest złożony, przeanalizuj każdy atrybut niebędący kluczem i sprawdź, czy zależy od całego klucza, czy tylko od jego części.

Zachęcam do samodzielnego przeanalizowania każdego ćwiczenia przed sprawdzeniem odpowiedzi. Proces myślowy prowadzący do rozwiązania jest równie ważny, jak samo rozwiązanie. Jeśli popełnisz błąd, przeanalizuj, gdzie leży przyczyna – to pomoże Ci uniknąć podobnych błędów w przyszłości. Pamiętaj, że normalizacja to umiejętność, którą rozwija się poprzez praktykę i analizę wielu różnych przykładów.

33/60Ćwiczenie 2: zaprojektuj dekompozycję

Ćwiczenie 2: zaprojektuj dekompozycję

Tabela Zamowienia: (ID_Zamowienia, Klient, Adres, Produkt, Cena, Ilosc, Data)

Klucz główny: ID_Zamowienia (prosty) → 2NF spełniona automatycznie.

ALE: co jeśli Klient → Adres? To zależność przechodnia (3NF), nie częściowa.

Zmiana: klucz złożony (ID_Zamowienia, Produkt).

  • Czy Klient zależy od części klucza? TAK – zależy od ID_Zamowienia, które jest częścią klucza (ID_Zamowienia, Produkt)
  • Czy Adres zależy od części klucza? TAK – podobnie, Adres zależy od ID_Zamowienia
Nie każda tabela wymaga 2NF – czasem klucz prosty oznacza, że 2NF jest automatyczna.
Tabela Zamowienia z kluczem prostym – automatyczna 2NF

Drugie ćwiczenie może zaskoczyć Cię swoim wynikiem – pokazuje ono, że 2NF nie zawsze jest potrzebna i nie każda tabela wymaga dekompozycji. Jeśli tabela ma klucz prosty (jednokolumnowy), to automatycznie spełnia warunek 2NF, ponieważ nie istnieje możliwość częściowej zależności funkcyjnej. W takim przypadku tabela jest w 2NF bez konieczności jakichkolwiek modyfikacji.

Warto zapamiętać tę lekcję: normalizacja nie polega na bezmyślnym stosowaniu reguł, ale na świadomej analizie struktury tabel i podejmowaniu decyzji o potrzebie dekompozycji. Jeśli tabela z kluczem prostym nie wykazuje innych problemów (takich jak redundancja czy anomalie), może pozostać w 2NF bez dalszych modyfikacji. Dodatkowe postaci normalne (3NF, BCNF) mogą być potrzebne, ale nie są wymagane przez samą 2NF.

To ćwiczenie uczy również, że w praktyce projektowej wiele tabel pozostaje w 2NF przez cały cykl życia systemu. Nie każda tabela musi być doprowadzona do 3NF czy BCNF – decyzja o dalszej normalizacji zależy od konkretnych wymagań biznesowych i analizy występujących zależności funkcyjnych. Celem normalizacji jest eliminacja problemów, a nie osiągnięcie jak najwyższej postaci normalnej za wszelką cenę.

34/60Ćwiczenie 3: rzeczywista tabela z naruszeniem 2NF

Ćwiczenie 3: rzeczywista tabela z naruszeniem 2NF

Tabela Dostawy: (ID_Dostawy, ID_Produktu, ID_Dostawcy, Data, NazwaProduktu, CenaProduktu, NazwaDostawcy, AdresDostawcy)

Klucz główny: (ID_Dostawy, ID_Produktu)

Zależności:

  • ID_Produktu → NazwaProduktu, CenaProduktu – zależność częściowa (ID_Produktu jest częścią klucza). Do usunięcia w 2NF.
  • ID_Dostawcy → NazwaDostawcy, AdresDostawcy – nie jest częścią klucza, więc to nie jest zależność częściowa. To zależność od nie-klucza do usunięcia w 3NF/BCNF.

Rozwiązanie: tabele Produkty i Dostawcy.

To jest częsty przypadek w realnych systemach – wiele zależności w jednej tabeli.
Tabela Dostawy ze strzałkami – ID_Produktu→NazwaProduktu, ID_Dostawcy→NazwaDostawcy

Ćwiczenie z rzeczywistymi danymi pokazuje, że problemy, które analizujemy na przykładzie bibliotecznym, występują powszechnie w systemach produkcyjnych. Tabele z wieloma kolumnami, mieszające różne encje, są na porządku dziennym w systemach rozwijanych przez wiele lat bez przestrzegania reguł normalizacji. Umiejętność analizy i refaktoryzacji takich tabel jest niezwykle cenna na rynku pracy.

W rzeczywistym projekcie możesz spotkać się z tabelami, które mają kilkadziesiąt, a nawet kilkaset kolumn. Analiza zależności funkcyjnych w tak dużych tabelach jest trudniejsza, ale zasady pozostają te same. Warto zacząć od zidentyfikowania naturalnych kluczy i grup powiązanych atrybutów, a następnie stopniowo wydzielać je do osobnych tabel. Proces ten, zwany refaktoryzacją schematu (ang. schema refactoring), jest standardową praktyką w utrzymaniu systemów bazodanowych.

Pamiętaj, że refaktoryzacja istniejącej bazy danych jest znacznie trudniejsza niż zaprojektowanie jej od początku zgodnie z regułami normalizacji. Dlatego tak ważne jest, aby od samego początku projektu stosować dobre praktyki projektowe. Koszt wprowadzenia poprawek na etapie projektowania jest wielokrotnie niższy niż koszt refaktoryzacji produkcyjnej bazy danych obsługującej tysiące użytkowników.

35/60Podsumowanie części III: co zrobiliśmy?

Podsumowanie części III: co zrobiliśmy?

  • Zidentyfikowaliśmy zależności częściowe w tabeli Wypozyczenia
  • Podzieliliśmy jedną tabelę na trzy:
    • Ksiazki – dane bibliograficzne (ISBN jako klucz)
    • Czytelnicy – dane osobowe (ID_Czytelnika jako klucz)
    • Wypozyczenia – zdarzenia wypożyczeń (klucz złożony)
  • Każda tabela jest w 2NF
  • Usunęliśmy redundancję danych książki i czytelnika
Podsumowując: jedna tabela → trzy. Każda ma jeden cel. To jest 2NF.
Animacja – jedna tabela rozpadająca się na trzy, każda z etykietą

Implementacja w MariaDB nowej struktury tabel to moment, w którym 2NF z abstrakcyjnej koncepcji staje się rzeczywistością. Korzystając z polecenia CREATE TABLE, definiujemy trzy tabele zgodne z 2NF: Czytelnicy, Ksiazki i Wypozyczenia. Każda tabela ma jasno określony klucz główny i zestaw atrybutów w pełni od niego zależnych. Tabele są połączone kluczami obcymi, co zapewnia integralność referencyjną.

Tworząc tabelę Czytelnicy, definiujemy ID_Czytelnika jako INT AUTO_INCREMENT PRIMARY KEY oraz kolumny dla danych osobowych. W tabeli Ksiazki używamy ID_Ksiazki jako sztucznego klucza, a ISBN przechowujemy jako osobny atrybut UNIQUE. W tabeli Wypozyczenia definiujemy ID_Wypozyczenia jako klucz główny oraz dwa klucze obce: ID_Czytelnika REFERENCES Czytelnicy(ID_Czytelnika) i ID_Ksiazki REFERENCES Ksiazki(ID_Ksiazki).

W praktyce warto dodać również indeksy na kluczach obcych, aby przyspieszyć zapytania JOIN, które będą teraz niezbędne do odczytu pełnych danych o wypożyczeniach. Indeksy na kolumnach ID_Czytelnika i ID_Ksiazki w tabeli Wypozyczenia znacząco poprawią wydajność zapytań łączących trzy tabele. W MariaDB indeksy na kluczach obcych są często zakładane automatycznie, ale warto to zweryfikować.

36/60Krok 1: tworzenie tabeli Czytelnicy

Tworzenie tabeli Czytelnicy

AUTO_INCREMENT – automatyczne nadawanie kolejnych numerów (1, 2, 3...).

UNIQUE na Email – zapewnia, że dwa razy nie wpiszemy tego samego emaila.

-- Tabela Czytelnicy – dane osobowe oddzielone od wypożyczeń
CREATE TABLE Czytelnicy (
    ID_Czytelnika  INT AUTO_INCREMENT PRIMARY KEY,
    Imie          VARCHAR(50) NOT NULL,
    Nazwisko      VARCHAR(50) NOT NULL,
    Adres         VARCHAR(200),
    Miasto        VARCHAR(50),
    KodPocztowy   VARCHAR(10),
    Telefon       VARCHAR(20),
    Email         VARCHAR(100) UNIQUE,
    DataRejestracji DATE
);

Omówienie: dlaczego ID_Czytelnika zamiast Nazwisko? (jednoznaczność, unikalność).

Zamiast powtarzać dane czytelnika przy każdym wypożyczeniu – przechowujemy je raz, w osobnej tabeli.
Zrzut ekranu konsoli MariaDB – CREATE TABLE Czytelnicy

Instrukcja CREATE TABLE dla tabeli Czytelnicy zawiera kilka ważnych elementów składni SQL, które warto omówić. Klauzula AUTO_INCREMENT przy kolumnie ID_Czytelnika informuje MariaDB, aby automatycznie nadawała kolejne wartości liczbowe przy każdym nowym wierszu. Dzięki temu programista nie musi ręcznie obliczać następnego dostępnego ID – system robi to samodzielnie, gwarantując unikalność.

Klauzula PRIMARY KEY (ID_Czytelnika) definiuje klucz główny tabeli. MariaDB automatycznie tworzy indeks na kolumnie klucza głównego, co przyspiesza wyszukiwanie i łączenie tabel. Dodatkowo klucz główny wymusza unikalność wartości – nie można wstawić dwóch wierszy z tym samym ID_Czytelnika. Wszystkie pozostałe kolumny (Adres, Miasto, KodPocztowy, Telefon, Email) są zdefiniowane jako VARCHAR z odpowiednimi długościami.

Warto również rozważyć dodanie ograniczenia NOT NULL dla kolumn obowiązkowych oraz DEFAULT dla kolumn z wartościami domyślnymi. Na przykład kolumna Email mogłaby mieć wartość domyślną jako pusty tekst, jeśli podanie emaila nie jest obowiązkowe. Jednak w przypadku danych osobowych lepiej wymusić podanie wszystkich danych kontaktowych, aby zapewnić kompletność informacji w systemie bibliotecznym.

37/60Omówienie: AUTO_INCREMENT i UNIQUE

AUTO_INCREMENT – MariaDB sama nadaje kolejne numery:

  • ID_Czytelnika = 1 dla Jana Kowalskiego
  • ID_Czytelnika = 2 dla Anny Nowak
  • Nigdy się nie powtórzą, nawet jeśli usuniesz wiersz

UNIQUE – gwarantuje, że wartość w kolumnie jest unikalna:

  • Dwa razy ten sam email? MariaDB zgłosi błąd
  • To zapobiega duplikacjom
AUTO_INCREMENT to 'automatyczny numerator' – MariaDB robi to za ciebie. UNIQUE to 'strażnik unikalności'.
Dwa bloki – AUTO_INCREMENT z rosnącymi numerami, UNIQUE z przekreślonym duplikatem

Klauzura FOREIGN KEY to jeden z najważniejszych mechanizmów zapewniających integralność referencyjną w relacyjnych bazach danych. Definiuje ona, że wartości w kolumnie ID_Czytelnika w tabeli Wypozyczenia muszą odpowiadać wartościom w kolumnie ID_Czytelnika w tabeli Czytelnicy. Dzięki temu nie można wprowadzić wypożyczenia dla nieistniejącego czytelnika, ani usunąć czytelnika, który ma powiązane wypożyczenia.

W MariaDB klucz obcy może być zdefiniowany z różnymi regułami dla operacji UPDATE i DELETE. Reguła ON DELETE CASCADE automatycznie usuwa wszystkie wypożyczenia czytelnika przy usunięciu czytelnika. Reguła ON DELETE RESTRICT uniemożliwia usunięcie czytelnika, jeśli ma on jakiekolwiek wypożyczenia. Reguła ON DELETE SET NULL ustawia wartość klucza obcego na NULL przy usunięciu czytelnika. Wybór odpowiedniej reguły zależy od wymagań biznesowych.

Indeksy na kolumnach kluczy obcych są równie ważne. Bez indeksu każde zapytanie JOIN łączące tabele Wypozyczenia z Czytelnicy wymagałoby sekwencyjnego przeszukania całej tabeli Wypozyczenia, co przy dużej liczbie wierszy byłoby bardzo wolne. Indeks na kolumnie ID_Czytelnika pozwala MariaDB szybko znaleźć wszystkie wypożyczenia danego czytelnika bez przeszukiwania wszystkich wierszy.

38/60Krok 2: tworzenie tabeli Ksiazki

Tworzenie tabeli Ksiazki

ISBN jako klucz główny – naturalny identyfikator (każda książka ma unikalny ISBN).

DEFAULT 1 – domyślna wartość, jeśli nie podasz żadnej.

-- Tabela Ksiazki – dane bibliograficzne oddzielone od wypożyczeń
CREATE TABLE Ksiazki (
    ISBN           VARCHAR(20) PRIMARY KEY,
    Tytul         VARCHAR(200) NOT NULL,
    Autor         VARCHAR(100),
    Gatunek       VARCHAR(50),
    RokWydania    SMALLINT,
    Wydawnictwo   VARCHAR(100),
    LiczbaEgzemplarzy INT DEFAULT 1
);

SMALLINT – typ liczbowy dla roku (zamiast YEAR, który obsługuje tylko lata 1901–2155).

ISBN to unikalny identyfikator książki – idealny naturalny klucz główny, bo każda książka ma inny ISBN.
Zrzut ekranu konsoli MariaDB – CREATE TABLE Ksiazki

Tworzenie tabeli Ksiazki w MariaDB ilustruje różne podejścia do definiowania kluczy głównych. W przykładzie na slajdzie użyto ISBN jako naturalnego klucza głównego, ponieważ ISBN jest unikalny dla każdej książki. Nie stosujemy AUTO_INCREMENT, ponieważ wartości ISBN są z góry określone przez standard i nie mogą być automatycznie generowane przez bazę danych.

W praktyce projektowej często stosuje się rozwiązanie hybrydowe: sztuczny klucz ID_Ksiazki jako PRIMARY KEY z AUTO_INCREMENT oraz ISBN jako osobna kolumna z ograniczeniem UNIQUE. Taka struktura łączy zalety obu podejść: wydajny numeryczny klucz główny do łączenia tabel oraz naturalny identyfikator ISBN do celów biznesowych i zgodności z zewnętrznymi systemami.

Ograniczenie UNIQUE na kolumnie ISBN zapewnia, że nie będą mogły istnieć dwa wiersze z tym samym ISBN, co chroni przed przypadkowym wprowadzeniem duplikatów książek. Jednocześnie klucz obcy w tabeli Wypozyczenia może odwoływać się do ID_Ksiazki, co jest wydajniejsze niż odwoływanie się do długiego, tekstowego ISBN.

39/60Krok 3: tworzenie tabeli Wypozyczenia (już w 2NF)

Tworzenie tabeli Wypozyczenia (w 2NF)

Klucz prosty (ID_Wypozyczenia) – 2NF spełniona automatycznie.

Klucze obce (FOREIGN KEY) łączą z tabelami Czytelnicy i Ksiazki.

-- Tabela Wypozyczenia – już tylko zależności pełne od klucza
CREATE TABLE Wypozyczenia (
    ID_Wypozyczenia INT AUTO_INCREMENT PRIMARY KEY,
    ID_Czytelnika   INT NOT NULL,
    ISBN            VARCHAR(20) NOT NULL,
    DataWyp         DATE NOT NULL,
    DataZwrotu      DATE,
    Kara            DECIMAL(10,2) DEFAULT 0.00,
    Status          VARCHAR(20) DEFAULT 'Wypożyczone',
    CONSTRAINT fk_czytelnik
        FOREIGN KEY (ID_Czytelnika) REFERENCES Czytelnicy(ID_Czytelnika),
    CONSTRAINT fk_isbn
        FOREIGN KEY (ISBN) REFERENCES Ksiazki(ISBN)
);

CONSTRAINT nadaje nazwę więzowi – łatwiej go później usunąć.

W 2NF wszystkie atrybuty niekluczowe zależą od CAŁEGO klucza – nie ma już zależności częściowych.
Zrzut ekranu konsoli MariaDB – CREATE TABLE Wypozyczenia z kluczami obcymi

Ostateczna struktura tabeli Wypozyczenia po 2NF składa się z sześciu kolumn: ID_Wypozyczenia jako klucz główny, ID_Czytelnika i ID_Ksiazki jako klucze obce, oraz DataWyp, DataZwrotu, Kara i Status jako atrybuty specyficzne dla zdarzenia wypożyczenia. W porównaniu z wyjściową tabelą z siedemnastoma kolumnami, jest to znaczące uproszczenie, które eliminuje redundancję i anomalie.

W tabeli Wypozyczenia po 2NF każdy wiersz reprezentuje jedno konkretne zdarzenie wypożyczenia. Aby uzyskać pełne dane o wypożyczeniu, włączając dane czytelnika i książki, należy użyć zapytania SELECT z JOIN łączącego trzy tabele. Przykładowe zapytanie: SELECT Czytelnicy.Imie, Czytelnicy.Nazwisko, Ksiazki.Tytul, Wypozyczenia.DataWyp FROM Wypozyczenia JOIN Czytelnicy ON Wypozyczenia.ID_Czytelnika = Czytelnicy.ID_Czytelnika JOIN Ksiazki ON Wypozyczenia.ID_Ksiazki = Ksiazki.ID_Ksiazki.

Mimo że zapytania stają się bardziej złożone, korzyści w postaci integralności danych i łatwości utrzymania są nieporównywalne. W systemach produkcyjnych z dużymi wolumenami danych, dobrze zindeksowane zapytania JOIN są często szybsze niż zapytania na jednej, dużej tabeli z redundancją, ponieważ muszą przetworzyć mniej danych.

40/60Co to jest klucz obcy (FOREIGN KEY)?

Klucz obcy (FOREIGN KEY)

Klucz obcy (FK) – kolumna w jednej tabeli, która wskazuje na klucz główny w innej.

Przykład: Wypozyczenia.ID_Czytelnika → Czytelnicy.ID_Czytelnika

Co daje klucz obcy?

  • Nie można dodać wypożyczenia dla nieistniejącego czytelnika
  • Nie można usunąć czytelnika, który ma wypożyczenia
  • Gwarantuje spójność danych (integralność referencyjną)

Analogia: w Excelu nie masz takiego zabezpieczenia.

Klucz obcy to jak lista gości na imprezie – nie wejdziesz bez zaproszenia. Baza pilnuje, żebyś nie odwoływał się do nieistniejących danych.
Dwie tabele połączone strzałką – Wypozyczenia.ID_Czytelnika → Czytelnicy.ID_Czytelnika

Klucz obcy (FOREIGN KEY) to jeden z fundamentów relacyjnego modelu danych, który zapewnia integralność referencyjną między powiązanymi tabelami. W naszej strukturze klucz obcy ID_Czytelnika w tabeli Wypozyczenia gwarantuje, że każdy wpis w tabeli Wypozyczenia odnosi się do istniejącego czytelnika w tabeli Czytelnicy. Bez tego mechanizmu moglibyśmy przypadkowo wprowadzić wypożyczenie dla nieistniejącego czytelnika.

W MariaDB klucz obcy definiuje się za pomocą składni: FOREIGN KEY (ID_Czytelnika) REFERENCES Czytelnicy(ID_Czytelnika). Dodatkowo można określić reguły dla operacji UPDATE i DELETE: ON DELETE CASCADE (automatyczne usunięcie wypożyczeń przy usunięciu czytelnika), ON DELETE RESTRICT (blokada usunięcia czytelnika, który ma wypożyczenia) lub ON DELETE SET NULL (ustawienie ID_Czytelnika na NULL przy usunięciu czytelnika).

Wybór odpowiedniej reguły zależy od wymagań biznesowych systemu. W systemie bibliotecznym najbezpieczniejszym rozwiązaniem jest ON DELETE RESTRICT, które uniemożliwia usunięcie czytelnika, dopóki ma on jakiekolwiek aktywne wypożyczenia. Dzięki temu pracownik biblioteki musi najpierw zakończyć wszystkie wypożyczenia czytelnika, zanim będzie mógł usunąć jego dane z systemu, co zapobiega przypadkowej utracie danych.

41/60Krok 4: migracja danych z 1NF do 2NF

Migracja danych z 1NF do 2NF

Wstawiamy unikalnych czytelników – każdy występuje dokładnie raz.

-- Wstawienie danych do Czytelnicy (unikalni czytelnicy z tabeli źródłowej)
INSERT INTO Czytelnicy (Imie, Nazwisko, Adres, Miasto, KodPocztowy, Telefon, Email)
VALUES
    ('Jan', 'Kowalski', 'ul. Polna 10', 'Kraków', '30-001', '123-456-789', 'jan@example.com'),
    ('Anna', 'Nowak', 'ul. Lipowa 5', 'Warszawa', '00-002', '987-654-321', 'anna@example.com'),
    ('Piotr', 'Wiśniewski', 'ul. Morska 12', 'Gdańsk', '80-003', '555-111-222', 'piotr@example.com');

SELECT DISTINCT – wybiera unikalne wartości (bez duplikatów).

Migracja danych to często najtrudniejszy etap – w realnych projektach zajmuje więcej czasu niż projektowanie tabel.
Strzałka od starej tabeli 1NF do nowej tabeli Czytelnicy – dane przepływają

Migracja danych z jednej tabeli do trzech to kluczowy etap praktycznej implementacji normalizacji. W realnym systemie proces ten wymaga starannego zaplanowania i przetestowania, ponieważ błędy podczas migracji mogą prowadzić do utraty danych. Standardowa procedura obejmuje utworzenie nowej struktury tabel, przeniesienie danych za pomocą zapytań INSERT SELECT, a następnie usunięcie starej tabeli po potwierdzeniu poprawności danych.

Przeniesienie danych książek do tabeli Ksiazki wymaga zapytania INSERT INTO Ksiazki (ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo) SELECT DISTINCT ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo FROM Wypozyczenia_stara. Klauzula DISTINCT zapewnia, że każda książka zostanie przeniesiona tylko raz, nawet jeśli w starej tabeli występuje wielokrotnie. Analogiczne zapytanie przenosi dane czytelników do tabeli Czytelnicy.

Po przeniesieniu książek i czytelników, dane wypożyczeń są przenoszone z użyciem kluczy obcych. Zamiast oryginalnych wartości tekstowych (Czytelnik, ISBN), wstawiamy odpowiednie ID_Czytelnika i ID_Ksiazki pobrane z nowych tabel. To wymaga zapytania z JOIN między starą tabelą a nowymi tabelami słownikowymi. Po pomyślnej migracji i weryfikacji danych, stara tabela może być usunięta, a nowa struktura staje się produkcyjna.

42/60Krok 5: wstawianie danych do Ksiazki

Wstawianie danych do Ksiazki

Każda książka występuje dokładnie raz – koniec redundancji.

-- Wstawienie unikalnych książek z tabeli źródłowej
INSERT INTO Ksiazki (ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo)
VALUES
    ('978-83-123-4567-1', 'Przedwiośnie', 'Stefan Żeromski', 'Powieść', 1924, 'Czytelnik'),
    ('978-83-123-4567-2', 'Lalka', 'Bolesław Prus', 'Powieść', 1890, 'Czytelnik'),
    ('978-83-123-4567-3', 'Pan Tadeusz', 'Adam Mickiewicz', 'Poemat', 1834, 'Czytelnik');

SELECT DISTINCT na ISBN – każda książka raz.

Każda książka dostaje jeden wiersz – nawet jeśli była wypożyczona 100 razy.
Strzałka – dane książek przepływają do tabeli Ksiazki, każda książka raz

Podczas migracji danych do tabeli Ksiazki kluczowe jest użycie klauzuli DISTINCT, która zapewnia, że każda książka zostanie przeniesiona tylko raz, nawet jeśli w źródłowej tabeli Wypozyczenia występuje w wielu wierszach. Bez DISTINCT moglibyśmy otrzymać duplikaty książek w tabeli Ksiazki, co naruszyłoby integralność klucza głównego i uniemożliwiłoby utworzenie indeksu unikalnego na ISBN.

W praktyce migracja danych z istniejącego systemu do nowej, znormalizowanej struktury jest jednym z najtrudniejszych zadań w procesie refaktoryzacji bazy danych. Wymaga nie tylko znajomości SQL, ale także dogłębnego zrozumienia danych i potencjalnych problemów, takich jak niespójności w danych źródłowych (na przykład ten sam ISBN z różnymi tytułami w różnych wierszach). W takich przypadkach konieczne jest podjęcie decyzji biznesowej, która wersja danych jest poprawna.

Aby zminimalizować ryzyko błędów podczas migracji, zaleca się wykonanie najpierw zapytań SELECT sprawdzających, a dopiero potem właściwych zapytań INSERT. Należy również wykonać kopię zapasową bazy danych przed rozpoczęciem migracji oraz zweryfikować liczbę przeniesionych wierszy po zakończeniu operacji. W systemach produkcyjnych migrację często przeprowadza się w oknie konserwacyjnym, gdy system jest niedostępny dla użytkowników.

43/60Krok 6: wstawianie danych do Wypozyczenia

Wstawianie danych do Wypozyczenia

ID_Czytelnika i ISBN muszą istnieć w swoich tabelach – inaczej błąd klucza obcego.

-- Wstawienie wypożyczeń z odniesieniami do Czytelnicy i Ksiazki
INSERT INTO Wypozyczenia (ID_Czytelnika, ISBN, DataWyp, DataZwrotu, Kara, Status)
VALUES
    (1, '978-83-123-4567-1', '2026-02-01', '2026-02-15', 0.00, 'Zwrócone'),
    (1, '978-83-123-4567-2', '2026-03-01', NULL, NULL, 'Wypożyczone');

JOIN łączy dane z dwóch tabel.

W 2NF musisz myśleć 'relacyjnie' – wypożyczenie łączy istniejącego czytelnika z istniejącą książką.
Trzy tabele – strzałki od Wypozyczenia do Czytelnicy i Ksiazki

Po zakończeniu migracji i dodaniu ograniczeń kluczy obcych, system automatycznie egzekwuje integralność referencyjną. Oznacza to, że nie można wprowadzić wypożyczenia dla czytelnika, który nie istnieje w tabeli Czytelnicy, ani dla książki, która nie istnieje w tabeli Ksiazki. To radykalna poprawa w porównaniu ze starą strukturą, gdzie każdy programista musiał ręcznie pilnować spójności danych.

Klucze obce chronią również przed usunięciem danych, które są nadal potrzebne. Jeśli pracownik biblioteki spróbuje usunąć czytelnika, który ma aktywne wypożyczenia, MariaDB zgłosi błąd naruszenia integralności referencyjnej i zablokuje operację. Dzięki temu dane pozostają spójne, nawet jeśli użytkownik systemu nie jest świadomy powiązań między tabelami.

Warto pamiętać, że klucze obce mają również wpływ na wydajność. Każde wstawienie, aktualizacja lub usunięcie wiersza w tabeli z kluczami obcymi wymaga sprawdzenia integralności referencyjnej w powiązanych tabelach, co może spowolnić operacje DML na dużych zbiorach danych. W systemach o bardzo wysokiej wydajności operacji zapisu czasami rezygnuje się z kluczy obcych na rzecz ręcznego egzekwowania integralności w warstwie aplikacji.

44/60Krok 7: weryfikacja – zapytania po normalizacji

Weryfikacja – zapytania po normalizacji

Teraz potrzebujemy JOIN (łączenie tabel) – w 1NF wystarczyła jedna tabela.

-- Znajdź wszystkie wypożyczenia Jana Kowalskiego (teraz z JOIN)
SELECT k.Tytul, w.DataWyp, w.Status
FROM Wypozyczenia w
JOIN Czytelnicy c ON w.ID_Czytelnika = c.ID_Czytelnika
JOIN Ksiazki k ON w.ISBN = k.ISBN
WHERE c.Imie = 'Jan' AND c.Nazwisko = 'Kowalski';

Cena za normalizację: bardziej złożone zapytania. Zysk: dane są spójne i bez redundancji.

JOIN-y to cena, którą płacimy za brak redundancji – ale SQL radzi sobie z nimi doskonale.
Schemat – trzy tabele połączone JOIN-ami – dane płyną przez połączenia

Zapytania SELECT po normalizacji do 2NF wymagają użycia JOIN w celu połączenia danych z trzech tabel: Czytelnicy, Ksiazki i Wypozyczenia. JOIN to operacja łączenia wierszy z dwóch lub więcej tabel na podstawie powiązanych kolumn. W naszym przypadku łączymy tabele na podstawie kluczy obcych: Wypozyczenia.ID_Czytelnika = Czytelnicy.ID_Czytelnika oraz Wypozyczenia.ID_Ksiazki = Ksiazki.ID_Ksiazki.

Przykładowe zapytanie: SELECT Czytelnicy.Imie, Czytelnicy.Nazwisko, Ksiazki.Tytul, Wypozyczenia.DataWyp FROM Wypozyczenia JOIN Czytelnicy ON Wypozyczenia.ID_Czytelnika = Czytelnicy.ID_Czytelnika JOIN Ksiazki ON Wypozyczenia.ID_Ksiazki = Ksiazki.ID_Ksiazki WHERE Wypozyczenia.Status = 'Wypozyczone'. To zapytanie zwraca listę wszystkich aktualnie wypożyczonych książek wraz z danymi czytelników.

W MariaDB istnieją różne typy JOIN: INNER JOIN (zwraca tylko wiersze z pasującymi wartościami w obu tabelach), LEFT JOIN (zwraca wszystkie wiersze z lewej tabeli i pasujące z prawej), RIGHT JOIN (odwrotność LEFT JOIN). W naszym przypadku INNER JOIN jest wystarczające, ponieważ każde wypożyczenie ma przypisanego czytelnika i książkę. Jednak jeśli chcielibyśmy wyświetlić listę wszystkich czytelników wraz z ich wypożyczeniami, użyjemy LEFT JOIN, aby uwzględnić czytelników bez wypożyczeń.

45/60Test anomalii UPDATE po 2NF

Test anomalii UPDATE po 2NF

Porównanie: przed 2NF (aktualizacja N wierszy) vs po 2NF (aktualizacja 1 wiersza).

-- Przed 2NF: zmiana adresu wymagała UPDATE wielu wierszy
-- Po 2NF: zmiana adresu to UPDATE jednego wiersza w Czytelnicy
UPDATE Czytelnicy
SET Adres = 'ul. Nowa 20', Miasto = 'Warszawa'
WHERE ID_Czytelnika = 1;

-- Sprawdzenie: czy zmiana jest widoczna we wszystkich wypożyczeniach?
SELECT c.Imie, c.Nazwisko, c.Adres, c.Miasto,
       k.Tytul, w.DataWyp
FROM Czytelnicy c
JOIN Wypozyczenia w ON c.ID_Czytelnika = w.ID_Czytelnika
JOIN Ksiazki k ON w.ISBN = k.ISBN
WHERE c.ID_Czytelnika = 1;
-- Wynik: wszystkie wypożyczenia widzą nowy adres – jeden UPDATE wystarczył!

To jest esencja 2NF – każdy fakt w jednym miejscu.

Różnica między 2NF a 1NF? W 2NF zmieniasz adres czytelnika raz – i wszystko jest spójne.
Dwa obrazy obok – 1NF (wiele wierszy do zmiany) vs 2NF (jeden wiersz)

Operacja UPDATE w strukturze 2NF jest znacznie prostsza i bezpieczniejsza niż w 1NF. W starej strukturze zmiana adresu czytelnika wymagała aktualizacji wszystkich wierszy zawierających dane tego czytelnika w tabeli Wypozyczenia, co przy dziesiątkach wypożyczeń było ryzykowne i czasochłonne. W nowej strukturze zmiana adresu wymaga aktualizacji dokładnie jednego wiersza w tabeli Czytelnicy.

Przykładowe zapytanie UPDATE po 2NF: UPDATE Czytelnicy SET Miasto = 'Warszawa', KodPocztowy = '00-001' WHERE ID_Czytelnika = 1. To zapytanie modyfikuje adres czytelnika o identyfikatorze 1, niezależnie od tego, ile wypożyczeń ma ten czytelnik. Po wykonaniu UPDATE wszystkie przyszłe zapytania JOIN zwrócą nowy adres, ponieważ dane adresowe są przechowywane tylko w jednym miejscu.

W systemach produkcyjnych, gdzie dane są aktualizowane przez wielu użytkowników jednocześnie, prostota UPDATE w 2NF ma szczególne znaczenie. Zapobiega sytuacjom, w których dwóch pracowników aktualizuje różne wystąpienia tego samego adresu, wprowadzając niespójność. W 2NF taka sytuacja jest niemożliwa, ponieważ adres istnieje tylko w jednym wierszu, a dostęp do niego jest serializowany przez mechanizmy transakcyjne MariaDB.

46/60Test anomalii INSERT po 2NF

Test anomalii INSERT po 2NF

Anomalia INSERT wyeliminowana – książki i czytelnicy mogą istnieć niezależnie.

-- Przed 2NF: nie można dodać książki bez wypożyczenia
-- Po 2NF: dodajemy książkę niezależnie od wypożyczeń
INSERT INTO Ksiazki
VALUES ('978-83-123-4567-9', 'Nowa książka',
        'Autor Testowy', 'Informatyka', 2026, 'Helion', 3);
-- Działa! Książka istnieje w bazie, nawet jeśli nikt jej nie wypożyczył

-- Podobnie: dodajemy nowego czytelnika
INSERT INTO Czytelnicy (Imie, Nazwisko, Adres, Miasto, KodPocztowy)
VALUES ('Ewa', 'Kamińska', 'ul. Szkolna 3', 'Łódź', '90-001');
-- Działa! Czytelnik istnieje, choć nie ma wypożyczeń

To fundamentalna zmiana w stosunku do 1NF.

Książka może istnieć w bazie bez ani jednego wypożyczenia – to normalne w 2NF.
Dwie strzałki – książka wchodzi do Ksiazki, czytelnik do Czytelnicy – niezależnie od wypożyczeń

Operacja INSERT w strukturze 2NF oferuje znacznie większą elastyczność niż w 1NF. W starej strukturze dodanie nowego czytelnika do systemu było niemożliwe bez jednoczesnego wypożyczenia mu książki, ponieważ każdy wiersz w tabeli Wypozyczenia reprezentował zdarzenie wypożyczenia. W nowej strukturze czytelnik może być dodany do tabeli Czytelnicy niezależnie od tego, czy dokonuje wypożyczenia.

Przykładowe zapytanie INSERT po 2NF: INSERT INTO Czytelnicy (Imie, Nazwisko, Adres, Miasto, KodPocztowy, Telefon, Email) VALUES ('Maria', 'Nowak', 'ul. Nowa 5', 'Łódź', '90-001', '555-123-456', 'maria@example.com'). To zapytanie dodaje nowego czytelnika do systemu bez konieczności natychmiastowego wypożyczenia książki. Analogicznie można dodać nową książkę do tabeli Ksiazki przed pierwszym wypożyczeniem.

Ta elastyczność ma ogromne znaczenie praktyczne. W systemie bibliotecznym czytelnicy często zapisują się do biblioteki na długo przed pierwszym wypożyczeniem. W starym systemie nie można było obsłużyć takiego scenariusza bez tworzenia fikcyjnego wypożyczenia, co zaburzało statystyki i raporty. W nowym systemie rejestracja czytelnika jest całkowicie niezależna od procesu wypożyczania, co odzwierciedla rzeczywisty proces biznesowy.

47/60Test anomalii DELETE po 2NF

Test anomalii DELETE po 2NF

DELETE nie jest już 'wszechzniszczeniem' – usuwasz tylko wypożyczenie.

-- Przed 2NF: usunięcie ostatniego wypożyczenia = utrata danych książki
-- Po 2NF: usuwamy tylko wypożyczenie, książka i czytelnik pozostają
DELETE FROM Wypozyczenia
WHERE ID_Wypozyczenia = 1;
-- Czytelnik i książka wciąż istnieją w swoich tabelach
SELECT Tytul FROM Ksiazki WHERE ISBN = '978-83-123-4567-1';
-- Wynik: 'Przedwiośnie' – dane książki są bezpieczne

SELECT Imie, Nazwisko FROM Czytelnicy WHERE ID_Czytelnika = 1;
-- Wynik: 'Jan', 'Kowalski' – dane czytelnika też są bezpieczne

Klucz obcy zabezpiecza przed usunięciem czytelnika/książki z aktywnymi wypożyczeniami.

DELETE usuwa tylko to, co chcesz usunąć. Klucz obcy chroni przed przypadkową utratą danych.
Kosz tylko przy tabeli Wypozyczenia – Czytelnicy i Ksiazki pozostają nienaruszone

Operacja DELETE w strukturze 2NF jest bezpieczna i nie niszczy powiązanych danych, co stanowi radykalną poprawę w porównaniu z 1NF. W starej strukturze usunięcie ostatniego wypożyczenia czytelnika powodowało usunięcie wszystkich jego danych osobowych, ponieważ były one przechowywane w tym samym wierszu. W nowej strukturze usunięcie wypożyczenia nie wpływa na dane czytelnika ani książki.

Przykładowe zapytanie DELETE po 2NF: DELETE FROM Wypozyczenia WHERE ID_Wypozyczenia = 1. To zapytanie usuwa tylko informację o konkretnym zdarzeniu wypożyczenia, nie naruszając danych czytelnika ani książki w tabelach Czytelnicy i Ksiazki. Czytelnik Jan Kowalski nadal istnieje w systemie, a książka Przedwiośnie jest nadal dostępna w katalogu bibliotecznym.

Jednak sama możliwość usunięcia wypożyczenia nie oznacza, że jest to zawsze bezpieczne. W praktyce systemy biblioteczne rzadko usuwają historię wypożyczeń – zamiast tego archiwizują ją do celów statystycznych i audytowych. W 2NF to rozwiązanie jest łatwe do zaimplementowania: wystarczy dodać kolumnę CzyAktywne lub datę archiwizacji w tabeli Wypozyczenia, a dane czytelników i książek pozostają nietknięte w swoich tabelach.

48/60Ograniczenia kluczy obcych w praktyce

Ograniczenia kluczy obcych w praktyce

Kolejność usuwania ma znaczenie: najpierw rekordy potomne, potem nadrzędne.

-- Próba usunięcia czytelnika z aktywnymi wypożyczeniami
DELETE FROM Czytelnicy WHERE ID_Czytelnika = 1;
-- Błąd: Cannot delete or update a parent row: a foreign key constraint fails
-- MariaDB nie pozwoli usunąć czytelnika, który ma wypożyczenia

-- Najpierw trzeba usunąć wypożyczenia, potem czytelnika
DELETE FROM Wypozyczenia WHERE ID_Czytelnika = 1;
DELETE FROM Czytelnicy WHERE ID_Czytelnika = 1;
-- Teraz działa – najpierw dzieci (wypożyczenia), potem rodzic (czytelnik)

To zabezpieczenie chroni przed anomalią DELETE.

Klucz obcy to strażnik – pilnuje, żebyś nie usunął danych, które są potrzebne gdzie indziej.
Strażnik (klucz obcy) blokujący drzwi – nie pozwala usunąć czytelnika z wypożyczeniami

Kolejność wykonywania operacji DELETE w strukturze z kluczami obcymi ma kluczowe znaczenie dla uniknięcia błędów integralności referencyjnej. Jeśli spróbujesz usunąć czytelnika, który ma powiązane wypożyczenia, MariaDB zgłosi błąd FOREIGN KEY constraint violation i zablokuje operację. Aby bezpiecznie usunąć czytelnika lub książkę, należy najpierw usunąć wszystkie powiązane wypożyczenia.

Prawidłowa kolejność: najpierw DELETE FROM Wypozyczenia WHERE ID_Czytelnika = 1 (usunięcie wszystkich wypożyczeń czytelnika), następnie DELETE FROM Czytelnicy WHERE ID_Czytelnika = 1 (usunięcie czytelnika). Ta sekwencja zapewnia, że w momencie usuwania czytelnika nie ma już żadnych wypożyczeń odwołujących się do jego ID, więc operacja zakończy się sukcesem.

W praktyce, jeśli system wymaga częstego usuwania danych kaskadowego, można zdefiniować klucz obcy z regułą ON DELETE CASCADE. Wtedy usunięcie czytelnika automatycznie spowoduje usunięcie wszystkich jego wypożyczeń. Należy jednak zachować ostrożność – kaskadowe usuwanie może prowadzić do niezamierzonej utraty danych, jeśli nie jest w pełni zrozumiane przez programistów i administratorów systemu.

49/60Ćwiczenie praktyczne: dodaj nowe wypożyczenie w 2NF

Ćwiczenie praktyczne: dodaj wypożyczenie w 2NF

W 2NF musisz znać ID_Czytelnika – nie możesz wpisać dowolnego tekstu.

-- Zadanie: dodaj wypożyczenie dla Ewy Kamińskiej (ID_Czytelnika = 6)
-- Książka: 'Nowa książka' (ISBN = 978-83-123-4567-9)
INSERT INTO Wypozyczenia (ID_Czytelnika, ISBN, DataWyp, Status)
VALUES (6, '978-83-123-4567-9', '2026-04-15', 'Wypożyczone');
-- Uwaga: ID_Czytelnika i ISBN muszą istnieć w swoich tabelach!
-- Jeśli nie istnieją → błąd klucza obcego

To wymaga wcześniejszego sprawdzenia, czy czytelnik i książka istnieją.

W 2NF musisz myśleć 'relacyjnie' – wypożyczenie łączy istniejącego czytelnika z istniejącą książką.
Formularz dodawania wypożyczenia – pola ID_Czytelnika i ISBN z listami rozwijanymi

W warstwie aplikacji, po normalizacji do 2NF, interfejs użytkownika musi uwzględniać nową strukturę danych poprzez stosowanie list rozwijanych (combo box) zamiast pól tekstowych dla danych referencyjnych. Na przykład, zamiast pozwalać użytkownikowi na ręczne wpisanie nazwiska czytelnika przy tworzeniu wypożyczenia, aplikacja powinna pobrać listę czytelników z tabeli Czytelnicy i wyświetlić ją w formie listy rozwijanej.

Takie podejście nie tylko ułatwia pracę użytkownikowi (nie musi pamiętać dokładnej pisowni nazwisk), ale także zapewnia integralność danych na poziomie aplikacji – użytkownik może wybrać tylko istniejącego czytelnika. Analogiczne rozwiązanie stosuje się dla książek: lista rozwijana z tytułami książek pobranymi z tabeli Ksiazki, z której użytkownik wybiera odpowiednią pozycję.

Implementacja takiego interfejsu wymaga zapytań SELECT do tabel Czytelnicy i Ksiazki w celu pobrania listy opcji. W systemach webowych dane te są często cachowane w pamięci podręcznej przeglądarki lub serwera, aby zminimalizować liczbę zapytań do bazy danych. W MariaDB można również utworzyć widoki (VIEW), które łączą dane z wielu tabel, ułatwiając programistom aplikacji dostęp do danych bez konieczności pisania złożonych zapytań JOIN.

50/60Wizualizacja: baza po 2NF – pełny obraz

Baza po 2NF – pełny obraz

Trzy tabele połączone kluczami obcymi:

  • Czytelnicy: ID_Czytelnika, Imie, Nazwisko, Adres, Miasto, KodPocztowy, Telefon, Email, DataRejestracji
  • Ksiazki: ISBN, Tytul, Autor, Gatunek, RokWydania, Wydawnictwo, LiczbaEgzemplarzy
  • Wypozyczenia: ID_Wypozyczenia, ID_Czytelnika, ISBN, DataWyp, DataZwrotu, Kara, Status

Każda tabela jest w 2NF. Każda tabela ma jeden cel: opisuje jedną encję. Dane są spójne: każdy fakt w jednym miejscu.

Po 2NF baza jest już całkiem dobrze zaprojektowana. Ale wciąż są ukryte problemy – o tym w 3NF.
Trzy tabele z kolumnami – Czytelnicy (8 kolumn), Ksiazki (7 kolumn), Wypozyczenia (7 kolumn) – z połączeniami

Pełny obraz struktury bazy danych po normalizacji do 2NF obejmuje trzy tabele: Czytelnicy z danymi osobowymi czytelników, Ksiazki z metadanymi książek oraz Wypozyczenia z informacjami o zdarzeniach wypożyczenia. Każda tabela ma swój własny klucz główny, a tabela Wypozyczenia zawiera klucze obce łączące ją z pozostałymi tabelami. To klasyczny przykład schematu relacyjnego, w którym każda encja świata rzeczywistego jest modelowana jako osobna tabela.

W takiej strukturze zapytania SQL stają się bardziej złożone, ale oferują znacznie większe możliwości. Możemy na przykład zadać pytanie: "ilu czytelników z Krakowa wypożyczyło książki Żeromskiego?" – wymaga to połączenia trzech tabel za pomocą JOIN, co w 1NF byłoby niemożliwe bez dodatkowej obróbki danych. W 2NF takie zapytanie jest nie tylko możliwe, ale także wydajne, jeśli klucze obce są odpowiednio zindeksowane.

Warto podkreślić, że struktura po 2NF jest znacznie bliższa rzeczywistemu modelowi danych niż jedna wielka tabela. Odzwierciedla ona sposób, w jaki ludzie myślą o danych: czytelnicy to jedna kategoria obiektów, książki to druga, a wypożyczenia to trzecia. Taka struktura jest łatwiejsza do zrozumienia dla nowych programistów dołączających do projektu i ułatwia utrzymanie systemu w dłuższej perspektywie czasowej.

51/60Podsumowanie: co 2NF eliminuje?

Co 2NF eliminuje? – lista kontrolna:

  • Zależności częściowe – każdy atrybut zależy od całego klucza
  • Anomalia INSERT dla książek – można dodać książkę bez wypożyczenia
  • Anomalia INSERT dla czytelników – można dodać czytelnika bez wypożyczenia
  • Anomalia UPDATE dla danych książki – jeden fakt = jeden wiersz do zmiany
  • Anomalia UPDATE dla danych czytelnika – jeden fakt = jeden wiersz do zmiany
  • Anomalia DELETE – usunięcie wypożyczenia nie niszczy danych
  • Redundancja danych książki – każda książka w jednym wierszu
  • Redundancja danych czytelnika – każdy czytelnik w jednym wierszu
2NF eliminuje 8 konkretnych problemów – to ogromny postęp w stosunku do 1NF.
8 zielonych znaczników – wszystkie problemy wyeliminowane przez 2NF

Powtórzenie listy wyeliminowanych problemów w części podsumowującej służy utrwaleniu wiedzy i pokazaniu pełnego obrazu korzyści płynących z 2NF. Każdy z ośmiu problemów, które zidentyfikowaliśmy i rozwiązaliśmy, był źródłem potencjalnych błędów i niespójności w systemie. Ich eliminacja znacząco podnosi jakość projektu bazy danych i ułatwia jej późniejsze utrzymanie.

Warto zauważyć, że lista wyeliminowanych problemów obejmuje zarówno aspekty strukturalne (redundancja), jak i operacyjne (anomalie). Oznacza to, że 2NF nie tylko poprawia statyczną strukturę bazy, ale także ułatwia codzienne operacje na danych. Pracownicy biblioteki mogą dodawać nowe książki i czytelników bez sztucznych ograniczeń, a zmiana danych osobowych wymaga tylko jednej, prostej operacji UPDATE.

Mimo tych wszystkich zalet, 2NF nie jest ostatnim etapem normalizacji. W tabeli Czytelnicy wciąż istnieje zależność przechodnia między Miastem a Kodem Pocztowym, która może prowadzić do niespójności. Na przykład, jeśli w systemie pojawi się wpis z miastem "Kraków" i kodem pocztowym "00-001" (który należy do Warszawy), baza danych nie zgłosi błędu. Eliminacją takich zależności zajmuje się 3NF, która jest tematem kolejnej prezentacji.

52/60Czego 2NF nie naprawiła?

Czego 2NF nie naprawiła?

Wciąż istnieją zależności PRZECHODNIE (transitive dependencies):

  • W tabeli Czytelnicy: ID_Czytelnika → Miasto → KodPocztowy
    • KodPocztowy zależy od Miasta, a Miasto od ID_Czytelnika
    • KodPocztowy → ZALEŻNOŚĆ PRZECHODNIA przez Miasto

Wciąż redundancja: Miasto, KodPocztowy powtarzają się dla czytelników z tego samego miasta.

Rozwiązanie: 3NF – następna prezentacja.

2NF to nie koniec – wciąż mogą istnieć zależności przechodnie, które generują redundancję.
Strzałka przechodnia – ID_Czytelnika → Miasto → KodPocztowy – zależność łańcuchowa

Po osiągnięciu 2NF w tabelach Czytelnicy i Ksiazki wciąż mogą występować zależności przechodnie, które są źródłem potencjalnych problemów. Zależność przechodnia występuje wtedy, gdy atrybut niebędący kluczem zależy od innego atrybutu niebędącego kluczem, który z kolei zależy od klucza głównego. Formalnie: jeśli A → B i B → C, to A → C jest zależnością przechodnią.

W naszej tabeli Czytelnicy mamy zależność ID_Czytelnika → Miasto → KodPocztowy. Oznacza to, że kod pocztowy zależy od miasta, a nie bezpośrednio od ID_Czytelnika. W praktyce może to prowadzić do niespójności: jeśli w systemie pojawi się czytelnik z miastem "Kraków" i kodem pocztowym "00-001" (który należy do Warszawy), baza danych nie zgłosi błędu, ponieważ nie ma mechanizmu wymuszającego poprawność pary (Miasto, KodPocztowy).

Rozwiązaniem jest dalsza normalizacja do 3NF, która eliminuje zależności przechodnie poprzez utworzenie osobnej tabeli dla kodów pocztowych. W tabeli Miasta każda para (Miasto, KodPocztowy) występowałaby tylko raz, a w tabeli Czytelnicy pozostałby tylko klucz obcy do tabeli Miasta. To gwarantuje, że nie ma możliwości wprowadzenia niespójnej pary miasto-kod pocztowy w systemie.

53/60Zależności przechodnie – przykład z życia

Przykład zależności przechodniej z życia:

Wyobraź sobie tabelę Pracownicy: (ID_Prac, Imie, Nazwisko, ID_Dzialu, NazwaDzialu, AdresDzialu)

Zależności:

  • ID_Prac → ID_Dzialu (pracownik należy do działu)
  • ID_Dzialu → NazwaDzialu, AdresDzialu (dział ma nazwę i adres)
  • Zatem: ID_Prac → ID_Dzialu → NazwaDzialu (zależność przechodnia)

Problem: jeśli 20 pracowników jest w dziale "IT", to "IT" pojawia się 20 razy.

Zmiana nazwy działu = UPDATE 20 wierszy.

Zależność przechodnia to 'efekt domina' – A zależy od B, B od C, więc A zależy od C przez B.
Łańcuch – ID_Prac → ID_Dzialu → NazwaDzialu z efektem domina

Przykład zależności przechodniej w tabeli Czytelnicy jest klasycznym problemem, który rozwiązuje 3NF. W bieżącej strukturze kod pocztowy jest przechowywany jako atrybut czytelnika, ale w rzeczywistości zależy on od miasta, a nie od czytelnika. To może prowadzić do sytuacji, w której dwóch czytelników z tego samego miasta ma różne kody pocztowe, co jest błędem logicznym.

Innym przykładem zależności przechodniej w naszym systemie może być zależność Gatunek → OpisGatunku, jeśli dodalibyśmy kolumnę z opisem gatunku literackiego. Gatunek zależy od ISBN (lub ID_Ksiazki), a opis gatunku zależy od gatunku. W efekcie opis gatunku zależałby przechodnio od ISBN poprzez Gatunek, co mogłoby prowadzić do niespójności opisów dla różnych książek tego samego gatunku.

Eliminacja zależności przechodnich w 3NF polega na utworzeniu osobnych tabel dla każdej grupy atrybutów połączonych zależnością przechodnią. W przypadku kodu pocztowego tworzymy tabelę Miasta z atrybutami (Miasto, KodPocztowy) i kluczem głównym ID_Miasta. W tabeli Czytelnicy przechowujemy ID_Miasta jako klucz obcy, co wymusza spójność pary miasto-kod pocztowy na poziomie bazy danych.

54/60Kiedy 2NF wystarczy?

Kiedy 2NF wystarczy?

  • Gdy klucz główny jest prosty (jednokolumnowy) – 2NF spełniona automatycznie
  • Gdy nie ma zależności przechodnich (wszystkie atrybuty zależą bezpośrednio od klucza)
  • Gdy akceptujemy pewną redundancję w zamian za wydajność (denormalizacja)

W praktyce: wiele systemów zatrzymuje się na 3NF, 2NF jest rzadkością jako cel sam w sobie.

2NF to zazwyczaj etap przejściowy – zaraz po niej idzie 3NF.

2NF to etap przejściowy – w praktyce rzadko zostajemy na 2NF, zwykle idziemy do 3NF.
Drogowskaz – 2NF jako przystanek na drodze do 3NF

2NF jest ważnym etapem w procesie normalizacji, ale w praktyce rzadko stanowi cel sam w sobie. Większość systemów produkcyjnych jest normalizowana do 3NF, która eliminuje zarówno częściowe zależności funkcyjne (2NF), jak i zależności przechodnie. Osiągnięcie 3NF zapewnia dobry kompromis między integralnością danych a wydajnością zapytań dla typowych aplikacji biznesowych.

Decyzja o tym, jak daleko posunąć normalizację, zależy od konkretnego projektu. Dla systemów transakcyjnych o dużym wolumenie operacji zapisu, normalizacja do 3NF jest standardem. Dla systemów raportowych i hurtowni danych, celowa denormalizacja (świadome odejście od reguł normalizacji na rzecz wydajności odczytu) jest często stosowana. Kluczowe jest zrozumienie konsekwencji każdej decyzji projektowej.

W naszym przykładzie bibliotecznym osiągnięcie 2NF przyniosło znaczącą poprawę jakości danych. Redundancja została wyeliminowana, a anomalie usunięte. Jednak zależność przechodnia między miastem a kodem pocztowym wciąż istnieje i może być źródłem problemów. Dlatego w kolejnym kroku doprowadzimy bazę do 3NF, która wyeliminuje ten ostatni typ problemu w naszej strukturze.

55/60Podsumowanie: od 1NF do 2NF – co się zmieniło?
Aspekt1NF (jedna tabela)2NF (trzy tabele)
Liczba tabel13
Dane książkiPowtarzaneOsobna tabela Ksiazki
Dane czytelnikaPowtarzaneOsobna tabela Czytelnicy
WypożyczeniaWszystko razemTylko dane wypożyczeń
Klucz głównyID_WypozyczeniaID w każdej tabeli
Klucze obceBrakFK w Wypozyczenia
ZapytaniaProste (bez JOIN)Z JOIN (ale precyzyjne)
RedundancjaWysokaNiska (ale nie zero)
AnomalieWszystkieCzęściowo wyeliminowane
Zmiana jest fundamentalna – z jednej 'wielkiej kupy' danych do trzech wyspecjalizowanych tabel.
Dwie kolumny – 1NF (jedna tabela) vs 2NF (trzy tabele) z podpisami

Porównanie ewolucji struktury bazy danych od 1NF przez 2NF do docelowej postaci uwidacznia postępy, jakie osiągnęliśmy w procesie normalizacji. Na początku mieliśmy jedną tabelę z siedemnastoma kolumnami, która mieszała trzy różne encje. Po 2NF mamy trzy tabele, każda z jasno określonym celem i sześcioma do siedmiu kolumnami. Każda tabela jest prostsza, bardziej wyspecjalizowana i łatwiejsza w utrzymaniu.

Warto zwrócić uwagę na to, jak zmieniła się liczba kluczy głównych: z jednego w 1NF do trzech w 2NF. Każdy klucz główny jednoznacznie identyfikuje encję w swojej tabeli. Klucze obce łączą tabele, tworząc sieć powiązań, która odzwierciedla rzeczywiste relacje między czytelnikami, książkami i wypożyczeniami. Ta struktura jest zgodna z modelem związku encji (ERD), który jest standardowym narzędziem projektowania baz danych.

Dalsza normalizacja do 3NF doda czwartą tabelę dla miast i kodów pocztowych. W BCNF, 4NF i 5NF struktura będzie się dalej rozwijać, aż osiągnie optymalną postać. Każdy etap normalizacji przybliża nas do bazy danych, w której każdy fakt przechowywany jest dokładnie raz, a wszystkie zależności między danymi są jawnie zdefiniowane i egzekwowane przez system.

56/60Ćwiczenie: zaprojektuj 2NF dla systemu sprzedaży

Ćwiczenie: zaprojektuj 2NF dla systemu sprzedaży

Tabela źródłowa Sprzedaz: (ID_Transakcji, Klient, Adres, Produkt, Kategoria, Cena, Ilosc, Data)

Klucz: ID_Transakcji (prosty) → 2NF automatycznie.

Zmiana na klucz złożony: (ID_Transakcji, Produkt).

  • Czy Produkt → Kategoria, Cena? TAK – zależność częściowa

Rozkład do 2NF:

  • Produkty: (Produkt, Kategoria, Cena)
  • Transakcje: (ID_Transakcji, Klient, Adres, Data)
  • ElementyTransakcji: (ID_Transakcji, Produkt, Ilosc)
To ćwiczenie pokazuje, że 2NF wymaga analizy zależności – nie zawsze jest oczywista.
Tabela Sprzedaz rozpadająca się na trzy: Produkty, Transakcje, ElementyTransakcji

Zadania do samodzielnego wykonania na zakończenie prezentacji mają na celu utrwalenie zdobytej wiedzy poprzez praktyczne zastosowanie reguł 2NF. Każde zadanie zawiera opis tabeli wraz z przykładowymi danymi i wymaga zidentyfikowania zależności częściowych, określenia, czy tabela jest w 2NF oraz zaproponowania dekompozycji, jeśli jest potrzebna. Zachęcam do samodzielnej próby przed sprawdzeniem odpowiedzi.

Pierwsze zadanie dotyczy tabeli Oceny z kluczem złożonym (ID_Studenta, ID_Przedmiotu, Data). Kolumny NazwaPrzedmiotu i Prowadzacy zależą tylko od ID_Przedmiotu, a nie od całego klucza, co stanowi zależność częściową. Rozwiązaniem jest utworzenie osobnej tabeli Przedmioty z atrybutami (ID_Przedmiotu, Nazwa, Prowadzacy) oraz pozostawienie w tabeli Oceny tylko atrybutu Ocena oraz kluczy obcych.

Drugie zadanie dotyczy tabeli z kluczem prostym, która jest automatycznie w 2NF. To zadanie uczy, że nie każda tabela wymaga dekompozycji. Trzecie zadanie dotyczy analizy rzeczywistego schematu bazy danych, co rozwija umiejętność praktycznego stosowania normalizacji w kontekście istniejących systemów. Pamiętaj, że regularna praktyka jest kluczem do opanowania normalizacji.

57/60Bibliografia i źródła

Kluczowe publikacje i materiały

  • E.F. Codd (1971) – 'Further Normalization of the Data Base Relational Model' (wprowadzenie 2NF i 3NF)
  • C.J. Date – 'An Introduction to Database Systems' (8th ed.)
  • R. Connolly, T. Begg – 'Database Systems: A Practical Approach'
  • Dokumentacja MariaDB – https://mariadb.com/kb/en/
  • Wikipedia – 'Second normal form' – https://en.wikipedia.org/wiki/Second_normal_form
Oryginalny artykuł Codda z 1971 roku definiuje 2NF i 3NF – to klasyka, którą warto znać.
Półka z książkami (okładki podręczników) i ikona przeglądarki z dokumentacją MariaDB

Bibliografia normalizacji baz danych zawiera zarówno klasyczne publikacje naukowe, jak i współczesne podręczniki i zasoby internetowe. Oryginalny artykuł Edgara Codda z 1970 roku "A Relational Model of Data for Large Shared Data Banks" jest fundamentalną pracą, która zapoczątkowała erę relacyjnych baz danych. W artykule tym Codd nie tylko zdefiniował model relacyjny, ale także wprowadził pojęcie zależności funkcyjnych i normalizacji.

Kolejne ważne publikacje to "Further Normalization of the Data Base Relational Model" Codda z 1971 roku, w którym zdefiniował 2NF i 3NF, oraz prace Raymonda Boyce'a i Ronalda Fagina, którzy rozszerzyli teorię normalizacji o BCNF, 4NF i 5NF. Dla studentów polecane są podręczniki C.J. Date'a "An Introduction to Database Systems" oraz Connolly'ego i Begga "Database Systems: A Practical Approach", które w przystępny sposób wyjaśniają zarówno teorię, jak i praktykę normalizacji.

Z zasobów internetowych warto korzystać z dokumentacji MariaDB, która zawiera praktyczne przykłady definiowania tabel z kluczami obcymi i ograniczeniami. Wikipedia oferuje dobre wprowadzenie do terminologii normalizacji w języku polskim i angielskim. Fora dyskusyjne, takie jak Stack Overflow, są doskonałym źródłem odpowiedzi na konkretne pytania dotyczące normalizacji w praktyce.

58/60Ćwiczenia do samodzielnego wykonania

Ćwiczenia do samodzielnego wykonania:

Ćwiczenie 1: Tabela Oceny z kluczem (StudentID, PrzedmiotID, Data) i atrybutami: Ocena, NazwaPrzedmiotu, Prowadzacy, ECTS. Zidentyfikuj zależności częściowe i zaprojektuj dekompozycję do 2NF.

Ćwiczenie 2: Tabela Rezerwacje (ID_Rez, Klient, Email, Pokoj, Hotel, AdresHotelu, DataOd, DataDo). Czy jest w 2NF? Jeśli nie – zaprojektuj dekompozycję.

Ćwiczenie 3 (dla zaawansowanych): W tabeli Wypozyczenia z BD_1NF mieliśmy kolumnę Czytelnik jako VARCHAR. Po normalizacji do 2NF użyliśmy ID_Czytelnika. Jakie są zalety i wady obu rozwiązań?

Ćwiczenia to klucz do zrozumienia – im więcej samodzielnie przeanalizujesz, tym lepiej zrozumiesz 2NF.
Trzy karty z zadaniami – Oceny, Rezerwacje, Analiza ID_Czytelnika

Zachęcam do samodzielnego wykonywania ćwiczeń przed sprawdzeniem odpowiedzi. Proces aktywnego rozwiązywania problemów jest znacznie skuteczniejszy w utrwalaniu wiedzy niż bierne czytanie gotowych rozwiązań. Nawet jeśli popełnisz błąd, analiza przyczyny błędu pomoże Ci lepiej zrozumieć reguły normalizacji i uniknąć podobnych pomyłek w przyszłości.

Przy rozwiązywaniu ćwiczeń stosuj systematyczną procedurę: najpierw sprawdź, czy tabela jest w 1NF, następnie określ klucz główny, zidentyfikuj wszystkie atrybuty niebędące kluczem i dla każdego z nich określ, od czego jest funkcyjnie zależny. Jeśli znajdziesz atrybuty zależne tylko od części klucza złożonego, masz zależność częściową i tabelę trzeba zdekomponować. Jeśli klucz jest prosty, tabela jest automatycznie w 2NF.

Pamiętaj, że umiejętność normalizacji rozwija się stopniowo wraz z praktyką. Nie zniechęcaj się, jeśli na początku niektóre przykłady sprawiają Ci trudność. Każdy kolejny przeanalizowany przypadek utrwala Twoją wiedzę i rozwija intuicję projektową. Po opanowaniu 2NF będziesz gotów do zgłębienia 3NF, która eliminuje zależności przechodnie i jest standardem w projektowaniu produkcyjnych baz danych.

59/60Zapowiedź 3NF – zależności przechodnie

Zapowiedź 3NF – zależności przechodnie

Po 2NF wciąż mamy problem w tabeli Czytelnicy:

  • ID_Czytelnika → Miasto → KodPocztowy
  • KodPocztowy zależy od Miasta, nie od ID_Czytelnika bezpośrednio

Konsekwencje:

  • Redundancja: "Kraków" i "30-001" powtarzają się dla każdego czytelnika z Krakowa
  • Anomalia UPDATE: zmiana kodu dla Krakowa = UPDATE wielu wierszy
  • Anomalia INSERT: nie można dodać kodu dla miasta bez czytelnika

Rozwiązanie: 3NF – osobna tabela Miasta.

3NF eliminuje zależności przechodnie – o tym w następnej prezentacji.
Strzałka przechodnia – ID_Czytelnika → Miasto → KodPocztowy – do wyeliminowania w 3NF

Trzecia postać normalna to naturalna kontynuacja procesu normalizacji, która zajmuje się eliminacją zależności przechodnich. Po doprowadzeniu bazy do 2NF wyeliminowaliśmy częściowe zależności funkcyjne, ale w tabelach wciąż mogą występować zależności pośrednie, w których atrybut niebędący kluczem zależy od innego atrybutu niebędącego kluczem. 3NF usuwa ten typ zależności, dodatkowo poprawiając integralność danych.

W naszym systemie bibliotecznym doskonałym przykładem zależności przechodniej jest relacja między miastem a kodem pocztowym w tabeli Czytelnicy. Kod pocztowy zależy od miasta, a miasto zależy od ID_Czytelnika. W efekcie kod pocztowy zależy przechodnio od ID_Czytelnika. To oznacza, że dwa wiersze z tym samym miastem muszą mieć ten sam kod pocztowy – a baza danych nie egzekwuje tego ograniczenia.

Rozwiązaniem jest utworzenie osobnej tabeli Miasta z kluczem głównym ID_Miasta oraz atrybutami Miasto i KodPocztowy. W tabeli Czytelnicy zamiast bezpośredniego przechowywania miasta i kodu pocztowego, przechowujemy ID_Miasta jako klucz obcy. Dzięki temu para (Miasto, KodPocztowy) jest przechowywana tylko raz i nie ma możliwości wprowadzenia niespójnych danych. To zagadnienie będzie szczegółowo omówione w następnej prezentacji.

60/60Dziękuję / Pytania / Zapowiedź 3NF

Podsumowanie drogi: dane surowe → 1NF → 2NF

Kluczowe koncepcje do zapamiętania:

  • Zależność funkcyjna: A → B (A wyznacza B)
  • Zależność częściowa: atrybut zależy od części klucza złożonego
  • Dekompozycja: podział tabeli na mniejsze, każda z jednym celem

Dziękujemy za uwagę! Zachęcamy do dyskusji i pytań.

Zapowiedź: '3NF: Trzecia postać normalna – eliminacja zależności przechodnich'

Normalizacja to nie teoria – to praktyczne narzędzie, które oszczędzi Ci godzin debugowania przy zapytaniach SQL.
Metafora drogi – 'Jesteś tutaj' na etapie 2NF, przed nami dalsze etapy normalizacji

Gratuluję ukończenia prezentacji o drugiej postaci normalnej! Przebyliśmy długą drogę od przypomnienia 1NF, przez zrozumienie zależności funkcyjnych, definicję i zastosowanie 2NF, aż po praktyczną implementację w MariaDB. Nauczyłeś się, jak identyfikować częściowe zależności funkcyjne i jak dekomponować tabele, aby je wyeliminować. Twoja wiedza o normalizacji znacząco się poszerzyła.

Zachęcam Cię do samodzielnego przećwiczenia zdobytych umiejętności. Spróbuj znormalizować do 2NF inną tabelę, na przykład z systemu sprzedaży lub magazynu. Możesz również wrócić do przykładów z prezentacji i spróbować samodzielnie przejść przez proces dekompozycji bez podglądania rozwiązań. Praktyka jest kluczem do opanowania normalizacji.

W następnej prezentacji pt. "3NF: Trzecia postać normalna – eliminacja zależności przechodnich" zajmiemy się kolejnym etapem normalizacji. Dowiesz się, jak identyfikować zależności przechodnie i jak je eliminować poprzez dalszą dekompozycję tabel. Do zobaczenia na kolejnym spotkaniu, gdzie nasza struktura bazodanowa wzbogaci się o czwartą tabelę dla miast i kodów pocztowych.