Przejdź do treści
Programowanie i bazy danych

Normalizacja i denormalizacja baz danych — postacie normalne na przykładzie

Normalizacja baz danych krok po kroku: anomalie, 1NF, 2NF, 3NF i BCNF na jednym przykładzie. Do tego denormalizacja — kiedy ma sens i jak ją robić w SQL.

CZCzarek ZawolskiAktualizacja: 8 min czytania
Schemat relacyjnej bazy danych z tabelami połączonymi kluczami obcymi

W skrócie

  • Normalizacja to podział danych na tabele tak, by każdy fakt był zapisany w jednym miejscu — usuwa redundancję i anomalie aktualizacji, wstawiania i usuwania.
  • 1NF: wartości atomowe i brak powtarzających się grup; 2NF: brak zależności od części klucza złożonego; 3NF: brak zależności przechodnich.
  • W praktyce projektuje się bazy transakcyjne (OLTP) do 3NF lub BCNF — wyższe postacie normalne są potrzebne rzadko.
  • Denormalizacja to celowe powielenie danych dla szybszych odczytów: kolumny wyliczane, tabele podsumowań, widoki zmaterializowane, schemat gwiazdy w hurtowniach.
  • Denormalizuj dopiero po zmierzeniu problemu i zawsze zaplanuj, kto i jak utrzymuje spójność kopii.
Spis treści

Normalizacja baz danych to proces projektowania tabel tak, aby każda informacja była zapisana dokładnie w jednym miejscu. Dzięki temu zmiana adresu klienta czy ceny produktu wymaga aktualizacji jednego wiersza, a baza nie może „rozjechać się” na sprzeczne wersje tych samych danych. Robi się to, doprowadzając tabele do kolejnych postaci normalnych: 1NF, 2NF, 3NF i ewentualnie BCNF.

Denormalizacja jest procesem odwrotnym: świadomie powielasz dane albo łączysz tabele, żeby przyspieszyć odczyty. Oba podejścia nie są konkurencyjne — dobrze zaprojektowany system zwykle jest znormalizowany, a denormalizację stosuje punktowo tam, gdzie zmierzono problem z wydajnością. Poniżej pokazujemy obie techniki na jednym, prostym przykładzie sklepu internetowego.

Punkt wyjścia: tabela, która ma wszystkie problemy

Wyobraź sobie, że sklep zapisuje zamówienia w jednej tabeli, jak w arkuszu kalkulacyjnym:

nr_zamdataklientmiasto_klientaproduktyceny
1012026-09-01Anna NowakKrakówMysz, Klawiatura89, 199
1022026-09-02Jan KowalskiGdańskMonitor899
1032026-09-05Anna NowakKrakówMysz89

Taki układ działa przez chwilę, ale szybko pojawiają się trzy klasyczne anomalie:

  • Anomalia aktualizacji. Anna Nowak przeprowadza się do Warszawy. Trzeba zmienić miasto we wszystkich jej zamówieniach; jeśli pominiesz jedno, baza zawiera dwie sprzeczne informacje.
  • Anomalia wstawiania. Nie da się zapisać nowego produktu ani klienta, dopóki ktoś czegoś nie zamówi, bo nie istnieje dla nich wiersz.
  • Anomalia usuwania. Usunięcie zamówienia 102 kasuje jedyną informację o tym, że Jan Kowalski w ogóle był klientem i mieszka w Gdańsku.

Do tego dochodzi redundancja: nazwa i miasto klienta powtarzają się w każdym zamówieniu, a ceny produktów w każdym wierszu. Normalizacja usuwa te problemy krok po kroku.

Zależności funkcyjne — pojęcie, na którym opiera się normalizacja

Zależność funkcyjna A → B oznacza, że znając wartość A, jednoznacznie znasz wartość B. W naszym przykładzie id_klienta → miasto_klienta (klient ma jedno miasto), a id_produktu → nazwa_produktu.

Kilka pojęć, które pojawiają się w definicjach:

  • klucz kandydujący — minimalny zestaw kolumn jednoznacznie identyfikujący wiersz,
  • klucz główny — wybrany klucz kandydujący,
  • atrybut kluczowy — kolumna należąca do któregoś klucza kandydującego; pozostałe to atrybuty niekluczowe.

Każda postać normalna mówi, jakich zależności funkcyjnych w tabeli być nie może.

Postacie normalne baz danych — od 1NF do BCNF

Pierwsza postać normalna (1NF): wartości atomowe

Tabela jest w 1NF, gdy:

  1. każda komórka zawiera jedną, niepodzielną wartość (w kolumnie produkty nie może być listy „Mysz, Klawiatura”),
  2. nie ma powtarzających się grup kolumn typu produkt1, produkt2, produkt3,
  3. każdy wiersz da się jednoznacznie zidentyfikować kluczem.

Rozbijamy listy na osobne wiersze. Każdy produkt w zamówieniu to teraz jeden wiersz, a kluczem jest para (nr_zam, id_produktu):

nr_zamid_produktudataid_klientaklientmiastonazwa_produktucenailosc
10112026-09-017Anna NowakKrakówMysz891
10122026-09-017Anna NowakKrakówKlawiatura1991
10232026-09-028Jan KowalskiGdańskMonitor8991

Tabela jest w 1NF, ale redundancja wręcz wzrosła — data i klient powtarzają się w każdym wierszu zamówienia.

Druga postać normalna (2NF): cała zależność od całego klucza

Tabela jest w 2NF, gdy jest w 1NF i żaden atrybut niekluczowy nie zależy tylko od części klucza złożonego.

U nas klucz to (nr_zam, id_produktu), tymczasem:

  • data, id_klienta zależą tylko od nr_zam,
  • nazwa_produktu zależy tylko od id_produktu.

To zależności częściowe. Wydzielamy je do osobnych tabel:

  • zamowienia(nr_zam, data, id_klienta, klient, miasto),
  • produkty(id_produktu, nazwa, cena_katalogowa),
  • pozycje_zamowienia(nr_zam, id_produktu, ilosc, cena_jednostkowa).

Zwróć uwagę na cena_jednostkowa w pozycjach. To nie jest błąd ani denormalizacja: cena w chwili zakupu to inny fakt niż aktualna cena katalogowa. Gdyby pozycje odwoływały się tylko do produkty.cena_katalogowa, podwyżka ceny zmieniłaby wartość historycznych zamówień.

Tabela z kluczem jednokolumnowym jest w 2NF automatycznie, jeśli spełnia 1NF — zależność częściowa wymaga klucza złożonego.

Trzecia postać normalna (3NF): bez zależności przechodnich

Tabela jest w 3NF, gdy jest w 2NF i żaden atrybut niekluczowy nie zależy od innego atrybutu niekluczowego. Inaczej mówiąc: kolumny opisują klucz, cały klucz i nic poza kluczem.

W tabeli zamowienia mamy łańcuch nr_zam → id_klienta → klient, miasto. Miasto zależy od zamówienia tylko pośrednio, przez klienta — to zależność przechodnia. Wydzielamy klientów:

CREATE TABLE klienci (
    id_klienta  INT PRIMARY KEY,
    imie_nazwisko VARCHAR(100) NOT NULL,
    miasto      VARCHAR(80)
);

CREATE TABLE produkty (
    id_produktu     INT PRIMARY KEY,
    nazwa           VARCHAR(120) NOT NULL,
    cena_katalogowa NUMERIC(10,2) NOT NULL
);

CREATE TABLE zamowienia (
    nr_zam     INT PRIMARY KEY,
    data       DATE NOT NULL,
    id_klienta INT NOT NULL REFERENCES klienci(id_klienta)
);

CREATE TABLE pozycje_zamowienia (
    nr_zam           INT REFERENCES zamowienia(nr_zam),
    id_produktu      INT REFERENCES produkty(id_produktu),
    ilosc            INT NOT NULL CHECK (ilosc > 0),
    cena_jednostkowa NUMERIC(10,2) NOT NULL,
    PRIMARY KEY (nr_zam, id_produktu)
);

Teraz przeprowadzka klientki to jedna zmiana w jednym wierszu tabeli klienci, nowy produkt można dodać bez zamówienia, a usunięcie zamówienia nie kasuje danych klienta. Wszystkie trzy anomalie zniknęły.

Proces normalizacji baz danych — podział jednej tabeli na klientów, produkty, zamówienia i pozycje

BCNF, 4NF i 5NF — kiedy iść dalej

Postać normalna Boyce’a-Codda (BCNF) to zaostrzona 3NF: dla każdej nietrywialnej zależności X → Y, X musi być nadkluczem. Różnica wychodzi tylko w tabelach z kilkoma nakładającymi się kluczami kandydującymi — np. gdy prowadzący jednoznacznie wyznacza przedmiot, a para (student, przedmiot) wyznacza prowadzącego. Szczegółowy przykład znajdziesz w artykule o postaci normalnej Boyce’a-Codda.

Czwarta postać normalna (4NF) usuwa zależności wielowartościowe — sytuację, gdy w jednej tabeli trzymasz dwie niezależne listy (np. języki i umiejętności pracownika), co wymusza sztuczne kombinacje wierszy. Piąta (5NF) dotyczy zależności złączeniowych i w praktyce projektowania aplikacji pojawia się rzadko.

Dla typowej bazy transakcyjnej rozsądnym celem jest 3NF lub BCNF. Jeśli podczas projektowania trzymasz się zasady „jedna tabela = jeden rodzaj obiektu lub relacji”, najczęściej trafiasz tam naturalnie.

Denormalizacja: kiedy celowo łamać zasady

Znormalizowana baza wymaga złączeń. Raport „sprzedaż według miast w ostatnim roku” łączy cztery tabele i przelicza miliony pozycji. Przy dużym ruchu odczytów to może być za wolne — i wtedy wchodzi denormalizacja.

Najczęstsze techniki:

  1. Kolumna z zapisaną wartością wyliczaną — np. wartosc_zamowienia w tabeli zamowienia zamiast sumowania pozycji przy każdym wyświetleniu listy zamówień.
  2. Powielenie kolumny z innej tabeli — np. miasto kopiowane do zamówienia, żeby raport geograficzny nie wymagał złączenia. Uwaga: to ma sens tylko wtedy, gdy chcesz zachować miasto z chwili zakupu albo akceptujesz opóźnioną synchronizację.
  3. Tabele podsumowań i widoki zmaterializowane — wynik ciężkiego zapytania zapisany jako tabela, odświeżany cyklicznie.
  4. Schemat gwiazdy w hurtowni danych — tabela faktów (sprzedaż) i zdenormalizowane tabele wymiarów (klient, produkt, czas). Standard w systemach analitycznych.
  5. Dokumenty JSON — w bazach dokumentowych i kolumnach JSONB dane odczytywane zawsze razem trzyma się w jednym dokumencie.

Przykład widoku zmaterializowanego w PostgreSQL:

CREATE MATERIALIZED VIEW sprzedaz_wg_miast AS
SELECT k.miasto,
       date_trunc('month', z.data) AS miesiac,
       SUM(p.ilosc * p.cena_jednostkowa) AS przychod
FROM zamowienia z
JOIN klienci k            ON k.id_klienta = z.id_klienta
JOIN pozycje_zamowienia p ON p.nr_zam = z.nr_zam
GROUP BY k.miasto, date_trunc('month', z.data);

CREATE UNIQUE INDEX ON sprzedaz_wg_miast (miasto, miesiac);

-- odświeżanie bez blokowania odczytów (wymaga indeksu unikalnego)
REFRESH MATERIALIZED VIEW CONCURRENTLY sprzedaz_wg_miast;

Raport czyta teraz gotową, małą tabelę, a dane źródłowe pozostają znormalizowane. To zwykle najbezpieczniejsza forma denormalizacji, bo kopia jest jawnie oznaczona jako pochodna i wiadomo, jak ją odtworzyć.

Ważne: Każda zdenormalizowana kopia musi mieć właściciela: wyzwalacz, zadanie cykliczne albo kod aplikacji, który ją aktualizuje. Kopia bez mechanizmu synchronizacji to prosta droga do anomalii, przed którymi chroniła normalizacja.

Normalizacja czy denormalizacja — jak zdecydować

KryteriumNormalizacjaDenormalizacja
Typ systemuTransakcyjny (OLTP): sklep, CRM, system księgowyAnalityczny (OLAP), raporty, wyszukiwarki, cache
Przewaga operacjiDużo zapisów i aktualizacjiDużo odczytów, rzadkie zapisy
SpójnośćGwarantowana strukturą i kluczamiWymaga dodatkowych mechanizmów
Zajętość miejscaMniejszaWiększa
Złożoność zapytańWięcej złączeńProstsze, szybsze odczyty
Koszt zmian schematuNiższyWyższy — zmiana w wielu miejscach

Praktyczna kolejność działań:

  1. Zaprojektuj schemat w 3NF.
  2. Dodaj indeksy pod rzeczywiste zapytania — często rozwiązują problem bez żadnej denormalizacji.
  3. Zmierz wydajność (EXPLAIN ANALYZE w PostgreSQL, EXPLAIN w MySQL) na realistycznej ilości danych.
  4. Dopiero gdy konkretne zapytanie jest za wolne, zdenormalizuj punktowo — najlepiej widokiem zmaterializowanym lub tabelą podsumowań.

Normalizacja a wybór i utrzymanie bazy danych

Zasady normalizacji są takie same w PostgreSQL, MySQL/MariaDB, SQL Server czy Oracle — to część teorii modelu relacyjnego, a nie funkcja konkretnego produktu. Różnią się narzędzia do denormalizacji: nie każdy silnik ma widoki zmaterializowane, a bazy NoSQL, jak MongoDB, z założenia zachęcają do modelowania pod wzorce odczytu. Jeśli dopiero wybierasz silnik, zajrzyj do przeglądu najpopularniejszych systemów baz danych.

Dobra struktura to dopiero początek. W produkcyjnej bazie potrzebujesz też kontroli dostępu, kopii zapasowych i monitorowania — zarówno wydajności, jak i tego, kto i jakie dane odczytuje. O ochronie i monitorowaniu aktywności baz danych piszemy w tekście co to jest DAM (Database Activity Monitoring), a o ograniczaniu uprawnień użytkowników w artykule o RBAC i ABAC.

Najczęściej zadawane pytania

Co to jest normalizacja bazy danych?

To proces projektowania tabel relacyjnej bazy danych tak, aby usunąć nadmiarowe dane i zależności, które prowadzą do niespójności. Realizuje się go przez doprowadzanie tabel do kolejnych postaci normalnych: 1NF, 2NF, 3NF, BCNF.

Czym różni się normalizacja od denormalizacji?

Normalizacja dzieli dane na więcej tabel, by każdy fakt był zapisany raz. Denormalizacja robi odwrotnie: celowo powiela dane lub łączy tabele, aby przyspieszyć odczyty kosztem trudniejszego utrzymania spójności.

Do której postaci normalnej normalizować bazę?

Dla typowych baz transakcyjnych celem jest trzecia postać normalna lub postać Boyce’a-Codda. Czwarta i piąta postać normalna dotyczą rzadkich przypadków zależności wielowartościowych i złączeniowych.

Kiedy stosuje się denormalizację?

Gdy odczyty dominują nad zapisami, a zmierzone zapytania z wieloma złączeniami są za wolne: w raportach, hurtowniach danych, cache’ach i systemach analitycznych. W bazach transakcyjnych stosuje się ją punktowo.

Czy normalizacja danych to to samo co normalizacja bazy danych?

Nie zawsze. W statystyce i uczeniu maszynowym normalizacja danych oznacza skalowanie wartości liczbowych, np. do przedziału 0–1. W bazach danych chodzi o strukturę tabel i zależności między kolumnami.

CZ

Autor

Czarek Zawolski

Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.