|
2.3 Normalisierung
2.3.1 Die erste Normalform
Die erste Normalform fordert, dass jede in einem Feld gespeicherte Information atomar ist und nicht mehr in weitere Informationen unterteilt werden kann. Dadurch erreichen Sie, dass Sie die enthaltenen Werte einfach abfragen oder sortieren können. Beispiele für nicht atomare Informationen sind folgende in einem einzigen Feld gespeicherte Informationen:
Vorname und Nachname
Straße, Hausnummer, PLZ und Ort
Wenn eine Tabelle ein Feld mit der Bezeichnung Name enthält und dieses sowohl den Vor- und den Nachnamen (in dieser Reihenfolge) wiedergibt, können Sie beispielsweise nur schwer nach dem Nachnamen sortieren. Vor- und der Nachname sind daher unbedingt in zwei Feldern zu speichern.
Bei der Adresse bietet sich ein ähnliches Bild: Nach Datensätzen mit einem bestimmten Ort oder einer bestimmten PLZ wird oft gesucht. Diese Informationen sollten Sie daher in separaten Feldern speichern (siehe Abbildung 2.29 und Abbildung 2.30).
Etwas anders sieht es bei der Straße und der Hausnummer aus: Die Angabe einer Straße hat ohne die Hausnummer zwar noch einen gewissen Informationsgehalt (wenn Sie zum nächsten IKEA fahren möchten, kommen Sie wahrscheinlich ohne die Angabe der Hausnummer aus), aber andersherum lässt sich mit einer Hausnummer allein wenig anfangen. Und das Sortieren danach macht in den meisten Fällen auch keinen Sinn. Fazit: Straße und Hausnummer sind quasi atomar und gehören deshalb normalerweise in ein Feld. Und dort, wo diese Regel durch eine Ausnahme bestätigt wird, werden die Anforderungen schon durchblicken lassen, dass die Informationen besser in getrennte Felder gehören.

Abbildung 2.29: Tabelle mit Personendaten vor ...

Abbildung 2.30: ... und nach der Atomisierung
Um den Inhalt des Feldes Name aus Abbildung 2.29 in die beiden Felder Vorname und Nachname aus Abbildung 2.30 zu überführen, ist zumindest eine regelmäßige Anordnung des zusammengesetzten Feldes erforderlich - also entweder immer oder , . Dann lassen sich die Daten leicht von der einen in die andere Tabelle überführen, etwa mit folgender Prozedur:
Public Sub NameAufteilen()
Dim db As DAO.Database Dim rst As DAO.Recordset Dim longPos As Long Dim strName As String Dim strVorname As String Dim strNachname As String Dim strSQL As String Dim lngLastSpace As Long
Set db = CurrentDb Set rst = db.OpenRecordset("tblPersonenNichtNormalisiert", _ dbOpenDynaset)
Do While Not rst.EOF strName = rst!Name lngLastSpace = InStrRev(strName, " ")
strVorname = Mid(strName, 1, lngLastSpace) strNachname = Mid(strName, lngLastSpace + 1)
strSQL = "INSERT INTO tblPersonenNormalisiert(Vorname, " _ & "Nachname) VALUES('" & strVorname & "','" & strNachname & "')" db.Execute strSQL
rst.MoveNext Loop
End Sub
Listing 2.1: Extrahieren der Bestandteile des Feldes Name in Vor- und Nachname
Die Prozedur geht davon aus, dass die Reihenfolge ist und dass der Nachname keine Leerzeichen enthält. Sie schreibt den Teil des Namens hinter dem letzten Leerzeichen in das Feld Nachname und den Rest in das Feld Vorname.
Wenn der Name auch mal in der anderen Reihenfolge und durch Komma getrennt angegeben ist, können Sie dies durch eine entsprechende Verfeinerung der Prozedur abfangen. Aber Unarten, wie zuerst den Nachnamen und dann den Vornamen zu schreiben, ohne dazwischen ein Komma einzufügen, sind leider ebenfalls gängig; hier hilft wohl nur manuelles Nacharbeiten.
Ein anderes Beispiel, das nach der ersten Normalform schreit, sind mehrere gleichartige Informationen in Listenform in einem einzigen Feld wie in Abbildung 2.31.

Abbildung 2.31: Beispiel für nicht atomare Informationen
Ein erster naiver Ansatz, die im Feld Lieferanten enthaltenen Daten in atomare Feldinhalte umzuwandeln, sieht wie in Abbildung 2.32 aus.
Das ist eine oft zu beobachtende Variante, um gleichartige Informationen zu einem Datensatz zu speichern. Leider birgt diese Variante mindestens drei Schwächen:
Früher oder später gibt es einen Artikel, der mehr Lieferanten als dafür vorgesehene Felder hat. Dann heißt es: Felder anfügen, Abfragen anpassen, Formulare anpassen, Code anpassen.
Wenn ein Artikel weniger Lieferanten als dafür vorgesehene Felder hat, bleiben diese leer und verschwenden unnötig Platz.
Wenn man nach Artikeln mit einem bestimmten Lieferanten sucht, muss man alle dafür vorgesehenen Felder durchgehen.

Abbildung 2.32: Atomar, aber nicht optimal: Felder mit gleichartigen Informationen
In diesem Fall gibt es nur eine Lösung: Da theoretisch jeder Artikel von jedem Lieferanten geliefert werden kann, muss eine m:n-Beziehung her. Das bedeutet, dass die Lieferanten in einer eigenen Tabelle gespeichert werden.
Welcher Lieferant welchen Artikel liefert, speichern Sie in einer Verknüpfungstabelle, die jeweils die Nummer des Artikels und des Lieferanten aufnimmt.
Das aus der Tabelle in Abbildung 2.32 entstehende Datenmodell sieht wie in Abbildung 2.33 aus.

Abbildung 2.33: Manchmal führt Atomatisieren zu m:n-Beziehungen
Um Daten wie aus der Tabelle tblArtikel_1 in Abbildung 2.32 in die Tabellen einer solchen m:n-Beziehung zu überführen, verwenden Sie etwa den Code aus folgendem Listing.
Public Sub AtomizeIntoMNRelation()
Dim db As DAO.Database Dim rstArtikel As DAO.Recordset Dim rstLieferanten As DAO.Recordset Dim strLieferant As String Dim fld As DAO.Field Dim lngLieferantID As Long
Set db = CurrentDb Set rstArtikel = db.OpenRecordset("tblArtikel_1", dbOpenDynaset)
'Alle Datensätze der Datensatzgruppe rstArtikel durchlaufen Do While Not rstArtikel.EOF
'Für alle Felder der Tabelle... For Each fld In rstArtikel.Fields
'...kontrolliere, ob der Name mit 'Lieferant' beginnt If Left(fld.Name, 9) = "Lieferant" _ And Not IsNull(fld.Value) Then
'Prüfen, ob schon ein Lieferant mit diesem Namen 'vorhanden ist strLieferant = fld.Value Set rstLieferanten = db.OpenRecordset _ ("SELECT * FROM tblLieferanten WHERE Lieferant = '" _ & DoubleQuotes(strLieferant) & "'", dbOpenDynaset)
'Wenn noch nicht vorhanden, Lieferant 'in tblLieferanten anlegen If rstLieferanten.RecordCount = 0 Then rstLieferanten.AddNew rstLieferanten!Lieferant = strLieferant
'LieferantID merken lngLieferantID = rstLieferanten!LieferantID rstLieferanten.Update Else lngLieferantID = rstLieferanten!LieferantID End If End If
'Neuen Datensatz in Verknüpfungstabelle anlegen db.Execute "INSERT INTO tblArtikelLieferanten(ArtikelID, " _ & "LieferantID) VALUES(" & rstArtikel!ArtikelID _ & ", " & lngLieferantID & ")"
Next fld rstArtikel.MoveNext
Loop
End Sub
Listing 2.2: Aufbrechen nicht atomarer Informationen in eine m:n-Beziehung
Das Resultat dieser Prozedur für die Daten aus der in Abbildung 2.32 gezeigten Tabelle finden Sie in Abbildung 2.34. Die Prozedur durchläuft alle Datensätze der ursprünglichen Tabelle und unterzieht die Inhalte aller Felder, deren Feldname mit Lieferant beginnt (also Lieferant1, Lieferant2 ...) und deren Feldinhalt nicht leer ist, einer gesonderten Behandlung: Zunächst wird überprüft, ob die Tabelle tblLieferanten bereits einen Lieferanten mit dem angegebenen Namen enthält. Falls nein, wird ein entsprechender Datensatz in dieser Tabelle angelegt. Der Wert des Feldes LieferantID wird in jedem Fall festgehalten, um in einer abschließenden Aktionsabfrage einen neuen Datensatz in der Verknüpfungstabelle tblArtikelLieferanten anzulegen.
Fast für jede Vorgehensweise gibt es Alternativen. Ein passendes Beispiel für das Anfügen verknüpfter Daten finden Sie in Kapitel 13, Abschnitt 13.3.8 »Funktionale Fehlerbehandlung«.

Abbildung 2.34: Diese Daten entsprechen der ersten Normalform
Nächster Abschnitt:
2.3.2 Die zweite Normalform
|