Projektowanie baz danych
Model relacyjny i DBMS, klucze podstawowe, kandydujące i obce, integralność encji i referencyjna, relacje 1:1, 1:N i N:M, diagram ER, normalizacja do 3NF krok po kroku, typy danych MySQL, indeksy i ACID.
Wstęp: projekt bazy powstaje na kartce, nie w SQL-u
W części pisemnej ten dział daje pytania o klucze, typy relacji i postacie normalne. W części praktycznej dostajesz opis firmy albo gotowy diagram ER i masz z niego zrobić działającą bazę: tabele, klucze obce, typy kolumn. Nikt nie pyta „czym jest baza danych". Pyta, czy tabela jest w 2NF, ile tabel potrzeba na relację wiele-do-wielu i co się stanie z wypożyczeniami po usunięciu klienta. Cały ten artykuł prowadzimy na jednym przykładzie — bazie wypożyczalni filmów — żeby normalizację dało się zobaczyć, a nie tylko wykuć.
📋 Spis treści
Fundamenty modelu relacyjnego
1
Baza danych, DBMS i model relacyjny
Egzamin oddziela dwie rzeczy, które w mowie potocznej się zlewają: bazę danych
(zbiór danych) od systemu zarządzania bazą danych (program, który ten zbiór
obsługuje). MySQL nie jest bazą danych — MySQL jest DBMS-em, a baza to
wypozyczalnia,
którą w nim utworzysz.
Uporządkowany zbiór danych o określonej strukturze, zapisany trwale i opisany własnym schematem. Struktura jest tu słowem kluczowym: plik tekstowy z notatkami bazą nie jest, bo bez zgadywania nie da się w nim odróżnić jednego pola od drugiego.
Database Management System, po polsku system zarządzania bazą danych. Oprogramowanie pośredniczące między użytkownikiem a plikami danych: wykonuje zapytania, pilnuje więzów, zarządza uprawnieniami, obsługuje transakcje i kopie zapasowe. Przykłady: MySQL, MariaDB, PostgreSQL, Microsoft SQL Server, Oracle Database, SQLite.
Model relacyjny opisał w 1970 roku Edgar F. Codd i użył terminów matematycznych. W SQL-u te same rzeczy nazywają się inaczej. Egzamin używa obu zestawów zamiennie, więc trzeba je umieć sparować:
| Termin teoretyczny | Termin praktyczny | Przykład z wypożyczalni |
|---|---|---|
| relacja | tabela | tabela filmy |
| krotka | rekord, wiersz | jeden konkretny film |
| atrybut | pole, kolumna | kolumna tytul |
| dziedzina | typ danych wraz z zakresem dopuszczalnych wartości | rok produkcji: liczba całkowita z przedziału 1895–2100 |
| stopień relacji | liczba kolumn | 5 kolumn = stopień 5 |
| liczność (kardynalność) | liczba wierszy | 840 filmów w katalogu |
Pułapka słowna, na której łatwo stracić punkt
W teorii Codda relacja to tabela. W codziennej mowie
„relacja" to związek między tabelami, np. jeden-do-wielu.
To dwa różne znaczenia tego samego słowa. Gdy w zadaniu pada „relacja 1:N",
chodzi o związek. Gdy pada „relacyjny model danych" albo „schemat relacji" —
chodzi o tabelę. Rozstrzyga kontekst: jeśli obok stoi dwukropek z literami,
mowa o związku.
- Brak duplikatów wierszy. Każdy wiersz musi być odróżnialny od pozostałych — gwarantuje to klucz podstawowy.
- Kolejność nie ma znaczenia. Ani kolejność wierszy, ani kolumn nie niesie informacji. Jeśli potrzebujesz „pierwszego" rekordu, musisz mieć kolumnę, po której da się posortować.
- Wartość w komórce jest atomowa. Jedna komórka = jedna wartość. To już jest warunek pierwszej postaci normalnej.
2
Klucze i integralność danych
Klucz to kolumna albo zestaw kolumn pełniący rolę identyfikatora lub odsyłacza. Nazw jest kilka i różnią się jednym słowem, więc łatwo je pomylić — a zadanie „wskaż klucz obcy w podanym schemacie" to pewne punkty, jeśli wiesz, czego szukać.
Jednoznacznie identyfikuje wiersz. W tabeli może być tylko jeden. Musi być unikalny i nie może zawierać NULL. Automatycznie tworzy indeks.
Każda kolumna lub zestaw kolumn, który mógłby pełnić rolę klucza podstawowego, bo jest unikalny i minimalny. W tabeli klientów kandydatami są PESEL, numer dowodu i adres e-mail. Jednego wybierasz na podstawowy, pozostałym zwykle nadajesz UNIQUE.
Kolumna wskazująca na klucz podstawowy innej tabeli. W jednej tabeli może być ich wiele. Może przyjmować NULL, gdy powiązanie jest opcjonalne, i może się powtarzać — to właśnie on realizuje stronę „wiele" relacji.
Klucz podstawowy zbudowany z dwóch lub więcej kolumn, bo żadna pojedyncza nie identyfikuje wiersza. Typowe miejsce: tabela łącząca, gdzie kluczem jest para (id_wypozyczenia, id_filmu).
Naturalny pochodzi z rzeczywistości: PESEL, numer ISBN, numer rejestracyjny. Sztuczny (surogatowy) nie znaczy nic poza bazą — to zwykle kolumna id INT AUTO_INCREMENT.
W praktyce projektowej wybiera się sztuczny i tego uzasadnienia oczekuje egzamin. Powody są trzy: jest krótki (4 bajty zamiast 11 znaków), nigdy się nie zmienia (numer telefonu klienta owszem — a zmiana klucza podstawowego pociąga zmiany we wszystkich tabelach podrzędnych) i istnieje zawsze, także dla klienta, który nie chce podać PESEL-u.
Każdy wiersz musi być jednoznacznie identyfikowalny, czyli klucz podstawowy jest unikalny i różny od NULL. Bez tego nie da się wskazać konkretnego rekordu do aktualizacji ani usunięcia — DBMS nie wie, o który wiersz chodzi.
Wartość klucza obcego musi wskazywać na istniejący wiersz w tabeli nadrzędnej albo być równa NULL. Nie może istnieć wypożyczenie klienta numer 77, jeśli w tabeli klientów nie ma numeru 77. To dlatego DBMS blokuje usunięcie klienta, który ma wypożyczenia.
3
Relacje 1:1, 1:N i N:M
Typ związku rozpoznajesz metodą dwóch pytań zadanych w obie strony. „Ilu klientów może mieć jedno wypożyczenie?" — jednego. „Ile wypożyczeń może mieć jeden klient?" — wiele. Jeden i wiele, czyli 1:N. Odpowiedź „wiele" po obu stronach oznacza N:M i wymusza trzecią tabelę.
Jednemu wierszowi odpowiada dokładnie jeden wiersz w drugiej tabeli. Rzadka, bo zwykle takie dane można trzymać w jednej tabeli. Stosuje się ją, gdy część kolumn jest rzadko używana albo poufna, np. pracownicy i dane_placowe.
Realizacja: klucz obcy w jednej z tabel, dodatkowo oznaczony jako UNIQUE.
Najczęstsza relacja w bazach. Jeden klient ma wiele wypożyczeń, jeden reżyser nakręcił wiele filmów, jedna kategoria obejmuje wiele produktów.
Realizacja: klucz obcy trafia zawsze po stronie „wiele". Kolumna id_klienta stoi w tabeli wypożyczeń, nie odwrotnie.
Jeden film ma wielu aktorów, jeden aktor gra w wielu filmach. Jeden uczeń ma wielu nauczycieli, jeden nauczyciel wielu uczniów.
Realizacja: w modelu relacyjnym nie da się jej zapisać wprost. Trzeba dołożyć tabelę łączącą (asocjacyjną, pośredniczącą), która rozbija N:M na dwie relacje 1:N.
Tabela łącząca zawiera co najmniej dwa klucze obce — po jednym do każdej z łączonych tabel — i najczęściej to właśnie ich para tworzy złożony klucz podstawowy. Może też przechowywać atrybuty samego związku, np. rolę, jaką aktor zagrał w danym filmie:
CREATE TABLE obsada (
id_filmu INT NOT NULL,
id_aktora INT NOT NULL,
rola VARCHAR(80),
PRIMARY KEY (id_filmu, id_aktora),
FOREIGN KEY (id_filmu) REFERENCES filmy(id_filmu),
FOREIGN KEY (id_aktora) REFERENCES aktorzy(id_aktora)
) ENGINE = InnoDB;Klucz złożony (id_filmu, id_aktora) załatwia od razu dwie sprawy: identyfikuje wiersz i blokuje wpisanie tego samego aktora dwa razy do tego samego filmu. Jeśli jednak aktor ma zagrać w filmie dwie różne role, klucz trzeba poszerzyć o kolumnę rola albo wprowadzić klucz sztuczny — to typowe pytanie „uzasadnij wybór klucza".
4
Model ER i diagram związków encji
Model ER (Entity-Relationship, model związków encji) to szkic bazy robiony przed napisaniem pierwszego CREATE TABLE. Opisuje, co przechowujemy i jak to jest ze sobą powiązane — bez typów danych i bez SQL-a.
- Encja — obiekt, o którym gromadzimy dane. W notacji Chena prostokąt. Po implementacji staje się tabelą. Encje nazywamy rzeczownikami: Klient, Film, Wypożyczenie.
- Atrybut — cecha encji, w notacji Chena elipsa. Staje się kolumną. Atrybut będący kluczem podkreślamy.
- Związek — powiązanie encji, w notacji Chena romb podpisany czasownikiem („wypożycza", „zawiera").
- Liczebność (kardynalność) — informacja, ile wystąpień jednej encji odpowiada wystąpieniu drugiej: 1:1, 1:N, N:M.
- Encja słaba — nie ma własnego pełnego klucza i istnieje wyłącznie w powiązaniu z encją nadrzędną (pozycja wypożyczenia bez wypożyczenia nie ma sensu).
- Notacja Chena — nad linią związku pisze się symbole 1, N, M przy odpowiednich końcach.
- Notacja kurzej stopki (crow's foot) — najpopularniejsza w narzędziach typu MySQL Workbench. Rozwidlenie („kurza łapka") na końcu linii oznacza wiele, pojedyncza kreska oznacza jeden.
- Opcjonalność — kółko przy końcu linii znaczy „zero lub…", czyli powiązanie nieobowiązkowe (klucz obcy dopuszcza NULL). Druga kreska znaczy „dokładnie jeden", czyli powiązanie obowiązkowe.
- Zapis (min, max) — np. (0,N) czytamy tak: wystąpienie tej encji może nie mieć żadnego powiązania albo mieć ich wiele.
Tak wygląda schemat, do którego dojdziemy w następnej sekcji drogą normalizacji. PK to klucz podstawowy, FK to klucz obcy:
klienci rezyserzy
--------------------- ---------------------
PK id_klienta PK id_rezysera
imie nazwisko
nazwisko kraj
telefon |
email | 1
| |
| 1 | N
| ---------------------
| N filmy
--------------------- ---------------------
wypozyczenia PK id_filmu
--------------------- tytul
PK id_wypozyczenia rok_produkcji
FK id_klienta cena_doba
data_wypozyczenia FK id_rezysera
data_zwrotu |
status | 1
| |
| 1 |
| pozycje_wypozyczenia |
| ------------------------- |
+-- N PK,FK id_wypozyczenia |
PK,FK id_filmu N ---+
liczba_dni
Zestawienie relacji:
klienci 1 : N wypozyczenia -- FK id_klienta w wypozyczeniach
rezyserzy 1 : N filmy -- FK id_rezysera w filmach
wypozyczenia N : M filmy -- realizowane przez tabele laczaca
-- pozycje_wypozyczenia: dwie relacje
-- 1:N i klucz podstawowy zlozony- Analiza wymagań — wypisz z treści zadania rzeczowniki (kandydaci na encje) i czasowniki (kandydaci na związki).
- Model konceptualny — diagram ER: encje, atrybuty, związki i ich liczebność. Bez typów danych.
- Model logiczny — zamiana encji na tabele, związków na klucze obce, rozbicie N:M tabelą łączącą, normalizacja do 3NF.
- Model fizyczny — dobór typów kolumn, więzów, indeksów i silnika (dla kluczy obcych w MySQL musi to być InnoDB, nie MyISAM).
- Implementacja — polecenia DDL, czyli CREATE DATABASE i CREATE TABLE. Pełną składnię znajdziesz w artykule o języku zapytań SQL.
5
Normalizacja: 1NF, 2NF, 3NF krok po kroku
Normalizacja to porządkowanie tabel tak, żeby każdy fakt był zapisany dokładnie raz. Postacie normalne są kumulatywne: żeby tabela była w 3NF, musi wcześniej spełniać 1NF i 2NF. Poniżej jeden przykład przeprowadzony przez wszystkie trzy etapy — dokładnie tak, jak w zadaniu „doprowadź poniższą tabelę do trzeciej postaci normalnej".
| Postać | Warunek | Typowe naruszenie |
|---|---|---|
| 1NF | Każda komórka zawiera jedną, niepodzielną wartość; brak powtarzających się grup kolumn; każdy wiersz jest unikalny. | lista wartości w jednej komórce („Matrix, Incepcja") albo kolumny film_1, film_2, film_3 |
| 2NF | Jest w 1NF i każdy atrybut niekluczowy zależy od całego klucza podstawowego, a nie od jego części. | zależność częściowa: przy kluczu (nr_wyp, id_filmu) kolumna tytul zależy tylko od id_filmu |
| 3NF | Jest w 2NF i żaden atrybut niekluczowy nie zależy od innego atrybutu niekluczowego. | zależność przechodnia: telefon zależy od id_klienta, a ten nie jest kluczem tej tabeli |
| BCNF | Zaostrzona 3NF: każdy wyznacznik zależności funkcyjnej musi być kluczem kandydującym. | rzadko wymagana na egzaminie — warto znać nazwę i to, że jest silniejsza od 3NF |
Zdanie-wytrych, którym sprawdzisz 3NF w pięć sekund: „każdy atrybut zależy od klucza, całego klucza i tylko od klucza".
Wypożyczalnia prowadzi ewidencję w arkuszu kalkulacyjnym. Jeden wiersz to jedno wypożyczenie:
WYPOZYCZENIA (postac nieznormalizowana)
nr | klient | telefon | data | wypozyczone filmy
---+----------------+-------------+------------+---------------------------
1 | Anna Nowak | 601-100-200 | 2026-03-04 | Matrix (Wachowscy);
| | | | Incepcja (Nolan)
2 | Piotr Kowalski | 602-300-400 | 2026-03-04 | Incepcja (Nolan)
3 | Anna Nowak | 601-100-200 | 2026-03-09 | Dunkierka (Nolan);
| | | | Matrix (Wachowscy)Problemy widać gołym okiem. Nie da się zapytać „ile razy wypożyczono Matrixa", bo tytuły siedzą sklejone w jednej komórce. Telefon Anny jest w dwóch miejscach — po zmianie numeru trzeba poprawić oba wiersze, a jeśli poprawi się tylko jeden, baza zacznie sama sobie zaprzeczać.
Naruszona reguła: komórka wypozyczone filmy zawiera wiele wartości, czyli nie jest atomowa. Dodatkowo upchnięto w niej dwie różne informacje: tytuł i reżysera.
Co robimy: rozbijamy listę na osobne wiersze (jeden wiersz = jedna wypożyczona pozycja) i rozdzielamy tytuł od reżysera na osobne kolumny. Skoro numer wypożyczenia zaczyna się powtarzać, klucz podstawowy musi być złożony: (nr_wyp, id_filmu).
WYPOZYCZENIA_1NF PK: (nr_wyp, id_filmu)
nr_wyp | id_filmu | tytul | rezyser | id_kl | klient | telefon | data
-------+----------+-----------+-----------+-------+----------------+-------------+-----------
1 | 10 | Matrix | Wachowscy | 5 | Anna Nowak | 601-100-200 | 2026-03-04
1 | 11 | Incepcja | Nolan | 5 | Anna Nowak | 601-100-200 | 2026-03-04
2 | 11 | Incepcja | Nolan | 8 | Piotr Kowalski | 602-300-400 | 2026-03-04
3 | 12 | Dunkierka | Nolan | 5 | Anna Nowak | 601-100-200 | 2026-03-09
3 | 10 | Matrix | Wachowscy | 5 | Anna Nowak | 601-100-200 | 2026-03-09Tabela jest już w 1NF — da się na niej wykonać WHERE tytul = 'Matrix'. Ale redundancja urosła: dane Anny powtarzają się teraz w czterech wierszach. To normalne na tym etapie i właśnie dlatego 1NF to dopiero początek.
Naruszona reguła: klucz jest złożony, a atrybuty niekluczowe zależą tylko od jego części — to zależność częściowa.
- tytul i rezyser zależą wyłącznie od id_filmu — znając numer filmu, znasz tytuł, bez patrzenia na numer wypożyczenia.
- id_kl, klient, telefon i data zależą wyłącznie od nr_wyp.
Co robimy: wyprowadzamy każdą taką grupę do własnej tabeli, a w tabeli z kluczem złożonym zostawiamy tylko to, co naprawdę zależy od całej pary, czyli atrybuty samego związku.
FILMY PK: id_filmu WYPOZYCZENIA PK: nr_wyp
----------------------------- ------------------------------------------------------
id_filmu | tytul | rezyser nr_wyp | id_kl | klient | telefon | data
10 | Matrix | Wachowscy 1 | 5 | Anna Nowak | 601-100-200 | 2026-03-04
11 | Incepcja | Nolan 2 | 8 | Piotr Kowalski | 602-300-400 | 2026-03-04
12 | Dunkierka | Nolan 3 | 5 | Anna Nowak | 601-100-200 | 2026-03-09
POZYCJE_WYPOZYCZENIA
PK: (nr_wyp, id_filmu)
FK: nr_wyp -> WYPOZYCZENIA(nr_wyp)
FK: id_filmu -> FILMY(id_filmu)
-----------------------------
nr_wyp | id_filmu | liczba_dni
1 | 10 | 3
1 | 11 | 3
2 | 11 | 7
3 | 12 | 2
3 | 10 | 2Zwróć uwagę, że kolumna liczba_dni słusznie została w tabeli łączącej — zależy jednocześnie od wypożyczenia i od filmu, czyli od całego klucza.
Skrót do zapamiętania: jeśli klucz podstawowy jest jednokolumnowy, tabela w 1NF automatycznie spełnia 2NF — nie ma czego naruszyć, bo klucz nie ma części. Naruszenie 2NF występuje wyłącznie przy kluczu złożonym.
Naruszona reguła: zależność przechodnia — atrybut niekluczowy zależy od innego atrybutu niekluczowego.
- W tabeli WYPOZYCZENIA zachodzi łańcuch nr_wyp → id_kl → klient, telefon. Nazwisko i telefon nie są cechą wypożyczenia, tylko klienta.
- W tabeli FILMY nazwisko reżysera powtarza się przy każdym jego filmie. Gdybyśmy dołożyli kraj reżysera, powielałby się razem z nazwiskiem — to ten sam mechanizm.
Co robimy: wydzielamy tabele KLIENCI i REZYSERZY, a w tabelach pierwotnych zostawiamy same klucze obce.
KLIENCI PK: id_klienta REZYSERZY PK: id_rezysera
------------------------------------ ---------------------------------------
id_klienta | imie | nazwisko | telefon id_rezysera | nazwisko | kraj
5 | Anna | Nowak | 601-100-200 1 | Wachowscy | USA
8 | Piotr | Kowalski | 602-300-400 2 | Nolan | Wielka Brytania
FILMY PK: id_filmu, FK: id_rezysera WYPOZYCZENIA PK: nr_wyp, FK: id_klienta
------------------------------------- ---------------------------------------------
id_filmu | tytul | rok | id_rez nr_wyp | id_klienta | data_wyp | data_zwrotu
10 | Matrix | 1999 | 1 1 | 5 | 2026-03-04 | 2026-03-07
11 | Incepcja | 2010 | 2 2 | 8 | 2026-03-04 | NULL
12 | Dunkierka | 2017 | 2 3 | 5 | 2026-03-09 | NULL
POZYCJE_WYPOZYCZENIA PK: (nr_wyp, id_filmu) -- bez zmian od kroku 2Baza jest w 3NF. Numer telefonu Anny występuje teraz w jednym miejscu, nazwisko reżysera również. Każdy fakt zapisany dokładnie raz — to jest cel całej operacji, a nie liczba tabel sama w sobie.
Sprawdzaj po kolei i zatrzymaj się na pierwszym „nie":
- Czy w którejkolwiek komórce jest lista wartości albo czy występują kolumny
typu telefon1,
telefon2? Jeśli tak — tabela
nie jest nawet w 1NF.
- Czy klucz podstawowy jest złożony? Jeśli nie — 2NF spełniona z automatu.
Jeśli tak — sprawdź, czy każda kolumna niekluczowa potrzebuje obu części
klucza. Jeśli któraś nie potrzebuje — tabela jest
tylko w 1NF.
- Czy jakaś kolumna niekluczowa daje się wyliczyć z innej niekluczowej
(nazwisko z numeru klienta, nazwa miasta z kodu pocztowego, wartość pozycji
z ceny i ilości)? Jeśli tak — tabela jest tylko w 2NF.
- Jeśli wszystkie trzy sprawdzenia wypadły pomyślnie — 3NF.
6
Anomalie i denormalizacja
Anomalie to konkretne szkody, jakie wyrządza nieznormalizowana tabela. Egzamin pyta o nie wprost: „wymień anomalię, która wystąpi po usunięciu wiersza". Wszystkie trzy pokazujemy na tabeli sprzed normalizacji.
Nie da się dodać danych, bo brakuje niepowiązanej z nimi informacji. Do jednej wielkiej tabeli nie wpiszesz nowego filmu, którego nikt jeszcze nie wypożyczył — musiałbyś zostawić puste numer wypożyczenia i dane klienta, a numer wypożyczenia jest częścią klucza. Tak samo nie zarejestrujesz klienta, który jeszcze nic nie wypożyczył.
Ta sama informacja leży w wielu wierszach, więc zmiana wymaga poprawienia ich wszystkich naraz. Anna zmienia numer telefonu — trzeba zaktualizować cztery wiersze. Jeśli poprawisz trzy, baza zawiera dwa sprzeczne numery tej samej osoby i przestaje być spójna.
Usuwając jedną informację, tracisz przy okazji inną. Kasujesz wypożyczenie numer 2 — i razem z nim znika jedyny ślad po Piotrze Kowalskim. Baza traci dane, których nikt nie kazał usuwać.
Normalizacja ma swoją cenę: dane rozproszone po kilku tabelach trzeba przy odczycie sklejać złączeniami, a każdy JOIN kosztuje czas. Denormalizacja to celowe wprowadzenie kontrolowanej redundancji, żeby przyspieszyć odczyt.
- Kiedy ma sens: baza jest czytana wielokrotnie częściej, niż zapisywana (raporty, hurtownie danych, statystyki), a zapytanie wymaga złączenia wielu dużych tabel.
- Przypadek w pełni uzasadniony: na fakturze zapisuje się cenę produktu z dnia sprzedaży. To wygląda na redundancję, ale nią nie jest — cena w cenniku się zmienia, a kwota na wystawionym dokumencie musi zostać taka, jaka była. To po prostu dwa różne fakty.
- Inny typowy przykład: kolumna liczba_wypozyczen w tabeli filmów zamiast liczenia jej za każdym razem funkcją COUNT.
- Cena: wracają anomalie aktualizacji. Nadmiarowe dane trzeba synchronizować — wyzwalaczem albo w kodzie aplikacji. Bez tego licznik zacznie kłamać.
- Zasada na egzamin: najpierw normalizujesz do 3NF, dopiero potem, mając zmierzony problem z wydajnością, rozważasz odstępstwo. Denormalizacja „na wszelki wypadek" to po prostu zły projekt.
7
Typy danych MySQL i więzy integralności
Zadanie praktyczne prawie zawsze zawiera polecenie „utwórz tabelę o podanej strukturze, dobierając odpowiednie typy danych". Punktowany jest sensowny dobór, a nie wpisanie wszędzie VARCHAR(255).
| Typ | Rozmiar / zakres | Kiedy używać |
|---|---|---|
| INT | 4 bajty; od −2 147 483 648 do 2 147 483 647, a z UNSIGNED od 0 do 4 294 967 295 | klucze podstawowe i obce, liczniki, identyfikatory |
| TINYINT | 1 bajt; od −128 do 127 (UNSIGNED od 0 do 255) | małe liczniki, oceny, wiek, liczba dni wypożyczenia |
| VARCHAR(n) | tekst o zmiennej długości; limit wiersza to 65 535 bajtów. Zajmuje tyle, ile realnie wpisano, plus 1–2 bajty na długość | nazwiska, tytuły, adresy e-mail — teksty o różnej długości |
| CHAR(n) | stała długość od 0 do 255 znaków, krótsze wartości dopełniane spacjami | dane o niezmiennej długości: PESEL (11 znaków), kod pocztowy, symbol waluty |
| TEXT | do 65 535 bajtów, przechowywany poza wierszem; nie da się nadać mu wartości DEFAULT | opisy, streszczenia filmów, treść komentarza |
| DATE | 3 bajty, format RRRR-MM-DD, zakres od 1000-01-01 do 9999-12-31 | data urodzenia, data wypożyczenia — gdy godzina nie ma znaczenia |
| DATETIME | format RRRR-MM-DD GG:MM:SS, ten sam zakres lat co DATE | moment zdarzenia: logowanie, rejestracja zamówienia |
| TIMESTAMP | 4 bajty, zakres od 1970 do 2038 roku, uwzględnia strefę czasową | znacznik utworzenia lub ostatniej modyfikacji rekordu |
| DECIMAL(p,s) | liczba dokładna: p cyfr znaczących, z czego s po przecinku. DECIMAL(8,2) to wartości do 999 999,99 | pieniądze — ceny, kwoty, salda |
| FLOAT / DOUBLE | 4 / 8 bajtów, zapis przybliżony (zmiennoprzecinkowy) | wielkości fizyczne: temperatura, masa, współrzędne. Nigdy do pieniędzy |
| BOOLEAN | w MySQL to alias typu TINYINT(1): 0 to fałsz, każda inna wartość to prawda | flagi typu „czy zwrócony", „czy konto aktywne" |
| ENUM | jedna wartość z zamkniętej listy podanej przy tworzeniu kolumny; wewnętrznie zapisywana jako liczba | status wypożyczenia: 'aktywne', 'zwrocone', 'zalegle' |
- Czy to liczba, na której wykonuje się działania? Numer
telefonu i PESEL to nie liczby — mają wiodące zera i myślniki, a nikt
ich nie dodaje. Idą jako VARCHAR albo
CHAR.
- Czy to pieniądze? Zawsze
DECIMAL.
FLOAT zapisuje wartości w przybliżeniu
i po zsumowaniu setek pozycji rachunek przestaje się zgadzać o grosze.
- Czy długość tekstu jest stała? Stała —
CHAR, zmienna —
VARCHAR, bardzo długa i nieprzeszukiwana —
TEXT.
- Czy zbiór wartości jest zamknięty i krótki? Wtedy ENUM albo osobna tabela słownikowa z kluczem obcym. Tabela słownikowa jest elastyczniejsza — dodanie nowej wartości do ENUM wymaga przebudowy struktury tabeli.
- NOT NULL — kolumna nie może pozostać pusta. Pilnuje, żeby wypożyczenie zawsze miało klienta i datę.
- UNIQUE — wartości w kolumnie nie mogą się powtarzać, ale może wystąpić NULL (w MySQL nawet wielokrotnie). To główna różnica wobec klucza podstawowego, obok tego, że więzów UNIQUE może być w tabeli wiele.
- DEFAULT — wartość wpisywana, gdy polecenie INSERT pominie kolumnę, np. status DEFAULT 'aktywne'.
- CHECK — warunek, który musi być spełniony, np. CHECK (cena_doba >= 0). W MySQL jest egzekwowany od wersji 8.0.16; we wcześniejszych składnia była przyjmowana, ale ignorowana.
- AUTO_INCREMENT — automatyczne nadawanie kolejnego numeru. Tylko dla kolumny liczbowej będącej kluczem lub indeksem. Usunięcie rekordu nie zwalnia numeru: po skasowaniu wiersza 5 następny dostanie 6, w numeracji zostanie dziura i jest to zachowanie prawidłowe.
- PRIMARY KEY — połączenie UNIQUE i NOT NULL, jedno na tabelę.
- FOREIGN KEY — więz referencyjny między tabelami. W MySQL wymaga silnika InnoDB oraz zgodnych typów kolumn po obu stronach.
CREATE DATABASE wypozyczalnia
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
USE wypozyczalnia;
CREATE TABLE klienci (
id_klienta INT AUTO_INCREMENT PRIMARY KEY,
imie VARCHAR(30) NOT NULL,
nazwisko VARCHAR(50) NOT NULL,
telefon VARCHAR(15),
email VARCHAR(100) UNIQUE,
data_rej DATE NOT NULL
) ENGINE = InnoDB;
CREATE TABLE rezyserzy (
id_rezysera INT AUTO_INCREMENT PRIMARY KEY,
nazwisko VARCHAR(60) NOT NULL,
kraj VARCHAR(40)
) ENGINE = InnoDB;
CREATE TABLE filmy (
id_filmu INT AUTO_INCREMENT PRIMARY KEY,
tytul VARCHAR(120) NOT NULL,
rok_produkcji SMALLINT,
cena_doba DECIMAL(6,2) NOT NULL DEFAULT 5.00,
opis TEXT,
id_rezysera INT,
CHECK (cena_doba >= 0),
FOREIGN KEY (id_rezysera) REFERENCES rezyserzy(id_rezysera)
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE = InnoDB;
CREATE TABLE wypozyczenia (
id_wypozyczenia INT AUTO_INCREMENT PRIMARY KEY,
id_klienta INT NOT NULL,
data_wypozyczenia DATE NOT NULL,
data_zwrotu DATE,
status ENUM('aktywne','zwrocone','zalegle') NOT NULL DEFAULT 'aktywne',
FOREIGN KEY (id_klienta) REFERENCES klienci(id_klienta)
ON DELETE RESTRICT
ON UPDATE CASCADE
) ENGINE = InnoDB;
CREATE TABLE pozycje_wypozyczenia (
id_wypozyczenia INT NOT NULL,
id_filmu INT NOT NULL,
liczba_dni TINYINT UNSIGNED NOT NULL DEFAULT 3,
PRIMARY KEY (id_wypozyczenia, id_filmu),
FOREIGN KEY (id_wypozyczenia) REFERENCES wypozyczenia(id_wypozyczenia)
ON DELETE CASCADE,
FOREIGN KEY (id_filmu) REFERENCES filmy(id_filmu)
ON DELETE RESTRICT
) ENGINE = InnoDB;Kolejność tworzenia tabel nie jest dowolna: tabela z kluczem obcym musi powstać po tabeli, na którą wskazuje. Przy usuwaniu kolejność jest odwrotna — najpierw tabele podrzędne. To najczęstsza przyczyna błędu przy odtwarzaniu bazy ze zrzutu.
8
Indeksy, akcje referencyjne i transakcje
Trzy tematy, które kończą projekt bazy: przyspieszenie odczytu, decyzja o losie rekordów podrzędnych i zabezpieczenie operacji, które muszą się wykonać w całości albo wcale.
Indeks to dodatkowa struktura (najczęściej B-drzewo) trzymająca posortowane wartości kolumny wraz ze wskaźnikami do wierszy. Działa jak skorowidz w książce: zamiast przeglądać wszystkie strony, sprawdzasz w spisie i idziesz prosto pod właściwy numer. Bez indeksu DBMS wykonuje pełne przeglądanie tabeli (full table scan) — przy milionie wierszy sprawdza milion wierszy.
- kolumna często pojawia się w WHERE, JOIN lub ORDER BY;
- ma dużo różnych wartości, czyli wysoką selektywność: nazwisko, e-mail, numer;
- tabela jest duża i głównie czytana.
- tabela jest intensywnie zapisywana — każdy INSERT, UPDATE i DELETE musi zaktualizować także indeksy;
- kolumna ma mało różnych wartości (płeć, status z trzema opcjami) — optymalizator i tak go pominie;
- indeksów jest zbyt wiele — rosną rozmiar bazy i czas zapisu, a zysk przy odczycie żaden.
Pamiętaj: PRIMARY KEY i UNIQUE tworzą indeks automatycznie — nie trzeba go zakładać osobno. Ręcznie zakłada się go poleceniem CREATE INDEX idx_nazwisko ON klienci(nazwisko);
Określają, co DBMS zrobi z wierszami podrzędnymi, gdy skasujesz lub zmienisz wiersz nadrzędny. Te same słowa kluczowe działają w obu klauzulach:
| Akcja | Skutek przy usunięciu rekordu nadrzędnego | Kiedy stosować |
|---|---|---|
| CASCADE | Usuwa także wszystkie powiązane rekordy podrzędne. Kasujesz wypożyczenie — znikają jego pozycje. | gdy rekord podrzędny nie ma sensu bez nadrzędnego (encja słaba) |
| SET NULL | Wpisuje NULL do kolumny klucza obcego. Usuwasz reżysera — filmy zostają, tylko bez przypisanego reżysera. | gdy powiązanie jest opcjonalne; kolumna FK nie może mieć NOT NULL |
| RESTRICT | Blokuje operację i zgłasza błąd, dopóki istnieją rekordy podrzędne. Nie usuniesz klienta, który ma wypożyczenia. | zachowanie domyślne i najbezpieczniejsze; chroni przed przypadkową utratą danych |
| NO ACTION | W MySQL na silniku InnoDB zachowuje się tak samo jak RESTRICT. | zapis zgodny z brzmieniem standardu SQL |
| SET DEFAULT | Ma wpisać wartość domyślną, ale silnik InnoDB tej akcji nie realizuje. | wymieniaj przy wyliczaniu akcji, nie stosuj w MySQL |
Częsty duet w praktyce: ON DELETE RESTRICT ON UPDATE CASCADE — nie pozwól skasować rekordu nadrzędnego, ale jeśli zmieni się jego identyfikator, przepisz zmianę do rekordów podrzędnych automatycznie.
Transakcja to ciąg operacji traktowany jako jedna niepodzielna całość. Klasyczny przykład to przelew: odjęcie kwoty z jednego konta i dopisanie jej do drugiego. Gdyby po pierwszej operacji zabrakło prądu, pieniądze by wyparowały. Transakcja temu zapobiega.
START TRANSACTION;
INSERT INTO wypozyczenia (id_klienta, data_wypozyczenia)
VALUES (5, '2026-03-04');
INSERT INTO pozycje_wypozyczenia (id_wypozyczenia, id_filmu, liczba_dni)
VALUES (LAST_INSERT_ID(), 10, 3);
COMMIT; -- zatwierdza obie zmiany na trwale
-- ROLLBACK; -- wycofalby obie, gdyby druga operacja sie nie powiodla- A — Atomicity (atomowość): wykonuje się całość albo nic. Nie ma stanu „w połowie".
- C — Consistency (spójność): baza przed transakcją i po niej spełnia wszystkie więzy integralności.
- I — Isolation (izolacja): równolegle wykonywane transakcje nie widzą swoich niezatwierdzonych zmian i nie psują sobie wyników.
- D — Durability (trwałość): po COMMIT dane przetrwają awarię zasilania — są już zapisane na nośniku, a nie tylko w pamięci.
W MySQL transakcje i klucze obce obsługuje silnik InnoDB. Starszy MyISAM nie obsługuje ani jednego, ani drugiego — to typowe pytanie „który silnik wybierzesz i dlaczego".
- Klucz obcy po złej stronie relacji 1:N. Zawsze trafia do tabeli po stronie „wiele". Kolumna id_klienta stoi w wypożyczeniach, a nie kolumna id_wypozyczenia w klientach — inaczej klient mógłby mieć tylko jedno wypożyczenie.
- Zapisanie relacji N:M w dwóch tabelach. Model relacyjny tego nie obsługuje. Zawsze trzy tabele: dwie główne i jedna łącząca z dwoma kluczami obcymi.
- Mylenie UNIQUE z PRIMARY KEY. UNIQUE dopuszcza NULL i może wystąpić w tabeli wielokrotnie. PRIMARY KEY jest jeden i nie dopuszcza NULL.
- Twierdzenie, że tabela z kluczem jednokolumnowym narusza 2NF. Nie może — naruszenie 2NF wymaga klucza złożonego. Przy kluczu pojedynczym przechodzisz od razu do sprawdzania 3NF.
- Zatrzymanie normalizacji na rozbiciu komórki. Rozbicie listy na wiersze to dopiero 1NF. Zadanie zwykle każe dojść do 3NF, więc trzeba jeszcze wydzielić tabele słownikowe.
- FLOAT do kwot pieniężnych. Zapis przybliżony, sumy przestają się zgadzać. Ceny i salda zawsze w DECIMAL.
- Numer telefonu i PESEL jako INT. Giną wiodące zera, a myślników i tak nie da się zapisać. To ciągi znaków, nie liczby.
- ON DELETE SET NULL przy kolumnie NOT NULL. Sprzeczność — baza nie przyjmie takiej definicji. SET NULL wymaga kolumny dopuszczającej NULL.
- Klucze obce na silniku MyISAM. Zostaną przyjęte składniowo, ale nie będą pilnowane. Do bazy z więzami wybierasz InnoDB.
Normalizacja wchodzi do głowy dopiero wtedy, gdy przerobisz kilka tabel samodzielnie i przestaniesz się zastanawiać, po której stronie postawić klucz obcy. Rozwiąż test z tego działu i zobacz, które reguły jeszcze uciekają.