Access-Datenbanken entwerfen

Datenbank-Entwurf und -Konzept

∅ 4.2 / 8 Bewertungen

Datenbank-Entwurf und -Konzept

Bitte beachten Sie, dass eine Übungs-Datenbank zum Download bereit steht, die zusätzlich zum PDF downzuloaden ist: Entwurf einer Access-Datenbank (*.mdb/.accdb, 56 kB)

Der Entwurf einer Access-Datenbank beginnt am besten auf dem Papier, damit Änderungen leicht vorzunehmen sind. Es ist eigentlich eine recht übersichtliche Technik, wenn man die Regeln beherrscht, die hier erläutert werden.

Das Schwierigste beim Erstellen einer Access-Datenbank ist eigentlich der Entwurf vorher. Sie sollten sich für die Planung durchaus ordentlich Zeit nehmen, denn hier entscheidet sich, ob das Konzept funktioniert.

Hinter dem richtigen Entwurf einer Datenbank steht allerdings weniger Genialität als vielmehr ein paar grundlegende Methoden, die leicht zu lernen sind.

Der Datenbank-Entwurf beginnt auf dem Papier und nicht am Rechner. Dort können Sie sich übersichtlich auf das Konzept konzentrieren und verlieren sich nicht schon in überflüssigen Kleinigkeiten wie Schriftgrößen und der Hintergrundfarbe von Formularen.

Damit die Datenbank auch so funktioniert, wie Sie es gerne hätten, müssen Sie leider zuerst ein paar Blicke auf ein bißchen Theorie werfen. Das geht aber schnell und lohnt sich.

Normalformen

Für die Aufteilung der zu speichernden Informationen in die Tabellen und Felder einer relativen Datenbank wie Access hat Edgar F. Codd schon 1960 Regeln entwickelt, die so genannten Normalformen. Eine Datenbank, die diesen Regeln entspricht, nennt sich normalisiert. Sie enthält dann keine überflüssigen oder gar widersprüchlichen Daten mehr.

Von den 5 Normalformen müssen Sie aber nur die ersten 3 kennen, um alle wesentlichen Probleme abzudecken. Schauen wir uns diese einfach mal an.

Erste Normalform: Atomisierung

Die 1. Normalform ("Jedes Attribut der Relation muss einen atomaren Wertebereich haben.") verlangt, dass nicht mehrere Informationen in einem einzigen Feld gespeichert werden dürfen. Die Versuchung ist auch recht groß,

  • in einem Feld adrTelefon mehrere Telefonnummern durch Kommata getrennt zu speichern oder

  • die gesamte Anschrift in einem gemeinsamen adrAnschrift-Feld oder

  • die lieferbaren Farben eines Artikels schnell im Feld adrFarben aufzulisten.

Outlook (was zwar gar keine relationale Datenbank ist, aber wegen der Datenstruktur besser eine sein sollte) ist eines der prominenten Beispiele für einen solchen Atomisierungsfehler: die Kategorien stehen einfach durch Semikolon getrennt hintereinander in einem Feld.

56372_outlookkategorien-png

Outlook-Kategorien als Beispiel eines Feldes mit mehreren, nicht-atomisierten Inhalten

Aber warum eigentlich nicht? Das ist doch so schön einfach? Weil Sie solche vermischten Daten nicht mehr brauchbar auswerten können:

  • Sie können nicht mehr alle Telefonnummern mit einer bestimmten Vorwahl herausfiltern. Entweder sehen Sie auch die anderen Telefonnummern dieses adrTelefon-Feldes oder Sie müssen diese per VBA-Programmierung mühsam wegblenden.

  • Sie können nicht nach Postleitzahlen sortieren, wenn diese lediglich Teil-Inhalt in einem adrAnschrift-Feld ist.

  • Sie können keine Liste aller vorkommenden Farben anzeigen lassen, ohne wiederum aufwändig zu programmieren.

Aus Feldern werden Datensätze

Durch das Verteilen gemischter Dateninhalte auf einzelne Felder oder einzelne Datensätze lassen sich deren Inhalte anschließend erst brauchbar auswerten.

Die Atomisierung, also das Zerlegen aller Daten und einzelne Speichern in getrennten Feldern oder sogar Datensätzen, meint mehreres:

  • Keine gleichwertigen Informationen (siehe Farben) in einem einzigen Feld sammeln

  • Keine Mischfelder (siehe Adresse) anlegen, deren Inhalte Sie irgendwann teilweise auswerten wollen

  • Keine nummerierten Felder à la Telefon1, Telefon2, Telefon3, die gleichartige Informationen nebeneinander enthalten.

Im Zweifelsfall trennen Sie ruhig einmal zu viel. Es ist sehr einfach, zwei Felder zu einem zusammenzufügen, aber immer problematisch, Inhalte zu teilen.

Stellen Sie sich ein adrName-Feld mit Inhalten vor wie Lorenz Hölscher oder John Montagu, 4. Earl of Sandwich oder Bernhard Victor Christoph-Carl von Bülow. Wie sollen Sie solche Daten jemals automatisch in adrVorname und adrNachname zerlegen?

Natürlich finden Sie in "Die 12 wichtigsten VBA-Text-Funktionen" die benötigten Techniken, um solche Informationen in Vor- und Nachname zu zerlegen. Aber zum einen handelt es sich dort um VBA-Programmierung, so daß Sie also kein SQL mehr benutzen können. Und zum anderen gäbe es schon bei diesen Beispiel keine Chance, die Nachnamen (Hölscher, Montagu, von Bülow) automatisch zu erkennen.

Mehrwertige Felder seit Access 2007?

Sie könnten sich an dieser Stelle über die Existenz so genannter mehrwertiger Felder seit Access 2007 wundern. Darin können mehrere Daten gespeichert und in Listenform dargestellt werden. Die mit Access 2007 neu eingeführten mehrwertigen Felder widersprechen aber tatsächlich nicht der 1. Normalform, denn sie enthalten intern auch nur eine 1:n-Beziehung. Diese wird lediglich vor dem Benutzer versteckt und ist entsprechend schwierig zu bearbeiten.

Aber auch Access 2007 bzw. Access 2010 "kochen nur mit Wasser", hebeln hier also keineswegs die 1. Normalform aus. In Wirklichkeit handelt es sich bei ihnen nämlich nur um versteckte m:n-Tabellen (siehe unten), die im Datenbankfenster nicht angezeigt werden.

Leider verführt das dazu, zu glauben, in einem einzigen Feld seien mehrere Informationen gleichzeitig gespeichert. Sie haben im Grunde keinen Vorteil von solchen versteckten Tabellen, sondern vor allem deutliche Probleme beim Zugriff auf deren Daten. Daher sind diese nicht zu empfehlen.

Zweite Normalform: Redundanz

Außer dem Inhalt eines Feldes ist es auch wichtig, wo dieses gespeichert wird. Die 2. Normalform verlangt, dass Daten, die von einem anderen Feld der gleichen Tabelle abhängig sind, nicht in dieser Tabelle gespeichert werden dürfen.

Diese Regel wird so selbstverständlich verletzt, dass Sie praktisch sicher sein können, dass das bei Ihren Datenbanken auch der Fall sein wird:

  • Eine Kunden-Tabelle enthält die Felder kndPLZ und kndOrt als Teil der Adresse.

  • Eine Tabelle für Artikelbestellungen enthält bstNetto, bstMWST und bstBrutto.

  • Eine Tabelle für Buch-Ausleihen enthält ausAutor und ausBuchtitel.

Das Problem dahinter ist weniger die eigentliche Redundanz, als das mehrfache Speichern überflüssiger Daten. Vielmehr riskieren Sie widersprüchliche Angaben:

  • Die Adresse lautet einmal 52099 Aachen und im nächsten Datensatz 52099 Bielefeld. Nur ihr "Weltwissen" kann Ihnen verraten, dass dort PLZ und Ort nicht zusammen passen. Wie soll die Datenbank das erkennen und verhindern?

  • Ein Artikel kostet netto 100 Euro mit einem Mehrwertsteuersatz von 19 % und Brutto von 105 Euro. Die drei passen offensichtlich nicht zusammen, aber was ist denn nun gültig?

  • Das Buch wird beim ersten Ausleihen als Harry Potter und der Gefangene von Askaban von Joanne K. Rowling eingetragen und beim nächsten Mal als H. Potter u. d. Gefangene v. Askaban von J. Rowling. Wie soll eine Datenbank diese Eintragungen als gleich erkennen?

Eine Datenbank mit solchen Problemen können sie unbesehen löschen, denn sie ist einfach untauglich.

Redundante Werte errechnen

Werte, die sich aus anderen Daten ergeben, dürfen nicht überflüssigerweise bereits in Tabellen gespeichert werden. Vielmehr werden sie bei Bedarf in einer Abfrage errechnet und stehen dann korrekt zur Verfügung. Andernfalls könnten widersprüchliche Daten enthalten sein.

Die Lösung sieht im Falle der Mehrwertsteuer sehr einfach aus, denn einer der drei Werte ist zu viel. Egal, welchen Sie weglassen, er ist aus den beiden übrigen zu errechnen und damit überflüssig.

Das ist eine grundlegende Regel: Daten, die sich aus anderen Daten der gleichen Tabelle ermitteln lassen, sind immer redundant. Sie dürfen nie gespeichert, sondern müssen immer live (in Abfragen) ermittelt werden.

Die anderen beiden Beispiele haben eine gemeinsame Methode, die Redundanz und damit mögliche Widersprüchlichkeit der Daten zu vermeiden: wenn die Informationen nicht mehrfach in dieser Tabelle stehen dürfen, werden sie eben in eine andere ausgelagert.

Dort steht jedes Buch bzw. jede PLZ-Ort-Kombination genau einmal und wird in der Ausleih-Tabelle bzw. der Adressen-Tabelle anhand einer eindeutigen Kennung lediglich nachgeschlagen:

  • Die Adressen-Tabelle enthält das Feld adrPLZRef und zeigt erst in einer Abfrage den passenden Ort aus der Nachschlagetabelle an. Die PLZ ist in Deutschland (theoretisch) eindeutig, auch wenn andersherum ein Ort mehrere Postleitzahlen besitzen kann.

  • Für die Ausleihe gibt es ein Feld ausbucISBNRef und in der Büchertabelle zu jedem Buch mit Titel und Autor das Feld bucISBN als eindeutige Kennung. Nur in der Buch-Tabelle stehen Titel und Autor genau einmal.

Mit dieser Normalisierung erwischen Sie sozusagen den Kern einer relationalen Datenbank und lösen gleich mehrere Probleme:

  • Abweichende Schreibweisen können nicht passieren.

  • Mit der noch zu besprechenden "referentiellen Integrität" können Sie Access prüfen lassen, dass die Kennung in der Nachschlagetabelle auch wirklich existiert.

  • Selbst eine 13stellige ISBN verbraucht immer deutlich weniger Speicherplatz als der Titel und Autorenname. Die Datenbank wird also kleiner und damit immer auch schneller.

Dritte Normalform: Historie

Was in der 3. Normalform so harmlos "jedes Feld darf nur genau vom Tabellenschlüssel abhängig sein" heißt, ist von recht hoher Brisanz für das spätere Funktionieren der Datenbank. Auch hier können Sie ziemlich sicher von Fehlern in fast jeder Datenbank ausgehen:

  • In der Artikeltabelle ist der jeweilige Preis gespeichert.

  • Die Kundentabelle enthält den Rabatt.

Das Problem bei diesen Verletzungen der 3. Normalform besteht darin, dass sie so unauffällig daherkommen. Denn selbstverständlich ist der Preis in der wirklichen Welt eine Eigenschaft des Artikels und der Rabatt genau diesem Kunden zugeordnet. Aber eben nicht dauerhaft!

Die zweite Abhängigkeit von artPreis oder kndRabatt besteht von einem Datum. Stellen Sie sich vor, Sie verkaufen dem Kunden am Anfang eines Jahres eine Tüte Gummibärchen für 0,50 Euro minus seinem Kundenrabatt von 30 %. Dann hat die Tüte tatsächlich 0,35 Euro gekostet.

Im Laufe des Jahres erhöhen Sie den Preis auf 0,60 Euro, außerdem verbessert sich der Kundenrabatt wegen hoher Umsätze auf 35 %. Die Gummibärchen kosten ihn Weihnachten also (0,60 Euro - 35 % =) 0,39 Euro. So weit ist alles harmlos.

Nun erstellen Sie den Jahresüberblick: Sie haben insgesamt 2 Tüten Gummibärchen an diesen Kunden verkauft und können ja Preis und Rabatt in den jeweiligen Nachschlagetabellen nachsehen.

Halt! Was steht dort denn? Die ersten Gummibärchen würden jetzt mit ebenfalls 0,39 Euro bewertet, obwohl Sie doch damals tatsächlich nur 0,35 Euro erhalten haben.

Deswegen lässt sich die 3. Normalform plastischer als Historienproblem beschreiben: zeitlich wechselnde Werte überschreiben ihre Vorgänger-Inhalte. Müssen Sie auf "historische" Daten weiterhin zugreifen können, ist das so nicht möglich.

Es lässt sich eigentlich noch genereller formulieren: nachdem Sie einen Datensatz eingetragen haben, ist er für Änderungen tabu. Sie können ihn vielleicht mal als Ganzes löschen. Aber sobald Sie jemals seine Inhalte verändern müssen, können Sie von einer Verletzung der 3. Normalform in seinem Datenbankmodell ausgehen.

Hilfsfelder speichern

Nun wäre es natürlich schön, wenn sich das Problem auch lösen ließe. Es gibt sogar zwei Lösungen:

  • Eine perfekt normalisierte: Zu der Artikeltabelle gibt es eine Artikelpreistabelle mit ID des Artikels, Preis und Startdatum, seit wann dieser Preis gilt. Zu jeder Auswertung müssen Sie dann anhand des Verkaufsdatums das nächste vorherige Startdatum und den dortigen Preis finden. Entsprechendes gilt für unsere Kundenrabatte-Tabelle.

  • Eine praxisnahe Lösung: Sie erweitern die Verkaufstabelle um zwei Felder vrkRabatt und vrkPreis und schreiben die Nachschlagewerte zum Zeitpunkt des Verkaufs dort per Makro hinein.

Ja, Sie haben richtig gelesen: die praxisnahe Lösung scheint eine Verletzung der 2. Normalform zu enthalten, denn diese beiden Felder enthalten redundante Daten. Das gilt aber nur bis zur nächsten Preis- oder Rabattänderung. Danach steht in vrkPreis der damalige Verkaufspreis und in artPreis ein anderer Preis für den nächsten Verkauf.

Werfen wir doch mal einen Blick auf die perfekt normalisierte Lösung und deren Konsequenzen: Ein Online-Shop hat rund 3 Mio. Artikel im Angebot, also muss die Artikelpreis-Tabelle zur Eröffnung 3 Mio. Start-Einträge erhalten.

Zu jedem Schlussverkauf müssen alle Preise doppelt (vorher reduzieren, hinterher wieder zurücksetzen) aufgenommen werden, jede generelle Preisanpassung fügt weitere 3 Mio. Datensätze hinzu. Dabei ist das Hauptproblem nur indirekt die Menge der gespeicherten Preisdaten.

Den bisher (3 Mio. Startwerte, 100.000 SSV-Preise * 2, 70.000 WSV-Preise * 2, 50.000 Ostersonderpreise * 2 sowie 1.000.000 generelle Preiserhöhungen =) 4,44 Mio. Artikelpreisen des Jahres stünden nämlich bei nur 1.000 täglichen Verkaufsvorgängen immerhin schon 3,65 Mio. Verkaufsdatensätze im Jahr gegenüber. Und jeder einzelne von diesen löst für jede Umsatzberechnung eine (nicht ganz einfache) Suche nach dem zugehörigen Preis aus.

Dazu müssen Sie jeweils zuerst das Maximum unterhalb eines Grenzwertes fürs Datum und danach noch den dazu passenden Preis dieses Datensatzes ermitteln. Und zwar pro Bilanz-Abfrage also 3,65 Mio. mal die 4,44 Mio. Preis-Datensätze durchsuchen. Auch mit selbstverständlich zu setzenden Indices geht bei so etwas jede Datenbank in die Knie! Das kostet Sie einen richtig teuren Server, damit die Datenbank bei solchen Abfragen überhaupt noch innerhalb der nächsten Viertelstunde eine Antwort gibt.

Dagegen nimmt sich der zusätzliche Platzverbrauch der beiden Felder vrkRabatt und vrkPreis mit (Long sind 4 Bytes und Währung sind 8 Bytes, also 3,65 Mio * (8+4) Bytes / 1024 / 1024 = ) 42 MB jährlich ziemlich bescheiden aus: dafür braucht man heutzutage ja nicht einmal eine größere Festplatte auf einem Hobby-Computer.

Und diese Felder stehen direkt im Verkaufsdatensatz, lösen also keine Suche in 4,44 Mio. Preisen aus. Damit ist der Zugriff in der Geschwindigkeit unübertroffen.

Aus Feldern werden Datensätze

Durch das Verteilen gemischter Dateninhalte auf einzelne Felder oder einzelne Datensätze lassen sich deren Inhalte anschließend erst brauchbar auswerten.

Die Atomisierung, also das Zerlegen aller Daten und einzelne Speichern in getrennten Feldern oder sogar Datensätzen, meint mehreres:

  • Keine gleichwertigen Informationen (siehe Farben) in einem einzigen Feld sammeln

  • Keine Mischfelder (siehe Adresse) anlegen, deren Inhalte Sie irgendwann teilweise auswerten wollen

  • Keine nummerierten Felder à la Telefon1, Telefon2, Telefon3, die gleichartige Informationen nebeneinander enthalten.

Im Zweifelsfall trennen Sie ruhig einmal zu viel. Es ist sehr einfach, zwei Felder zu einem zusammenzufügen, aber immer problematisch, Inhalte zu teilen.

Stellen Sie sich ein adrName-Feld mit Inhalten vor wie Lorenz Hölscher oder John Montagu, 4. Earl of Sandwich oder Bernhard Victor Christoph-Carl von Bülow. Wie sollen Sie solche Daten jemals automatisch in adrVorname und adrNachname zerlegen?

Natürlich finden Sie in "Die 12 wichtigsten VBA-Text-Funktionen" die benötigten Techniken, um solche Informationen in Vor- und Nachname zu zerlegen. Aber zum einen handelt es sich dort um VBA-Programmierung, so daß Sie also kein SQL mehr benutzen können. Und zum anderen gäbe es schon bei diesen Beispiel keine Chance, die Nachnamen (Hölscher, Montagu, von Bülow) automatisch zu erkennen.