Zum Inhalt springen

2. Funktionale Abhängigkeiten und Normalformen

Zu Zen-Modus wechseln

In der 3. Klasse ging es bei der Normalisierung vor allem darum, Tabellen bis zur dritten Normalform zu bringen und ein Gefühl dafür zu bekommen, warum Redundanz schadet. Hier schauen wir genauer hin: Wir formalisieren, warum eine Zerlegung nötig ist, mit dem Begriff der funktionalen Abhängigkeit, und gehen einen Schritt weiter bis zur Boyce-Codd-Normalform sowie zur vierten und fünften Normalform.

Eine funktionale Abhängigkeit beschreibt, dass ein Attribut ein anderes eindeutig bestimmt. Man schreibt A → B und liest: „A bestimmt B” oder „B ist funktional abhängig von A”. Kennt man den Wert von A, ist der Wert von B eindeutig festgelegt.

Ein Beispiel aus einer schlecht entworfenen Tabelle mit Bestellungen:

bestell_idkunde_idkunde_namekunde_plz
142Hofer9500
242Hofer9500
317Berger1010

Hier gilt:

  • kunde_id → kunde_name — zu einer Kundennummer gehört genau ein Name.
  • kunde_id → kunde_plz — und genau eine PLZ.
  • bestell_id → kunde_id — jede Bestellung gehört zu genau einem Kunden.

Die linke Seite einer funktionalen Abhängigkeit heißt Determinante. Ein Schlüssel ist eine Determinante, die alle Attribute der Relation bestimmt.

Aus wenigen funktionalen Abhängigkeiten folgen oft weitere, ohne dass man sie extra aufschreibt. Aus bestell_id → kunde_id und kunde_id → kunde_plz folgt zum Beispiel bestell_id → kunde_plz. Um systematisch zu bestimmen, was alles aus einer Attributmenge folgt, verwendet man die Attributhülle (englisch attribute closure).

Die Attributhülle einer Attributmenge X — geschrieben X⁺ — ist die Menge aller Attribute, die sich aus X mithilfe der funktionalen Abhängigkeiten bestimmen lassen. Anschaulich: „Wenn ich die Werte von X kenne, welche anderen Attribute liegen dann eindeutig fest?”

Man berechnet X⁺ mit einem einfachen Verfahren: Man beginnt mit X selbst und fügt so lange die rechten Seiten passender Abhängigkeiten hinzu, bis nichts Neues mehr dazukommt.

  1. Setze X⁺ := X (die Attribute, mit denen man startet).
  2. Suche eine funktionale Abhängigkeit A → B, deren linke Seite A bereits vollständig in X⁺ steckt.
  3. Ist das der Fall, nimm B in X⁺ auf.
  4. Wiederhole ab Schritt 2, bis sich X⁺ nicht mehr vergrößert.

Für die Bestelltabelle mit den Abhängigkeiten bestell_id → kunde_id, kunde_id → kunde_name und kunde_id → kunde_plz:

SchrittBetrachtete Abhängigkeit{bestell_id}⁺ danach
Start{ bestell_id }
1bestell_id → kunde_id{ bestell_id, kunde_id }
2kunde_id → kunde_name{ bestell_id, kunde_id, kunde_name }
3kunde_id → kunde_plz{ bestell_id, kunde_id, kunde_name, kunde_plz }

Jetzt sind alle Attribute der Relation enthalten — die Berechnung stoppt.

Die Attributhülle ist das Werkzeug, um Schlüssel zu prüfen und zu finden:

  • X ist ein Superschlüssel, wenn X⁺ alle Attribute der Relation enthält (X bestimmt die ganze Zeile).
  • X ist ein Kandidatenschlüssel, wenn X ein Superschlüssel ist und minimal — kein einziges Attribut lässt sich weglassen, ohne die Superschlüssel-Eigenschaft zu verlieren.

Im Beispiel enthält {bestell_id}⁺ alle Attribute, also ist bestell_id ein Superschlüssel und, weil er aus nur einem Attribut besteht, auch ein Kandidatenschlüssel. Dagegen ist {kunde_id}⁺ = { kunde_id, kunde_name, kunde_plz } — die bestell_id fehlt. kunde_id bestimmt also nicht die ganze Zeile und ist kein Schlüssel der Bestelltabelle.

Häufig lässt sich eine Menge funktionaler Abhängigkeiten vereinfachen, ohne ihre Aussage zu verändern. Eine kanonische Überdeckung (auch minimale Überdeckung) ist eine gleichwertige, aber möglichst kleine FD-Menge. Man erreicht sie in drei Schritten:

  1. Rechte Seiten vereinzeln: Jede Abhängigkeit auf genau ein Attribut rechts bringen (A → B, C wird zu A → B und A → C).
  2. Überflüssige Abhängigkeiten entfernen: Eine Abhängigkeit streichen, wenn sich ihre rechte Seite auch ohne sie noch aus der linken herleiten lässt (per Attributhülle geprüft).
  3. Linke Seiten reduzieren: Bei mehrattributigen linken Seiten prüfen, ob ein Attribut überflüssig ist.

Die kanonische Überdeckung ist die Grundlage für einen sauberen Datenbankentwurf: Sie zeigt die wesentlichen Abhängigkeiten ohne Ballast und wird beim Zerlegen in Normalformen gebraucht.

Die Tabelle oben speichert Name und PLZ eines Kunden bei jeder Bestellung erneut. Das führt zu drei klassischen Problemen, den Anomalien:

  • Einfügeanomalie: Ein neuer Kunde ohne Bestellung lässt sich nicht speichern, weil bestell_id als Schlüssel fehlt.
  • Änderungsanomalie: Zieht Kunde 42 um, muss die PLZ in jeder seiner Bestellzeilen geändert werden. Wird eine vergessen, widersprechen sich die Daten.
  • Löschanomalie: Löscht man die letzte Bestellung eines Kunden, verschwinden auch dessen Name und Adresse.

Alle drei entstehen aus derselben Ursache: In einer Tabelle stecken Fakten über verschiedene Dinge (Bestellungen und Kunden), die eigentlich getrennt gehören.

Normalisierung zerlegt eine Tabelle schrittweise, bis jede Tabelle nur noch Fakten über eine Sache enthält. Die Stufen bauen aufeinander auf:

  • 1. Normalform (1NF): Jedes Attribut enthält nur atomare Werte, keine Listen oder Wiederholgruppen.
  • 2. Normalform (2NF): 1NF und jedes Nicht-Schlüssel-Attribut hängt vom ganzen Schlüssel ab, nicht nur von einem Teil (relevant bei zusammengesetzten Schlüsseln).
  • 3. Normalform (3NF): 2NF und kein Nicht-Schlüssel-Attribut hängt von einem anderen Nicht-Schlüssel-Attribut ab (keine transitiven Abhängigkeiten).

Für die Bestelltabelle heißt das: kunde_name und kunde_plz hängen von kunde_id ab, und kunde_id ist kein Schlüssel der Bestellung, sondern nur ein Attribut. Das ist eine transitive Abhängigkeit (bestell_id → kunde_id → kunde_plz) und verletzt die 3NF. Die Lösung ist die Zerlegung in zwei Tabellen:

Bestellungen

bestell_idkunde_id
142
242
317

Kunden

kunde_idkunde_namekunde_plz
42Hofer9500
17Berger1010

Jetzt steht jeder Kundenfakt genau einmal. Alle drei Anomalien sind verschwunden.

Die BCNF ist eine strengere Fassung der 3NF. Sie verlangt: Bei jeder funktionalen Abhängigkeit A → B muss die Determinante A ein Schlüssel sein.

Die 3NF lässt in seltenen Fällen noch eine Ausnahme zu, nämlich wenn ein Attribut Teil eines Schlüssels ist. Genau diese Lücke schließt die BCNF. In der Praxis sind Tabellen, die in 3NF sind, meist auch in BCNF; der Unterschied wird nur bei mehreren überlappenden Kandidatenschlüsseln sichtbar.

1NF bis BCNF drehen sich alle um funktionale Abhängigkeiten. Die 4NF behandelt ein anderes Problem: zwei voneinander unabhängige mehrwertige Fakten in derselben Tabelle.

Beispiel: Eine Lehrkraft unterrichtet mehrere Fächer und spricht mehrere Sprachen. Beides hat nichts miteinander zu tun — welches Fach jemand unterrichtet, sagt nichts über die gesprochenen Sprachen aus. Trotzdem landet in einer einzigen Tabelle jede Kombination:

lehrerfachsprache
MaierMathematikDeutsch
MaierMathematikEnglisch
MaierPhysikDeutsch
MaierPhysikEnglisch

Maier unterrichtet zwei Fächer und spricht zwei Sprachen — und schon stehen vier Zeilen da (das kartesische Produkt 2 × 2). Kommt ein drittes Fach dazu, müssen sofort zwei weitere Zeilen ergänzt werden, eine je Sprache, obwohl das neue Fach mit den Sprachen gar nichts zu tun hat. Das ist eine Änderungsanomalie — und die Tabelle ist dabei schon in BCNF, weil der Schlüssel die ganze Zeile ist.

Man beschreibt das mit zwei mehrwertigen Abhängigkeiten (multivalued dependencies): lehrer ↠ fach und lehrer ↠ sprache, gelesen „zu einem Lehrer gehört eine ganze Menge von Fächern und, davon unabhängig, eine Menge von Sprachen”.

Die 4NF verlangt, dass eine Tabelle nicht zwei solche unabhängigen mehrwertigen Fakten mischt. Die Lösung ist die Aufteilung in zwei Tabellen:

lehrer_fach

lehrerfach
MaierMathematik
MaierPhysik

lehrer_sprache

lehrersprache
MaierDeutsch
MaierEnglisch

Jetzt steht jedes Fach und jede Sprache genau einmal, und ein neues Fach ist eine einzige neue Zeile.

Die 5NF ist der seltenste und abstrakteste Fall. Sie betrifft Tabellen, die sich nur dann verlustfrei zerlegen lassen, wenn man sie in drei (oder mehr) Tabellen aufteilt — eine Aufteilung in nur zwei genügt nicht.

Beispiel: Ein Vertreter verkauft Produkte bestimmter Firmen. Drei Zusammenhänge greifen ineinander: welche Firmen ein Vertreter vertritt, welche Produkte eine Firma herstellt und welche Produkte ein Vertreter überhaupt verkauft. In einer Tabelle sieht das so aus:

angebot — „Vertreter V verkauft Produkt P der Firma F”

vertreterfirmaprodukt
BergerAlphaTechLaptop
BergerAlphaTechMaus
BergerBetaSoftLaptop

Nun gelte die Geschäftsregel: Wenn ein Vertreter eine Firma vertritt, diese Firma ein Produkt herstellt und der Vertreter dieses Produkt führt, dann verkauft er dieses Produkt auch für diese Firma. Unter dieser Regel ist die dritte Zeile bereits durch die anderen Angaben festgelegt — die Tabelle enthält also redundante Information.

Die 5NF zerlegt in drei Tabellen, die jeweils nur einen der paarweisen Zusammenhänge festhalten:

  • vertreter_firma — wer vertritt welche Firma,
  • firma_produkt — welche Firma stellt welches Produkt her,
  • vertreter_produkt — wer führt welches Produkt.

Das Original bekommt man nur zurück, indem man alle drei Tabellen zusammen verbindet; eine Zerlegung in nur zwei Tabellen würde falsche Zeilen erzeugen. Diese Eigenschaft heißt Verbundabhängigkeit (join dependency), und die 5NF verlangt, dass sich jede solche Verbundabhängigkeit bereits aus den Schlüsseln ergibt.

Eine Zerlegung ist nur dann korrekt, wenn man die ursprüngliche Tabelle durch einen Join wieder exakt herstellen kann, ohne falsche Zeilen zu erzeugen. Das nennt man verlustfreie Zerlegung. Praktisch ist sie gesichert, wenn die beiden Teiltabellen über ein Attribut verbunden sind, das in einer der beiden Tabellen Schlüssel ist, im Beispiel kunde_id.

Verliert man diese Eigenschaft aus dem Blick, kann eine „normalisierte” Aufteilung beim Zusammenführen mehr Zeilen liefern als das Original. Deshalb prüft man nach jeder Zerlegung: Lässt sich das Original per Join sauber rekonstruieren?

Höhere Normalformen vermeiden Redundanz und Anomalien, erzeugen aber mehr Tabellen und damit mehr Joins. Für den operativen Betrieb (OLTP) ist das richtig. Für Auswertungen (OLAP, siehe nächstes Kapitel) wird bewusst denormalisiert, weil dort Lesegeschwindigkeit wichtiger ist als redundanzfreie Speicherung. Normalisierung ist also kein Selbstzweck, sondern eine Entscheidung im Kontext.

  • Eine funktionale Abhängigkeit A → B bedeutet, dass A den Wert von B eindeutig bestimmt; die linke Seite heißt Determinante.
  • Die Attributhülle X⁺ ist die Menge aller aus X bestimmbaren Attribute; mit ihr prüft man Superschlüssel (X⁺ enthält alle Attribute) und findet minimale Kandidatenschlüssel. Die kanonische Überdeckung ist die kleinste gleichwertige FD-Menge.
  • Anomalien (Einfügen, Ändern, Löschen) entstehen, wenn eine Tabelle Fakten über mehrere Dinge vermischt.
  • Die Normalformen 1NF–3NF beseitigen sie schrittweise; die BCNF verschärft die 3NF mit der Regel „jede Determinante ist ein Schlüssel”.
  • Die 4NF trennt zwei unabhängige mehrwertige Fakten (z. B. Fächer und Sprachen einer Lehrkraft), die 5NF behandelt seltene Fälle, die sich nur in drei oder mehr Tabellen verlustfrei zerlegen lassen.
  • Eine Zerlegung muss verlustfrei sein, also per Join exakt rekonstruierbar.
  • Für Analytik wird bewusst denormalisiert; Normalisierung ist eine Abwägung, kein Dogma.