Normalisierung von Datenbanken: 1NF, 2NF und 3NF an einem Beispiel
Normalisierung bedeutet, die Struktur einer Datenbank so umzubauen, dass jeder Fakt genau einmal gespeichert wird. Die Regeln der Normalisierung sind als Stufen beschrieben — Normalformen: Jede weitere Form beseitigt ihre eigene Art von Duplizierung. In der Praxis genügen in den allermeisten Fällen die ersten drei.
Gehen wir alle drei Formen an einem durchgängigen Beispiel durch, das den Weg von der „Alles-in-einem-Tabelle" zum sauberen Schema zurücklegt.
Der Ausgangspunkt: alles in einer Tabelle
Ein Essenslieferdienst speichert seine Bestellungen so:
Sieht kompakt aus, ist aber unbrauchbar:
- Cola-Verkäufe lassen sich nicht zählen — die Produkte sind zu einem String verklebt, es bleibt nur LIKE '%Cola%' und die Hoffnung, dass nie eine „Cola Zero" auftaucht.
- Anna hat eine neue Telefonnummer — aktualisiert werden müssen alle ihre Bestellungen. Eine Zeile übersehen, und die Datenbank enthält zwei verschiedene Nummern, ohne dass sich klären lässt, welche stimmt.
- Bestellung 2 wurde gelöscht — und mit ihr verschwand Boris samt Telefonnummer aus der Datenbank.
- Ein neuer Kunde ohne Bestellung — es gibt schlicht keinen Ort, ihn zu erfassen.
Diese Situationen heißen Änderungs-, Einfüge- und Löschanomalien. Genau dafür gibt es die Normalisierung: damit sie unmöglich werden.
1NF: ein Wert pro Zelle
Die erste Normalform verlangt Atomarität: Am Schnittpunkt von Zeile und Spalte steht ein unteilbarer Wert — keine Listen, keine Aufzählungen. Wir falten jede Bestellposition in eine eigene Zeile aus:
Cola-Verkäufe sind jetzt ein gewöhnliches GROUP BY. Eine Zeile wird eindeutig durch das Paar (order_id, product) bestimmt — den zusammengesetzten Schlüssel der Tabelle.
Doch die Duplizierung ist sogar gewachsen: Annas Name und Telefonnummer wiederholen sich in jeder Zeile jeder ihrer Bestellungen. Weiter geht's.
Eine Mitarbeitertabelle hat eine Spalte skills mit Werten wie 'SQL, Python, Excel'. Welche Normalisierungsregel ist verletzt?
2NF: ein Fakt hängt vom ganzen Schlüssel ab, nicht von einem Teil
Die zweite Normalform betrachtet Tabellen mit zusammengesetztem Schlüssel und verlangt: Jede Nicht-Schlüssel-Spalte muss vom gesamten Schlüssel (order_id, product) abhängen, nicht von einem Teil davon. Wir haben zwei Verstöße:
- customer und phone hängen nur von order_id ab — wer bestellt hat, ändert sich nicht mit dem Produkt in der Zeile;
- price hängt nur von product ab — der Preis der Pizza ist in allen Bestellungen derselbe.
Wir verlagern jeden Fakt in eine Tabelle, in der er von seinem Schlüssel vollständig abhängt:
MySQL 8.1CREATE TABLE orders ( order_id INT PRIMARY KEY, customer VARCHAR(100), phone VARCHAR(20) ); CREATE TABLE products ( product_id INT PRIMARY KEY, name VARCHAR(100), price DECIMAL(10,2) ); CREATE TABLE order_items ( order_id INT REFERENCES orders(order_id), product_id INT REFERENCES products(product_id), quantity INT, PRIMARY KEY (order_id, product_id) );
Der Produktpreis steht jetzt einmal in products, der Inhalt einer Bestellung sind kompakte Identifikator-Paare in order_items.
3NF: Nicht-Schlüssel-Spalten hängen nicht voneinander ab
Die dritte Normalform erledigt die letzte Duplizierung — transitive Abhängigkeiten, bei denen eine Nicht-Schlüssel-Spalte nicht vom Schlüssel, sondern von einer anderen Nicht-Schlüssel-Spalte abhängt. In der Tabelle orders ist genau das übrig: phone hängt von customer ab, nicht von der Bestellnummer. Annas Telefonnummer wiederholt sich immer noch in jeder ihrer Bestellungen.
Wir geben dem Kunden eine eigene Tabelle:
MySQL 8.1CREATE TABLE customers ( customer_id INT PRIMARY KEY, name VARCHAR(100), phone VARCHAR(20) ); CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT REFERENCES customers(customer_id), order_date TIMESTAMP );
Die klassische Formel bringt die 3NF auf den Punkt: Jede Nicht-Schlüssel-Spalte hängt ab „vom Schlüssel, vom ganzen Schlüssel und von nichts außer dem Schlüssel".
Eine Bestelltabelle enthält die Spalten city und city_zip, wobei die Postleitzahl eindeutig durch die Stadt bestimmt ist. Welche Form ist verletzt?
Das fertige Schema
Aus einer Alles-in-einem-Tabelle wurden vier verbundene Tabellen — das Schema lässt sich live erkunden:
Prüfen wir, was aus den Anomalien wurde: Annas Telefonnummer ändert sich an einer Stelle; das Löschen einer Bestellung rührt den Kunden nicht an; ein Kunde ohne Bestellung lebt ruhig in customers; Cola-Verkäufe werden über order_items gezählt — ganz ohne LIKE.

Wann Duplizierung gerechtfertigt ist: Denormalisierung
Normalisierung ist kein Selbstzweck, sondern ein Schutz der Daten vor dem Auseinanderlaufen. Manchmal weicht man bewusst von ihr ab:
- Momentaufnahme. Der Preis in products ändert sich, aber auf dem Beleg einer bezahlten Bestellung muss er so bleiben, wie er beim Kauf war. Deshalb bekommt order_items oft eine eigene price-Spalte — kein Fehler, sondern ein festgehaltener historischer Fakt.
- Vorberechnete Summen. Die Bestellsumme lässt sich jedes Mal aus den Positionen berechnen, aber bei Tausenden Anfragen pro Sekunde ist es günstiger, total_amount in orders zu speichern und bei Änderungen zu aktualisieren.
- Analytik. In Reporting-Schichten werden Daten absichtlich wieder in breite Tabellen „zurückgefaltet", damit Abfragen nicht ein Dutzend Joins zusammensetzen müssen.
Das Schlüsselwort ist bewusst: Denormalisierung bedeutet immer, dass für die Übereinstimmung der Duplikate jetzt Ihr Code verantwortlich ist, nicht das DBMS.
Wie weiter
- woraus eine solide Tabellenstruktur besteht — in der Lektion Struktur relationaler Datenbanken;
- die Primär- und Fremdschlüssel, auf denen die Beziehungen ruhen — in der Lektion Constraints;
- dem Entwurf von Schemata „vom Geschäftsproblem aus", inklusive Normalisierung, Denormalisierung und Datenhistorie, widmet sich ein ganzer Kurs — Datenbankdesign und Architektur.
Passende Artikel
SQL von Null: Schritt-für-Schritt-Lernplan für Einsteiger 2026
Eine 6-Wochen-Roadmap — vom ersten SELECT bis zum Vorstellungsgespräch
ACID: Die 4 Eigenschaften von Datenbank-Transaktionen einfach erklärt
Atomarität, Konsistenz, Isolation, Dauerhaftigkeit — am Beispiel einer Überweisung
ROW_NUMBER vs RANK vs DENSE_RANK in SQL: der Unterschied an einem Beispiel
Drei Ranking-Funktionen, eine Abfrage — und der Unterschied ist sichtbar