Normalizacja baz danych polega na rozbiciu jednej dużej tabeli na kilka powiązanych tak, żeby każdą informację zapisać w jednym miejscu. W naszym przykładzie z dwunastoma wypożyczeniami w bibliotece szkolnej zmiana wychowawcy jednej klasy wymagała poprawienia sześciu wierszy, a po normalizacji jednego. Tabelę doprowadzamy krok po kroku do trzeciej postaci normalnej (3NF), a skrypt w SQLite sprawdza, że zapytanie z JOIN oddaje wszystkie 12 wierszy bez jednej różnicy. Przy okazji wychodzą dwie pułapki SQLite: klucze obce są w nim domyślnie wyłączone, a klucz główny złożony z kilku kolumn przyjmuje puste wartości, jeśli nie dopiszemy NOT NULL.
Normalizacja baz danych w skrócie
Normalizacja to projektowanie tabel według kolejnych reguł, zwanych postaciami normalnymi. Każda postać to zestaw warunków, które tabela musi spełnić. Pierwsza postać normalna (1NF) wymaga jednej wartości w każdej komórce. Druga (2NF) wymaga, żeby każda kolumna spoza klucza zależała od całego klucza, a nie tylko od jego części. Trzecia (3NF) wymaga, żeby kolumny niebędące kluczem nie zależały od siebie nawzajem.
Model relacyjny, na którym stoi cała ta teoria, opisał Edgar F. Codd w artykule “A relational model of data for large shared data banks”, wydanym w czerwcu 1970 roku w Communications of the ACM. Są też postacie wyższe: postać Boyce’a-Codda (BCNF), czwarta i piąta. W zwykłej aplikacji szkolnej, sklepowej czy firmowej sensownym celem jest zwykle 3NF. Wyższe postacie dotyczą zależności, które w takich tabelach pojawiają się rzadko.
Po normalizacji każdy fakt ma jedno miejsce w bazie. Wychowawca klasy siedzi w jednym wierszu, tytuł książki w jednym wierszu, a tabela wypożyczeń trzyma tylko numery, które je łączą. Podstawy relacyjnych baz i SQL opisaliśmy w przewodniku bazy danych SQL od podstaw.
Tabela wyjściowa: wypożyczenia w bibliotece szkolnej
Bibliotekarka prowadzi rejestr w jednej tabeli: jeden wiersz na ucznia, a wypożyczone książki wpisane po przecinku w jednej komórce. To jest postać nieznormalizowana, czasem oznaczana 0NF. Dane w przykładzie są zmyślone.
| Uczeń | Klasa | Wychowawca | Wypożyczone książki |
|---|---|---|---|
| Ola | 2A | Anna Lis | Lalka (B. Prus) 08.09; Pan Tadeusz (A. Mickiewicz) 22.09 |
| Kuba | 2A | Anna Lis | Lalka (B. Prus) 29.09; Kamienie na szaniec (A. Kamiński) 06.10 |
| Zosia | 2B | Piotr Sowa | Pan Tadeusz (A. Mickiewicz) 09.09; Quo vadis (H. Sienkiewicz) 01.10 |
| Tymek | 2B | Piotr Sowa | Kamienie na szaniec (A. Kamiński) 15.09; Balladyna (J. Słowacki) 13.10 |
| Maja | 2B | Piotr Sowa | Quo vadis (H. Sienkiewicz) 17.09; Lalka (B. Prus) 20.10 |
| Igor | 3C | Ewa Wrona | Mały Książę (A. de Saint-Exupéry) 10.09; Pan Tadeusz (A. Mickiewicz) 02.10 |
W takiej tabeli nie da się prosto zapytać, kto wypożyczył “Lalkę”. Baza widzi w ostatniej kolumnie jeden długi napis. Nie da się też policzyć wypożyczeń z września bez rozcinania tekstu w każdej komórce.
Trzy anomalie, policzone w SQLite
Anomalia to sytuacja, w której zmiana jednej informacji wymaga ruszenia wielu wierszy albo w ogóle się nie da. Sprawdziliśmy je na tabeli po pierwszym kroku normalizacji (1NF, wersja z 12 wierszami opisana niżej), uruchamiając skrypt w Pythonie 3.14.3 z biblioteką SQLite 3.50.4.
- Anomalia aktualizacji. Klasa 2B dostaje nowego wychowawcę. Polecenie
UPDATEzmienia 6 wierszy, bo Piotr Sowa jest wpisany przy każdym wypożyczeniu trojga uczniów tej klasy. Wystarczy pominąć jeden wiersz, żeby baza podawała dwóch różnych wychowawców jednej klasy. - Anomalia wstawiania. Do szkoły dochodzi Lena z 3C, która jeszcze nic nie wypożyczyła. Baza odrzuca wiersz komunikatem
NOT NULL constraint failed: wypozyczenia_1nf.ksiazka_id, bo bez książki nie ma klucza głównego. - Anomalia usuwania. Igor oddaje “Małego Księcia” i bibliotekarka kasuje to wypożyczenie. Liczba książek znanych bazie spada z 6 do 5, bo tytuł i autor tej książki były zapisane tylko w tym jednym wierszu.

Wszystkie trzy anomalie mają tę samą przyczynę. W jednej tabeli siedzą fakty o czterech różnych rzeczach: uczniach, klasach, książkach i samych wypożyczeniach. Normalizacja rozdziela je tak, żeby każda rzecz miała swoją tabelę.
Frontend Master 2026 · Kodożercy
Zrozum, co się dzieje pod spodem twojej strony
Frontend Master 2026 to sześć kursów Kodożerców z HTML, CSS, JS i Git. Z fundamentami przestajesz kopiować bez zrozumienia, zaczynasz świadomie poprawiać, łączyć i rozszerzać każdy projekt.
Zobacz program →
Klucz główny i klucz obcy
Klucz główny (ang. primary key) to kolumna albo zestaw kolumn, które jednoznacznie wskazują jeden wiersz tabeli. W tabeli uczniów jest nim numer ucznia, w tabeli książek numer książki. Klucz obcy (ang. foreign key) to kolumna, która przechowuje klucz główny z innej tabeli i w ten sposób łączy wiersze. Kolumna uczen_id w tabeli wypożyczeń jest kluczem obcym, bo wskazuje wiersz w tabeli uczniów.
Klucz złożony składa się z kilku kolumn. Jedno wypożyczenie wskazują u nas razem trzy wartości: numer ucznia, numer książki i data. Ten sam uczeń może wypożyczyć tę samą książkę drugi raz, ale nie tego samego dnia. Klucz złożony jest tu potrzebny, żeby pokazać zależność od części klucza, którą usuwa druga postać normalna.
Baza pilnuje klucza obcego tylko wtedy, gdy ma to włączone. W SQLite sprawdzanie jest domyślnie wyłączone. Dokumentacja SQLite pisze wprost, że trzeba je włączać osobno dla każdego połączenia poleceniem PRAGMA foreign_keys = ON. Więcej o tworzeniu tabel i zapytaniach jest w naszym kursie SQL od podstaw.
Pierwsza postać normalna (1NF): jedna wartość w komórce
Tabela jest w pierwszej postaci normalnej, gdy każda komórka zawiera jedną wartość, nie ma powtarzających się grup kolumn, a każdy wiersz ma klucz główny. Listę książek z jednej komórki rozkładamy więc na osobne wiersze: jeden wiersz to jedno wypożyczenie. Uczniowie i książki dostają numery, bo imię i tytuł nie są unikalne.
| uczen_id | imie | klasa | wychowawca | ksiazka_id | tytul | autor | data_wyp |
|---|---|---|---|---|---|---|---|
| 1 | Ola | 2A | Anna Lis | 101 | Lalka | Bolesław Prus | 2026-09-08 |
| 1 | Ola | 2A | Anna Lis | 102 | Pan Tadeusz | Adam Mickiewicz | 2026-09-22 |
| 2 | Kuba | 2A | Anna Lis | 101 | Lalka | Bolesław Prus | 2026-09-29 |
| 2 | Kuba | 2A | Anna Lis | 103 | Kamienie na szaniec | Aleksander Kamiński | 2026-10-06 |
| 3 | Zosia | 2B | Piotr Sowa | 102 | Pan Tadeusz | Adam Mickiewicz | 2026-09-09 |
| 3 | Zosia | 2B | Piotr Sowa | 104 | Quo vadis | Henryk Sienkiewicz | 2026-10-01 |
| 4 | Tymek | 2B | Piotr Sowa | 103 | Kamienie na szaniec | Aleksander Kamiński | 2026-09-15 |
| 4 | Tymek | 2B | Piotr Sowa | 105 | Balladyna | Juliusz Słowacki | 2026-10-13 |
| 5 | Maja | 2B | Piotr Sowa | 104 | Quo vadis | Henryk Sienkiewicz | 2026-09-17 |
| 5 | Maja | 2B | Piotr Sowa | 101 | Lalka | Bolesław Prus | 2026-10-20 |
| 6 | Igor | 3C | Ewa Wrona | 106 | Mały Książę | Antoine de Saint-Exupéry | 2026-09-10 |
| 6 | Igor | 3C | Ewa Wrona | 102 | Pan Tadeusz | Adam Mickiewicz | 2026-10-02 |
Kluczem głównym tej tabeli jest trójka (uczen_id, ksiazka_id, data_wyp). Teraz zapytanie o “Lalkę” działa i zwraca trzy wiersze. Wszystkie trzy anomalie opisane wyżej dotyczą jednak właśnie tej wersji.
Druga postać normalna (2NF): bez zależności od części klucza
Tabela jest w drugiej postaci normalnej, gdy jest w 1NF, a żadna kolumna spoza klucza nie zależy tylko od części klucza kandydującego. Kluczem kandydującym nazywa się każdy zestaw kolumn, który mógłby pełnić rolę klucza głównego. Warunek ma znaczenie tylko przy kluczach złożonych. Jeśli wszystkie klucze kandydujące tabeli są jednokolumnowe, tabela w 1NF spełnia 2NF automatycznie.
W naszej tabeli imię, klasa i wychowawca zależą tylko od uczen_id. Tytuł i autor zależą tylko od ksiazka_id. Od całej trójki zależy w praktyce samo wypożyczenie. Dzielimy więc tabelę na trzy:
- uczniowie (
uczen_id, imie, klasa, wychowawca): 6 wierszy, - ksiazki (
ksiazka_id, tytul, autor): 6 wierszy, - wypozyczenia (
uczen_id,ksiazka_id,data_wyp): 12 wierszy.
Tytuł “Lalki” jest teraz zapisany raz. Zmiana wychowawcy klasy 2B wymaga teraz trzech poprawek zamiast sześciu, po jednej na każdego ucznia tej klasy. Wychowawca dalej powtarza się przy każdym uczniu.
Trzecia postać normalna (3NF): bez zależności przechodnich
Tabela jest w trzeciej postaci normalnej, gdy jest w 2NF, a żadna kolumna spoza klucza nie zależy od innej kolumny spoza klucza. Tak brzmi ta definicja w uproszczeniu, które wystarcza dla tabel z jednym kluczem kandydującym, jak w naszym schemacie. Taką zależność przez pośrednika nazywa się przechodnią. U nas wychowawca zależy od klasy, a klasa od ucznia. Wychowawca jest więc związany z uczniem tylko przez klasę.
Wyprowadzamy klasy do osobnej tabeli klasy (klasa, wychowawca) z trzema wierszami. W tabeli uczniów zostaje kolumna klasa jako klucz obcy. Zmiana wychowawcy 2B to teraz jeden wiersz.

Autor książki zostaje w tabeli książek. W naszym przykładzie autor nie ma własnych danych, więc nie tworzy zależności przechodniej. Gdyby baza zapisywała też datę urodzenia autora, ta kolumna zależałaby od autora. Wtedy autorzy dostaliby osobną tabelę.
Normalizacja w SQL: skrypt SQLite i JOIN, który oddaje dane wyjściowe
Cały przykład da się sprawdzić w Pythonie bez instalowania niczego, bo moduł sqlite3 jest w bibliotece standardowej. Skrypt tworzy tabelę w 1NF i mierzy na niej anomalie. Potem buduje cztery tabele w 3NF i odtwarza z nich tabelę wyjściową zapytaniem z JOIN. Na koniec sprawdza, co dzieje się z kluczem obcym bez PRAGMA foreign_keys = ON i z nim.
Kod
import sqlite3
# Tabela wyjściowa w 1NF: jeden wiersz = jedno wypożyczenie (dane zmyślone)
wypozyczenia_1nf = [
# uczen_id, imie, klasa, wychowawca, ksiazka_id, tytul, autor, data
(1, "Ola", "2A", "Anna Lis", 101, "Lalka", "Bolesław Prus", "2026-09-08"),
(1, "Ola", "2A", "Anna Lis", 102, "Pan Tadeusz", "Adam Mickiewicz", "2026-09-22"),
(2, "Kuba", "2A", "Anna Lis", 101, "Lalka", "Bolesław Prus", "2026-09-29"),
(2, "Kuba", "2A", "Anna Lis", 103, "Kamienie na szaniec", "Aleksander Kamiński", "2026-10-06"),
(3, "Zosia", "2B", "Piotr Sowa", 102, "Pan Tadeusz", "Adam Mickiewicz", "2026-09-09"),
(3, "Zosia", "2B", "Piotr Sowa", 104, "Quo vadis", "Henryk Sienkiewicz", "2026-10-01"),
(4, "Tymek", "2B", "Piotr Sowa", 103, "Kamienie na szaniec", "Aleksander Kamiński", "2026-09-15"),
(4, "Tymek", "2B", "Piotr Sowa", 105, "Balladyna", "Juliusz Słowacki", "2026-10-13"),
(5, "Maja", "2B", "Piotr Sowa", 104, "Quo vadis", "Henryk Sienkiewicz", "2026-09-17"),
(5, "Maja", "2B", "Piotr Sowa", 101, "Lalka", "Bolesław Prus", "2026-10-20"),
(6, "Igor", "3C", "Ewa Wrona", 106, "Mały Książę", "Antoine de Saint-Exupéry", "2026-09-10"),
(6, "Igor", "3C", "Ewa Wrona", 102, "Pan Tadeusz", "Adam Mickiewicz", "2026-10-02"),
]
print("SQLite", sqlite3.sqlite_version)
# --- 1. Anomalie w jednej tabeli ---
db = sqlite3.connect(":memory:")
db.execute("""CREATE TABLE wypozyczenia_1nf (
uczen_id INTEGER NOT NULL, imie TEXT NOT NULL, klasa TEXT NOT NULL,
wychowawca TEXT NOT NULL, ksiazka_id INTEGER NOT NULL, tytul TEXT NOT NULL,
autor TEXT NOT NULL, data_wyp TEXT NOT NULL,
PRIMARY KEY (uczen_id, ksiazka_id, data_wyp))""")
db.executemany("INSERT INTO wypozyczenia_1nf VALUES (?,?,?,?,?,?,?,?)", wypozyczenia_1nf)
zmienione = db.execute("UPDATE wypozyczenia_1nf SET wychowawca = 'Marek Kos' WHERE klasa = '2B'").rowcount
print("Anomalia aktualizacji: zmiana wychowawcy 2B zmienia wierszy:", zmienione)
try:
db.execute("INSERT INTO wypozyczenia_1nf (uczen_id, imie, klasa, wychowawca) VALUES (7, 'Lena', '3C', 'Ewa Wrona')")
print("Anomalia wstawiania: nowy uczeń bez wypożyczenia WSZEDŁ")
except sqlite3.IntegrityError as e:
print("Anomalia wstawiania: nowy uczeń bez wypożyczenia odrzucony:", e)
ksiazek_przed = db.execute("SELECT COUNT(DISTINCT ksiazka_id) FROM wypozyczenia_1nf").fetchone()[0]
db.execute("DELETE FROM wypozyczenia_1nf WHERE ksiazka_id = 106")
ksiazek_po = db.execute("SELECT COUNT(DISTINCT ksiazka_id) FROM wypozyczenia_1nf").fetchone()[0]
print("Anomalia usuwania: książek w bazie przed", ksiazek_przed, "po usunięciu jednego wypożyczenia", ksiazek_po)
db.close()
# --- 1b. Ta sama tabela w 2NF: uczniowie razem z wychowawcą klasy ---
uczniowie_2nf = {(r[0], r[1], r[2], r[3]) for r in wypozyczenia_1nf}
print("2NF: zmiana wychowawcy 2B zmienia wierszy:", sum(1 for u in uczniowie_2nf if u[2] == "2B"))
# --- 1c. Pułapka SQLite: NULL w złożonym kluczu głównym bez NOT NULL ---
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE test (uczen_id INTEGER, ksiazka_id INTEGER, data_wyp TEXT, PRIMARY KEY (uczen_id, ksiazka_id, data_wyp))")
db.execute("INSERT INTO test (uczen_id) VALUES (7)")
print("Klucz złożony bez NOT NULL przyjął wiersz:", db.execute("SELECT * FROM test").fetchall())
db.close()
# --- 2. Ta sama baza w 3NF ---
SCHEMAT = """
CREATE TABLE klasy (klasa TEXT PRIMARY KEY, wychowawca TEXT NOT NULL);
CREATE TABLE uczniowie (uczen_id INTEGER PRIMARY KEY, imie TEXT NOT NULL,
klasa TEXT NOT NULL REFERENCES klasy(klasa));
CREATE TABLE ksiazki (ksiazka_id INTEGER PRIMARY KEY, tytul TEXT NOT NULL, autor TEXT NOT NULL);
CREATE TABLE wypozyczenia (uczen_id INTEGER NOT NULL REFERENCES uczniowie(uczen_id),
ksiazka_id INTEGER NOT NULL REFERENCES ksiazki(ksiazka_id), data_wyp TEXT NOT NULL,
PRIMARY KEY (uczen_id, ksiazka_id, data_wyp));
"""
def zbuduj(wlacz_klucze_obce):
db = sqlite3.connect(":memory:")
if wlacz_klucze_obce:
db.execute("PRAGMA foreign_keys = ON")
db.executescript(SCHEMAT)
db.executemany("INSERT OR IGNORE INTO klasy VALUES (?,?)", {(r[2], r[3]) for r in wypozyczenia_1nf})
db.executemany("INSERT OR IGNORE INTO uczniowie VALUES (?,?,?)", {(r[0], r[1], r[2]) for r in wypozyczenia_1nf})
db.executemany("INSERT OR IGNORE INTO ksiazki VALUES (?,?,?)", {(r[4], r[5], r[6]) for r in wypozyczenia_1nf})
db.executemany("INSERT INTO wypozyczenia VALUES (?,?,?)", [(r[0], r[4], r[7]) for r in wypozyczenia_1nf])
return db
db = zbuduj(True)
for t in ("klasy", "uczniowie", "ksiazki", "wypozyczenia"):
print("Tabela", t, "wierszy:", db.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0])
ODTWORZ = """SELECT u.uczen_id, u.imie, u.klasa, k.wychowawca, w.ksiazka_id, s.tytul, s.autor, w.data_wyp
FROM wypozyczenia w
JOIN uczniowie u ON u.uczen_id = w.uczen_id
JOIN klasy k ON k.klasa = u.klasa
JOIN ksiazki s ON s.ksiazka_id = w.ksiazka_id"""
odtworzone = db.execute(ODTWORZ).fetchall()
print("JOIN zwraca wierszy:", len(odtworzone), "| identyczne z tabelą wyjściową:", sorted(odtworzone) == sorted(wypozyczenia_1nf))
print("Po normalizacji zmiana wychowawcy 2B zmienia wierszy:",
db.execute("UPDATE klasy SET wychowawca = 'Marek Kos' WHERE klasa = '2B'").rowcount)
db.execute("INSERT INTO uczniowie VALUES (7, 'Lena', '3C')")
print("Po normalizacji nowy uczeń bez wypożyczenia: dodany")
db.execute("DELETE FROM wypozyczenia WHERE ksiazka_id = 106")
print("Po normalizacji książek w bazie po usunięciu wypożyczenia:", db.execute("SELECT COUNT(*) FROM ksiazki").fetchone()[0])
db.close()
# --- 3. Pułapka SQLite: klucze obce są domyślnie wyłączone ---
for wlacz in (False, True):
db = zbuduj(wlacz)
print("PRAGMA foreign_keys =", db.execute("PRAGMA foreign_keys").fetchone()[0])
try:
db.execute("INSERT INTO wypozyczenia VALUES (99, 101, '2026-10-27')")
print(" wypożyczenie dla nieistniejącego ucznia 99: PRZESZŁO")
except sqlite3.IntegrityError as e:
print(" wypożyczenie dla nieistniejącego ucznia 99: odrzucone ->", e)
db.close()
Wynik
Wynik z 29 października 2026 roku (Python 3.14.3, SQLite 3.50.4, Windows 11), wklejony bez zmian:
SQLite 3.50.4
Anomalia aktualizacji: zmiana wychowawcy 2B zmienia wierszy: 6
Anomalia wstawiania: nowy uczeń bez wypożyczenia odrzucony: NOT NULL constraint failed: wypozyczenia_1nf.ksiazka_id
Anomalia usuwania: książek w bazie przed 6 po usunięciu jednego wypożyczenia 5
2NF: zmiana wychowawcy 2B zmienia wierszy: 3
Klucz złożony bez NOT NULL przyjął wiersz: [(7, None, None)]
Tabela klasy wierszy: 3
Tabela uczniowie wierszy: 6
Tabela ksiazki wierszy: 6
Tabela wypozyczenia wierszy: 12
JOIN zwraca wierszy: 12 | identyczne z tabelą wyjściową: True
Po normalizacji zmiana wychowawcy 2B zmienia wierszy: 1
Po normalizacji nowy uczeń bez wypożyczenia: dodany
Po normalizacji książek w bazie po usunięciu wypożyczenia: 6
PRAGMA foreign_keys = 0
wypożyczenie dla nieistniejącego ucznia 99: PRZESZŁO
PRAGMA foreign_keys = 1
wypożyczenie dla nieistniejącego ucznia 99: odrzucone -> FOREIGN KEY constraint failed
Co pokazuje wynik
Zapytanie z trzema złączeniami (JOIN) zwraca dokładnie 12 wierszy, identycznych z tabelą wyjściową. Normalizacja nie zgubiła więc żadnej informacji. Trzy anomalie znikają: zmiana wychowawcy to jeden wiersz, nową uczennicę da się dodać, a książka zostaje w bazie po usunięciu wypożyczenia.
Dwie linie dotyczą samego SQLite. Bez PRAGMA foreign_keys = ON baza przyjęła wypożyczenie dla ucznia numer 99, którego nie ma. Z włączonym sprawdzaniem odrzuciła je komunikatem FOREIGN KEY constraint failed. Druga pułapka dotyczy klucza głównego. Dokumentacja polecenia CREATE TABLE przyznaje, że przez błąd we wczesnych wersjach SQLite przyjmuje puste wartości w większości kolumn klucza głównego, jeśli nie dopiszemy NOT NULL. Skrypt to potwierdza: wiersz (7, None, None) przeszedł. Dlatego kolumny klucza złożonego w naszym schemacie mają NOT NULL.
To samo w MySQL
Schemat działa w MySQL po drobnych zmianach typów, na przykład VARCHAR zamiast TEXT w kolumnach klucza. Według dokumentacji MySQL 8.4 klucze obce sprawdzają tabele InnoDB i NDB, a zmienna foreign_key_checks jest domyślnie włączona. Tego wariantu nie uruchamialiśmy, bo na tej maszynie nie ma serwera MySQL.
BCNF, 4NF i denormalizacja: kiedy przestać
Postać Boyce’a-Codda (BCNF) zaostrza 3NF: w każdej nietrywialnej zależności kolumna albo zestaw kolumn, od których zależą inne, musi jednoznacznie wskazywać wiersz, czyli być nadkluczem. Różnica wychodzi tylko w tabelach z kilkoma nakładającymi się kluczami. Nasza biblioteka w 3NF spełnia też BCNF, bo wszystkie zależności w każdej tabeli wychodzą od klucza. Czwarta postać normalna dotyczy tabel, w których jedna rzecz ma kilka niezależnych list wartości, na przykład uczeń z listą kółek zainteresowań i osobną listą języków.
Denormalizacja to świadome odejście od normalizacji, zwykle po to, żeby raport nie musiał łączyć wielu tabel. Robi się ją w tabelach raportowych i hurtowniach danych, gdzie dane się czyta, a rzadko poprawia. Normalizacja ogranicza powielanie danych i anomalie przy poprawkach, ale każde zapytanie o pełny obraz potrzebuje złączeń, więc nie zawsze przyspiesza bazę. W bazie aplikacji, w której dane często się zmieniają, warto zaczynać od 3NF i denormalizować tylko tam, gdzie pomiar pokaże wolne zapytanie.
Normalizacja a egzamin INF.03
Projektowanie tabel jest częścią kwalifikacji INF.03. W informatorze CKE dla zawodu technik programista jedna z jednostek efektów kształcenia nazywa się “Projektowanie i administrowanie bazami danych”. Wśród efektów stoi tam “tworzy relacyjne bazy danych zgodnie z projektem”. Samo słowo “normalizacja” w tym informatorze się nie pojawia, więc dokument nie mówi, w jakiej formie egzamin sprawdza to zagadnienie.
Jak wygląda cały egzamin, opisaliśmy w tekście o egzaminie INF.03. Przedmioty i egzaminy w obu zawodach są w przewodnikach po zawodach technik programista i technik informatyk.
FAQ
Ile jest postaci normalnych?
W podręcznikach opisuje się zwykle 1NF, 2NF, 3NF, BCNF, 4NF i 5NF, a część źródeł dodaje jeszcze szóstą postać normalną (6NF). W praktyce projektowania baz aplikacji najczęściej mówi się o pierwszych trzech i o BCNF.
Czym różni się druga postać normalna od trzeciej?
Druga postać normalna usuwa zależność od części klucza złożonego, a trzecia zależność między kolumnami spoza klucza. W naszym przykładzie 2NF wyprowadza tytuł książki do tabeli książek, bo zależy tylko od numeru książki. 3NF wyprowadza wychowawcę do tabeli klas, bo zależy od klasy.
Czy normalizacja spowalnia bazę danych?
Normalizacja ogranicza powielanie danych i anomalie przy poprawkach, ale zapytania o pełny obraz potrzebują złączeń tabel. Przy kluczach z indeksami i typowej bazie aplikacji ten koszt jest zwykle mały. Tam, gdzie pomiar pokaże wolne raporty, stosuje się denormalizację wybranych tabel.
Czy MySQL i SQLite pilnują kluczy obcych?
MySQL pilnuje kluczy obcych w tabelach InnoDB i NDB, a sprawdzanie jest tam domyślnie włączone. SQLite obsługuje klucze obce od wersji 3.6.19, ale domyślnie ich nie sprawdza. Każde połączenie musi włączyć sprawdzanie poleceniem PRAGMA foreign_keys = ON.
Podsumowanie
Normalizacja baz danych rozdziela fakty o różnych rzeczach do osobnych tabel, tak żeby każdy był zapisany raz. W przykładzie z biblioteki szkolnej 1NF rozłożyła listę książek na 12 wierszy. 2NF wydzieliła uczniów i książki, a 3NF klasy z wychowawcami. Zmiana wychowawcy jednej klasy spadła z sześciu poprawek do trzech, a potem do jednej. Zapytanie z JOIN oddało wszystkie 12 wierszy wyjściowych bez różnic. Przy pracy w SQLite trzeba pamiętać o dwóch rzeczach: włączyć PRAGMA foreign_keys = ON w każdym połączeniu i dopisać NOT NULL do kolumn złożonego klucza głównego. Skrypt nie wymaga zewnętrznych pakietów i działa w Pythonie 3.6 lub nowszym, a dane z biblioteki da się podmienić na własną tabelę.
Newsletter · DevstockAcademy & Kodożercy
Bądź na bieżąco ze światem IT, AI i automatyzacji
Co wtorek: newsy z branży, praktyczne tipy i narzędzia które warto znać. Zero spamu.



