Normalisierung von Datenbanken hilft dir, Informationen so auf Tabellen zu verteilen, dass sie eindeutig gepflegt werden können. Eine Kundenadresse sollte zum Beispiel nicht in jeder Bestellposition erneut stehen. Sonst können Änderungen übersehen werden und widersprüchliche Daten entstehen. In diesem Leitfaden gehst du ein Bestellbeispiel Schritt für Schritt bis zur dritten Normalform (3NF) durch – mit den Regeln, den typischen Fehlern und einem ausführbaren SQL-Server-Beispiel.

Was bedeutet Normalisierung von Datenbanken?

Bei der Normalisierung untersuchst du, welche Fakten fachlich zusammengehören und wovon sie abhängen. Daraus entstehen Tabellen für eigenständige Dinge wie Kunden, Bestellungen und Artikel. Primärschlüssel identifizieren Datensätze; Fremdschlüssel verbinden die Tabellen. Das Ziel ist vor allem, unnötige Wiederholungen und Änderungsfehler zu vermeiden. Eine normalisierte Struktur ist allerdings nicht automatisch die schnellste für jede Abfrage: Leistung prüfst du später anhand echter Daten und Abfragen.

Eine funktionale Abhängigkeit bedeutet: Wenn ein Wert feststeht, ist dadurch ein anderer Wert eindeutig bestimmt. In unserem Beispiel bestimmt eine BestellungID genau ein Bestelldatum. Die Kombination aus BestellungID und PositionNr bestimmt eine konkrete Bestellposition. Solche fachlichen Regeln sind wichtiger als die zufälligen Werte in einem kleinen Beispieldatensatz.

Warum ist Normalisierung wichtig?

Eine schlecht aufgeteilte Tabelle kann drei typische Anomalien verursachen:

  • Änderungsanomalie: Der Kundenname steht in fünf Bestellpositionen. Eine Korrektur wird nur in vier Zeilen vorgenommen.
  • Einfügeanomalie: Ein neuer Artikel kann erst erfasst werden, wenn es eine Bestellung dafür gibt.
  • Löschanomalie: Mit der letzten Bestellposition eines Artikels verschwindet auch seine einzige Stammdatenbeschreibung.

Normalisierung senkt dieses Risiko, indem jedes Faktum an einem passenden Ort gespeichert wird. Schlüssel und Constraints sichern die Beziehungen zusätzlich ab.

1. Normalform (1NF): keine Listen in einer Spalte

Eine praktische Regel für die erste Normalform lautet: Eine Spalte enthält Werte eines festgelegten Typs, keine wiederholende Liste gleichartiger Angaben. Was als einzelner Wert gilt, hängt vom fachlichen Zweck ab. Eine vollständige Adresse kann für eine Anwendung ein Wert sein; wenn du regelmäßig nach Postleitzahl oder Ort filtern musst, sind getrennte Felder sinnvoll.

Diese Darstellung erschwert die Verarbeitung:

BestellungID Kunde Artikel
1001 Max Müller Tastatur, Maus

In Artikel stehen zwei Einträge als Textliste. Eine erste Verbesserung ist eine Zeile pro Position:

BestellungID PositionNr Kunde Artikel Menge
1001 1 Max Müller Tastatur 1
1001 2 Max Müller Maus 2

Die Kombination aus BestellungID und PositionNr unterscheidet hier die Positionen eindeutig. Ein ausdrücklich definierter Primärschlüssel ist für eine SQL-Tabelle sehr empfehlenswert; die theoretische 1NF-Regel wird jedoch nicht allein dadurch erfüllt, dass du eine PRIMARY KEY-Klausel hinzufügst. Die wiederholten Kundendaten sind weiterhin ein Problem.

2. Normalform (2NF): keine Abhängigkeit von einem Teil des Schlüssels

Die zweite Normalform setzt die 1NF voraus. Jedes Attribut, das nicht zu einem Kandidatenschlüssel gehört, soll vom gesamten Kandidatenschlüssel abhängen, nicht nur von einem Teil. Das wird bei zusammengesetzten Schlüsseln sichtbar. Hat eine Tabelle nur einen einspaltigen Kandidatenschlüssel, kann es bezogen auf diesen Schlüssel keine partielle Abhängigkeit geben.

Stell dir eine Positionstabelle mit dem Schlüssel (BestellungID, PositionNr) und den Spalten Bestelldatum, KundeID, ArtikelID, Menge und EinzelpreisBeiBestellung vor. Das Bestelldatum und die KundeID hängen bereits von BestellungID allein ab. Sie gehören deshalb in eine eigene Tabelle für Bestellungen. Menge und vereinbarter Einzelpreis gehören dagegen zur konkreten Position.

Tabelle nach der Aufteilung Schlüssel Weitere Spalten
Bestellungen BestellungID Bestelldatum, KundeID, Kundenname
Bestellpositionen BestellungID + PositionNr ArtikelID, Artikelname, Menge, EinzelpreisBeiBestellung

Damit ist die partielle Abhängigkeit der Bestellinformationen beseitigt. Die Tabellen sind aber noch nicht sauber in der 3NF: Kundenname und Artikelname werden weiterhin über andere Nichtschlüsselattribute bestimmt.

3. Normalform (3NF): transitive Abhängigkeiten auflösen

Die dritte Normalform setzt die 2NF voraus. Vereinfacht gesagt sollen beschreibende Nichtschlüsselattribute nicht über andere Nichtschlüsselattribute vom Schlüssel abhängen. In Bestellungen gilt beispielsweise BestellungID → KundeID → Kundenname. Der Kundenname gehört daher in die Kundentabelle. Entsprechend gilt in Bestellpositionen (BestellungID, PositionNr) → ArtikelID → Artikelname; der Artikelname gehört in die Artikeltabelle.

Das Ergebnis der Normalisierung von Datenbanken bis zur 3. Normalform besteht in diesem Beispiel aus vier Tabellen:

Tabelle Enthält Beziehung
Kunden KundeID, Kundenname Wird von Bestellungen referenziert
Artikel ArtikelID, Artikelname Wird von Bestellpositionen referenziert
Bestellungen BestellungID, KundeID, Bestelldatum KundeID verweist auf Kunden
Bestellpositionen BestellungID, PositionNr, ArtikelID, Menge, EinzelpreisBeiBestellung Verweist auf Bestellungen und Artikel

Wichtig: Den EinzelpreisBeiBestellung solltest du nicht automatisch in die Artikeltabelle verschieben. Er beschreibt den tatsächlich vereinbarten Preis dieser Position. Ein später geänderter Listenpreis darf die historische Bestellung nicht verändern. Ob weitere Angaben ausgelagert werden müssen, entscheidet die jeweilige Fachregel, nicht allein ein Tabellenname.

Normalisierung von Datenbanken als SQL-Server-Beispiel

Die folgenden T-SQL-Anweisungen bilden das 3NF-Beispiel mit Primär- und Fremdschlüsseln ab. Führe sie nur in einer Testdatenbank aus; sie erstellen vier Tabellen im Schema dbo. Wenn die Namen dort schon existieren, wähle andere Namen. Für andere Datenbanksysteme muss die Syntax angepasst werden.

CREATE TABLE dbo.NormKunden (
    KundeID int NOT NULL PRIMARY KEY,
    Kundenname nvarchar(100) NOT NULL
);

CREATE TABLE dbo.NormArtikel (
    ArtikelID int NOT NULL PRIMARY KEY,
    Artikelname nvarchar(100) NOT NULL
);

CREATE TABLE dbo.NormBestellungen (
    BestellungID int NOT NULL PRIMARY KEY,
    KundeID int NOT NULL,
    Bestelldatum date NOT NULL,
    CONSTRAINT FK_NormBestellungen_Kunden
        FOREIGN KEY (KundeID) REFERENCES dbo.NormKunden (KundeID)
);

CREATE TABLE dbo.NormBestellpositionen (
    BestellungID int NOT NULL,
    PositionNr int NOT NULL,
    ArtikelID int NOT NULL,
    Menge int NOT NULL CHECK (Menge > 0),
    EinzelpreisBeiBestellung decimal(12, 2) NOT NULL
        CHECK (EinzelpreisBeiBestellung >= 0),
    CONSTRAINT PK_NormBestellpositionen
        PRIMARY KEY (BestellungID, PositionNr),
    CONSTRAINT FK_NormPositionen_Bestellungen
        FOREIGN KEY (BestellungID)
        REFERENCES dbo.NormBestellungen (BestellungID),
    CONSTRAINT FK_NormPositionen_Artikel
        FOREIGN KEY (ArtikelID) REFERENCES dbo.NormArtikel (ArtikelID)
);

Die Kundendaten werden einmal gespeichert. Jede Bestellung verweist auf einen Kunden; jede Position auf eine Bestellung und einen Artikel. Die Positionsnummer ist nur innerhalb einer Bestellung eindeutig. Deshalb besteht der Primärschlüssel der Positionstabelle aus zwei Spalten. Die Microsoft-Dokumentation zu Primär- und Fremdschlüsseln beschreibt, wie SQL Server diese Beziehungen absichert. Beachte: Ein Fremdschlüssel erzeugt in SQL Server nicht automatisch einen passenden Index auf der referenzierenden Tabelle.

Wie frage ich die normalisierten Daten wieder ab?