Normalisieren einer Datenbank

Einführung

Wie wir in der Einführung des Abschnitts Daten in Tabellen gesehen haben, kann ein schlechtes Datenbankschema Redundanzen verursachen. Durch diese wiederum können Anomalien entstehen.

Um solche Redundanzen zu vermeiden, wurden die sogenannten Normalformen entwickelt, die jedes Datenbankschema erfüllen sollte.
Wenn wir also ein Schema aufgrund eines Entity-Relationship-Diagramms entworfen haben oder einen Vorschlag für ein Schema vorliegen haben, sollten wir untersuchen, ob diese Normalformen erfüllt werden. Ist dies nicht der Fall, muss das Schema angepasst werden.

In der Schule lernen wir die drei Normalformen kennen. Es gibt noch weitere, die man zum Beispiel bei Wikipedia finden kann.

Erste Normalform


Wiedergabe stellt eine Verbindung zu YouTube her.

Ein lokaler Basketballverein verwaltet die Daten seiner Mannschaften mittels einer Tabelle. Der Verein besitzt eine erste, zweite und dritte Mannschaft, die von verschiedenen Trainern betreut werden. Jeder Trainer betreut nur eine Mannschaft. Innerhalb einer Mannschaft hat jeder Spieler eine feste Trikotnummer. Damit eignet sich folgende Tabelle zur Mitgliederverwaltung:

TrikotNrNameAdresseOrtMannschaftTrainer
01SiegfriedRheinallee 240541 DüsseldorfersteMoritz
02GuntherRingweg 4140546 DüsseldorfersteMoritz
03HagenSchusterweg 340672 MeerbuschersteMoritz
01HaleBibelweg 140543 DüsseldorfzweiteArthur
02JohnScheunenweg 440670 MeerbuschzweiteArthur
03ParisSchusterweg 340541 DüsseldorfzweiteArthur
01NikSchusterweg 1240672 MeerbuschdritteKlaus

Angenommen, man möchte nun alle Mitglieder heraussuchen, die in Meerbusch wohnen. Dies ist sicherlich mithilfe der Spalte Ort möglich, allerdings könnte es etwas unübersichtlich sein, da in dieser Spalte nicht nur der Ort, sondern auch die Postleitzahl eingetragen ist. Das Attribut Ort ist also aus verschiedenen Angaben zusammengesetzt. Etwas schlauer ist vielleicht die folgende Aufteilung der Tabelle:

TrikotNrNameStraßeHNr.PLZOrtMannschaftTrainer
01SiegfriedRheinallee240541DüsseldorfersteMoritz
02GuntherRingweg4140546DüsseldorfersteMoritz
 

Nun haben wir die beiden Attribute PLZ und Ort. Diese sind nicht mehr zusammengesetzt sondern, wie man sagt, atomar.

Ebenso wurde das Attribut Adresse in die beiden atomaren Attribute Straße und Hausnummer zerlegt. In Abituraufgaben wird dies aber nicht immer einheitlich gemacht. D.h., manchmal wird die Adresse nicht zerlegt.

Auf diese Weise überführt man die Tabelle in die erste Normalform. Allgemein formuliert man diese wie folgt:

Ein Datenbankschema befindet sich in der 1. Normalform, wenn alle Attribute nur atomare Werte annehmen können.

Zweite Normalform


Wiedergabe stellt eine Verbindung zu YouTube her.

Funktionale Abhängigkeit

Betrachten wir die Mitgliederdaten weiter, so stellen wir fest, dass die Tabelle einige Redundanzen aufweist. Zum Beispiel ist es vollkommen überflüssig, dass der Trainer in jeder Zeile angegeben wird. Es fällt auch auf, dass der Eintrag in der Spalte Trainer natürlich nur von dem Eintrag in der Spalte Mannschaft abhängt. Mit anderen Worten: Kennen wir den Wert des Attributs Mannschaft, so können wir sofort auf den Wert des Attributs Trainer schließen.
Man sagt, das Attribut Trainer ist funktional abhängig vom Attribut Mannschaft. Allgemein formuliert man dies wie folgt:

Gegeben sei eine Tabelle mit Attributen A und B. Kann man aus dem Wert des Attributs A stets eindeutig auf den Wert des Attributs B schließen, so sagt man, das Attribut B ist funktional abhängig von Attribut A.
Man schreibt dies kurz A ⟶ B.

In unserem Beispiel gilt also Mannschaft ⟶ Trainer. Außerdem liegt die weitere funktionale Abhängigkeit PLZ ⟶ Ort vor, denn aus einer Postleitzahl können wir immer auf dem Ort schließen.

Die Abhängigkeit muss aber ganz allgemein gültig im folgenden Sinn sein. In unserer Tabelle ist es zufällig so, dass alle Spieler mit der Trikotnummer 3 in Schusterweg 3 wohnen. Das bedeutet aber nicht, dass hier eine funktionale Abhängigkeit vorliegt! Es kann ja jederzeit passieren, dass es einen Spieler mit der Nummer 3 gibt, der woanders wohnt.

Man kann nun nicht nur die funktionale Abhängigkeit zwischen einzelnen Attributen betrachten, sondern auch untersuchen, ob man bei Kenntnis der Werte einer gewissen Attributkombination auf die Werte eines anderen Attributs schließen kann. Zum Beispiel wurde eingangs erklärt, dass jeder Spieler eindeutig durch die Mannschaft und seine Trikotnummer festgelegt ist. Kennt man also die Werte dieser beiden Attribute, so kann man zum Beispiel auf den Namen des Spielers schließen. Dies können wir so festhalten:

{TrikotNr, Mannschaft} ⟶ Name.

Allgemein formuliert man dies so:

Gegeben sei eine Tabelle mit Attributen {A1, A2,…, An} und B. Kann man aus der Wertekombination der Attribute {A1, A2,…, An} stets eindeutig auf den Wert des Attributs B schließen, so sagt man, das Attribut B ist funktional abhängig von der Attributkombination {A1, A2,…, An}.
Man schreibt dies kurz {A1, A2,…, An} ⟶ B.

Volle funktionale Abhängigkeit

Eine weitere funktionale Abhängigkeit, die man so findet, ist diese:

{TrikotNr, Mannschaft} ⟶ Trainer

Wenn man diese genau betrachtet, fällt aber auf, dass das Attribut TrikotNr auf der linken Seite unnötig ist, denn schließlich gilt bereits Mannschaft} ⟶ Trainer.
Im Gegensatz dazu, kann man bei der funktionalen Abhängigkeit {TrikotNr, Mannschaft} ⟶ Name auf der linken Seite keines der beiden Attribute weglassen, denn alleine aus der Trikotnummer oder alleine aus der Mannschaft kann man nicht auf den Namen eines Spielers schließen.

Diesen Unterschied macht man deutlich, indem man sagt, dass Attribut Name ist voll funktional abhängig von der Attributkombination{TrikotNr, Mannschaft}.
Dies schreibt man {TrikotNr, Mannschaft} ⟹ Name.

Das Attribut Trainer ist von der Attributkombination {TrikotNr, Mannschaft} zwar funktional abhängig, aber nicht voll funktional abhängig.

Formulieren wir auch dies allgemein:

Gegeben sei eine Tabelle mit Attributen {A1, A2,…, An} und B. Ist B funktional abhängig von {A1, A2,…, An} aber von keiner Teilmenge von {A1, A2,…, An}, so sagt man, das Attribut B ist voll funktional abhängig von der Attributkombination {A1, A2,…, An}.
Mit anderen Worten: Man kann aus gegebenen Werten von {A1, A2,…, An} stets eindeutig auf den Wert von B schließen. Ist jedoch für eines der Attribute {A1, A2,…, An} der Wert unbekannt, so dann man nicht auf den Wert von B schließen.
Man schreibt: {A1, A2,…, An} ⟹ B

Formulierung der 2. Normalform

Betrachten wir noch einmal die nicht volle funktionale Abhängigkeit {TrikotNr, Mannschaft} ⟶ Trainer. Hier sind zwei Punkte zu beobachten:

  • Es ist klar, dass das Attribut Trainer von der Attributkombination {TrikotNr, Mannschaft} funktional abhängig ist, denn schließlich handelt es sich bei dieser Attributkombination um den Primärschlüssel der Tabelle.
  • Die Tatsache, dass das Attribut Trainer bereits von dem Attribut Mannschaft funktional abhängig ist, ist Ursache für Redundanzen in der Tabelle, wie man bei Betrachtung der letzten beiden Spalten klar erkennt. Diese Redundanzen können zu Update-Anomalien führen.

Aufgrund der zweiten Beobachtung möchte man vermeiden, dass ein Attribut bereits von einem Teil des Primärschlüssels abhängig ist. Dies liefert die Motivation für die 2. Normalform.

Ein Datenbankschema befindet sich in der 2. Normalform, wenn es bereits in der 1. Normalform ist und zusätzlich für jede Tabelle gilt: Alle Attribute, die selbst nicht Teil des Primärschlüssels sind, sind voll funktional abhängig vom Primärschlüssel.

Beachte, dass hier ausdrücklich verlangt wird, dass auch die 1. Normalform vorliegt.

Um unser Beispiel in die 2. Normalform zu überführen, müssen wir eine zweite Tabelle einführen:

Spieler

TrikotNrNameStraßeHNr.PLZOrtMannschaft
01SiegfriedRheinallee240541Düsseldorferste
02GuntherRingweg4140546Düsseldorferste
 

Coaches

MannschaftTrainer
ersteMoritz
ersteMoritz

Oder kompakt geschrieben:

Spieler(TrikotNr, Name, Straße, HNr., PLZ, Ort, ↑Mannschaft)
Coaches(Mannschaft, Trainer)

Dritte Normalform


Wiedergabe stellt eine Verbindung zu YouTube her.

Transitive Abhängigkeit

Die 2. Normalform ist in vielen Fällen noch nicht ausreichend, um alle Redundanzen in einem Datenbankschema zu entfernen. Betrachten wir die Verwaltung der Mitglieder im Basketballverein, die sich bereits in 2. Normalform befindet.

In der Tat existieren hier noch Redundanzen, wie man bei Betrachtung der letzten beiden Spalten der Tabelle Mitglieder sieht. Die formale Beschreibung dieser Form von Redundanz sieht auf den ersten Blick etwas umständlich aus, aber an einem Beispiel kann man sie gut nachvollziehen.

In obigem Schema ist PLZ (wie alle Nichtschlüsselattribute) funktional abhängig vom Primärschlüssel:

{TrikotNr, Mannschaft}  ⟶ PLZ

Von dem Attribut PLZ wiederum ist das Attribut Ort funktional abhängig:

PLZ  ⟶ Stadt

Es gibt also – anschaulich gesprochen – einen Umweg, auf dem man die funktionale Abhängigkeit des Attributs Stadt von {TrikotNr, Mannschaft} beschreiben kann:

{TrikotNr, Mannschaft}  ⟶ PLZ⟶ Stadt

Man sagt, dass das Attribut Stadt transitiv abhängig vom Primärschlüssel{TrikotNr, Mannschaft} ist. Solche transitiven Abhängigkeiten möchte man vermeiden, da sie wieder Redundanzen erzeugen.

Sehen wir uns die formale Definition für diese Form der Abhängigkeit an:

Gegeben sei eine Tabelle mit Nichtschlüsselattributen {B1, B2,…, Bm} und dem weiteren Attribut C (das nicht in der Menge {B1, B2,…, Bm} vorkommt). Falls nun das Attribut C funktional abhängig ist von der Attributkombination {B1, B2,…, Bm}, so sagt man, dass C transitiv vom Primärschlüssel abhängig ist.

Formulierung der 3. Normalform

Die Definition der 3. Normalform wird nun sicher niemanden besonders überraschen. Man beachte aber, dass dort ausdrücklich verlangt wird, dass die vorliegende Datenbank schon in der 2. Normalform ist. Dies ist die letzte Normalform, die wir betrachten.

Ein Datenbankschema befindet sich in der 3. Normalform, wenn es sich bereits in der 2. Normalform befindet und zusätzlich gilt: Es gibt kein Nichtschlüsselattribut, das transitiv vom jeweiligen Primärschlüssel abhängig ist.
Eine Datenbank in 3. Normalform nennt man normalisiert.

Unsere Datenbank können wir also so normalisieren:

Spieler(TrikotNr, Name, Straße, HNr., ↑PLZ, ↑Mannschaft)
Coaches(Mannschaft, Trainer)
Stadt(PLZ, Ort)