Bei der Programmierung gilt: Vermeide Redundanz! Redundante Codezeilen erhöhen den Wartungsaufwand. Auch bei Datenbanken sind doppelte Datensätze nicht sehr hilfreich. Um diese zu verhindern, muss die Datenbank erst normalisiert werden. Ich erkläre hier kurz und knapp, wie man seine Datenbank unter Gesichtspunkten der Normalisierung plant und versuche dabei das Thema so sanft wie möglich anzugehen. Das bedeutet auch, dass ich euch nicht mit den verschiedenen Formen der Normalform bombardiere, sondern die Schilderung der Problematik Problematik und die Lösung so einfach wie möglich halte.
Es gilt, eine Tabelle zu erstellen, in der die Bestellungen von Kunden festgehalten werden. Unüberlegtes “Draufloserstellen” könnte zu folgender Tabelle führen:

Problematik
Man hat alle Daten, die für das Speichern einer Bestellung benötigt, in eine Tabelle gestopft. Dieses Konstrukt würde auf den ersten Blick sogar funktionieren, kann man sich doch jetzt schon beispielsweise alle Kunden anzeigen lassen, die Schokolade bestellt hatten:

Das ist aber nur ein scheinbarer Erfolg. Wir haben aber drei Probleme:
- In der Tabelle sind massenhaft Daten (unnötigerweise) doppelt.
- Das Feld “Kundenanschrift” ist zu stark verallgemeinert. Was ist, wenn wir alle Kunden anzeigen möchten, die im Postleitzahlengebiet 22 wohnen?
- Wie erstellen wir eine Abfrage, die alle Bestellungen komplett auflistet, in der Schokolade vorkommt?
Diese drei Probleme werde ich nun näher beleuchten.
Problem 1: Doppelte Datensätze
Erstmal ist es nicht schlimm, wenn Daten doppelt vorkommen. Die Tabelle “funktioniert”, egal wie oft die Daten doppelt sind. Jedoch sollte man mit Ressourcen sparsam umgehen, außerdem müssen die Daten auch wartbar bleiben. Wenn Erna Mustermann auf einmal eine neue Telefonnummer hat, müssen wir jede Zeile von “Bestellungen” durchgehen und die Telefonnummer ändern, wenn es sich dabei um Erna Mustermann handelt.
Problem 2: Nicht sauber getrennte Felder
Je mehr Information wir in ein Feld (eine Zelle) setzen, desto schwieriger wird die Auswertung bzw. die Suche nach einem bestimmten Merkmal. Postleitzahlen sind, zumindest in Deutschland, immer numerisch. Dies macht es möglich, gleichartige Postleitzahlen zu gruppieren. Das funktioniert jedoch nur, wenn die Postleitzahl auch als einzelnes Feld zur Verfügung steht. Mit der obenstehenden Lösung haben wir keine Möglichkeit, die Datensätze sortiert nach Postleitzahl auszugeben, weil sie nicht separat erreichbar oder filterbar sind.
Problem 3: Komplexere Abfragen machen Probleme
Natürlich ist es möglich, sich alle Bestellungen komplett aufzulisten, in denen Schokolade bestellt wurde:
- Suche nach Zeilen, in denen “Schokolade” vorkommt
- Speichere die dazugehörigen Bestellungsnummern in einer Liste
- Gib alle Datensätze aus, deren Bestellungsnummern teil der erstellten Liste sind
Dies ist recht umfangreich für eine eigentlich einfache Abfrage. Besonders bei komplexeren Abfragen (Gib mir alle Bestellungen, die keine Schokolade enthielten und aus mindestens drei Artikeln bestehen) kann es hier sehr schnell unübersichtlich werden.
Wie lösen wir also das Dilemma? Nun, alle drei Probleme lassen sich durch Normalisierung lösen, wobei Problem Nr. 1 die Neustrukturierung der Tabellen betrifft und Nr. 2 die Aufteilung der Spalten innerhalb der Tabelle. Problem Nr. 3 löst sich von selbst, wenn wir die ersten beiden gelöst haben. Der Einfachheit halber fange ich mal bei Problem Nr. 2 an.
Einzelne Felder sauber trennen
Zuerst überlegen wir uns, welche Daten voneinander separiert werden müssen. Dabei ist immer der Anwendungsfall entscheidend. Möchte man die größtmögliche Flexibilität bewahren, müsste man den Vor- vom Nachnamen trennen, die Strasse von der Hausnummer, die Postleitzahl vom Ort und den Artikeltyp von der Artikelvariante. Wir werden der Einfachheit halber die Postleitzahl und die Stadt getrennt voneinander speichern. Das führt zu einer solchen Tabelle:
Nun können wir sehr einfach alle Bestellungen ausgeben lassen, die im Postleitzahlengebiet 99 stattfanden:

Trotzdem haben wir noch mit allerlei redundanten Daten zu kämpfen.
Tabellen separieren
Jetzt geht’s ans Eingemachte. Wir sehen uns die Tabelle an und überlegen, welche Daten regelmäßig doppelt sind. Diese verfrachten wir in eine andere Tabelle. Wir fangen mit den Kundendaten an – die wiederholen sich laufend. Alle Kundeninformationen werden nun in der Tabelle “kunden” separat abgespeichert:

Doch Moment! Wie können wir nun den Bestellungen die Kunden zuordnen? Die Informationen der beiden Tabellen muss verknüft werden. Wir können sagen: Ein Kunde hat mehrere Bestellungen bzw. mehrere Artikel bestellt. Umgekehrt bedeutet das: Ein bestellter Artikel gehört zu einem Kunden. Wir müssen also jedem Eintrag in der Tabelle “Bestellungen” einem Kunden zuordnen. Ein Kunde mit der Spalte “id” identifiziert. Die Kunden-ID wird also zu jedem bestellten Artikel hinterlegt:
Das sieht schonmal sehr schön und übersichtlich aus. Wenn sich nun die Telefonnummer eines Kunden ändert, müssen wir das an nur einer einzigen Stelle tun. Trotzdem enthält die Tabelle noch immer redundante Informationen: Das Datum, die Bestellnummer und die Kunden-ID ist pro Bestellung immer die gleiche. Man muss hier also differenzieren zwischen einer Bestellung (Kunde, Datum, Bestellnummer) und mehrere Artikel (Artikel-ID, Artikelname, Artikelpreis), die zu einer Bestellung gehören.
Erneut splitten wir also die Tabelle “Bestellungen” und speichern einzelne Artikel in einer neuen Tabelle “artikel” ab. Das führt dazu, dass wir nun jeden Artikel nur einmal in der Datenbank haben. Ändert sich z.B. die Bezeichnung des Artikels, müssen wir es an nur einer Stelle tun*.
Weil wir sämtliche Informationen über die Artikel ausgegliedert haben, müssen wir diese Information nun nicht mehr in der Tabelle “Bestellungen” berücksichtigen. Das wiederum bedeutet, dass in der Tabelle “Bestellungen” nur noch das Datum, die Bestellnummer und der dazugehörige Kunde gespeichert sein muss. Pro Bestellung ist in “bestellungen” nur ein Datensatz nötig:

Jetzt fehlt nur noch eine Zuordnung: Welche Bestellung umfasst welche Artikel? Rein theoretisch könnte man in der Tabelle “Artikel” ein Feld hinzufügen, dass die zugehörige Bestellungs-ID speichert. Das bedeutet aber, dass auch hier wieder Redundanz in Kauf genommen wird: Jeder Artikel wäre somit wieder mehrfach vorhanden. Die Lösung: Wir legen uns eine weitere Tabelle “artikel2bestellungen” an, die die Zuordnungen zwischen Bestellung und Artikel speichert:
Das ganze sieht nun sehr abstrakt aus. Es ist aber die einzige Möglichkeit, die Daten nicht doppelt erscheinen zu lassen. Zur Übersicht hier noch einmal die Beziehungen zueinander:
Jetzt ist kein Eintrag mehr redundant – es sei denn, es lässt sich nicht verhindern. Wir können dank der gewonnenen Flexibilität nun sämtliche Abfragen problemlos realisieren – Voraussetzung dafür ist natürlich, dass man mit JOINs vertraut ist. Eine übersichtliche Einführung dazu gibt es hier.
Fazit
Datenbank-Normalisierung ist noch viel mehr als das. Es exisiteren verschiedene Formen der Normalisierung “Normalform” 1 bis 5), außerdem gibt es neben der Redundanz noch weitere Notwendigkeiten, die eine Normalisierung erfordern. Ich habe es hier bewusst bei einer einfachen Erklärung belassen, da ich aus eigener Erfahrung weiß wie trocken und unhandlich der Stoff ist.
*) Achtung!
Das Separieren von Tabelleninformationen zur Vermeidung der Redundanz ist kein Vorgang, den man nach einem festen “Rezept” angehen kann. Es hängt immer vom Anwendungsfall ab. In meinem Beispiel bin ich sogar streng genommen über das Ziel hinausgeschossen: Wenn sich der Preis eines Artikels ändert, dann sind alle Bestellungen davon unnmittelbar betroffen – auch die, die schon abgeschickt wurden. Es lässt sich nicht mehr nachvollziehen, welchen Preis Artikel X zum Zeitpunkt einer Bestellung hatte. Die einzige Lösung ist hier, die Preisinformationen in “artikel2bestellungen” mit einzubeziehen.



Die Preisinformationen sollten auf jeden Fall mit zu den Bestellungen gespeichert werden, sonst steigt Dir nämlich das Finanzamt aufs Dach. :-)
Danke für das Kommentar – du hast natürlich Recht. Habe es daher im letzten Absatz beschrieben.
Du sagst ja selber, ist nur ein Einstieg. Wenn man sich aber mal mit dem Beispiel genauer befasst findet man selbst in diesem simplen Vorgang noch einige Optimierungsmöglichkeiten (abgesehen von Olivers Aussage).
Mit welchem Programm / welcher GUI hast du diese schönen Abfragen hinbekommen? Ich selber arbeite nur in der Shell und bekomme es s/w und (recht) unübersichtlich.
Peace ;)