Grundlagen: Relationale Datenmodellierung

    • Allgemein

      Grundlagen: Relationale Datenmodellierung

      Als Programmierer denkt man hauptschlich objektorientiert, hantiert mit Objekten, die evtl. wieder Objekte enthalten, oder auch Listen von UnterObjekten - Baumstrukturen eben. Da muß man ein Stück umdenken, wenn man mit Datenmodellierung in Kontakt kommt.

      Datenmodellierung bedeutet, ein adäquates Modell der Wirklichkeit herzustellen - die Verhältnisse im Modell sollen der Wirklichkeit entsprechen. Trivial etwa, dass man für Zahlen keine Strings als Datentyp nimmt. Aber das OOP - Denken selbst stößt schnell an eine Grenze, denn die Wirklichkeit passt nicht in eine Baumstruktur. Schauen wir folgendes kleine Xml an:

      XML-Quellcode

      1. <NewDataSet>
      2. <Polizist>
      3. <Gustav />
      4. <Simone />
      5. <Klaus />
      6. </Polizist>
      7. <Punk>
      8. <Sigi />
      9. <Flo />
      10. </Punk>
      11. </NewDataSet>
      Es geht um Personen und ihre Berufe. Jede Person hat einen. Also sie ist genau in den XmlNode eingeschachtelt, der ihren Beruf darstellt. Was aber, wenn der Polizist Gustav gleichzeitig Punk ist? - also nach Feierabend kanner sich ja die Haare stylen und Party machen (oder was immer den Punk-"Beruf" ausmacht 8| )

      XML-Quellcode

      1. <NewDataSet>
      2. <Polizist>
      3. <Gustav />
      4. <Simone />
      5. <Klaus />
      6. </Polizist>
      7. <Punk>
      8. <Gustav />
      9. <Sigi />
      10. <Flo />
      11. </Punk>
      12. </NewDataSet>
      Dann haben wir aber jetzt 2 Gustavse in der Datenbank, und das ist auch schon ihr Untergang. Weils in Wirklichkeit ein und derselbe Gustav ist. Ebenso un-darstellbar ist ein Gustav ohne Beruf. Die bereits untergegangene Datenbank gerät also unter einen unterseeischen Erdrutsch.
      Das Thema ist abgehandelt - Datenmodellierung mit verschachtelten Listen (Bäumen) funktioniert nicht - Punkt.



      Da Baumstrukturen zur Abbildung der Realität prinzipiell und unrettbar unzureichend sind, ist das Konzept Schachtelung komplett aus der Datenbänkerei verbannt - man hat besseres gefunden.
      Nämlich man kann Verschachtelung simulieren. Man legt keine Listen innerhalb von Listen an, sondern nur noch einzelne Listen, pro Datensatztyp eine (und nennt sie "Tabelle"). In Xml stellt man Tabellen übrigens einfach dadurch dar, dass man mehrere Elemente desselben Datensatztyps hat.

      XML-Quellcode

      1. <NewDataSet>
      2. <Person name="Gustav" />
      3. <Person name="Sigi" />
      4. <Person name="Flo" />
      5. <Person name="Simone" />
      6. <Person name="Klaus" />
      7. <Person name="Gustav" />
      8. <Beruf bezeichnung="Polizist" />
      9. <Beruf bezeichnung="Punk" />
      10. </NewDataSet>
      Das sind jetzt 2 Tabellen: "Person" und "Beruf". Aber wie soll das eine Baumstruktur simulieren, oder gar den doppelten Gustav wieder vereinen? Dazu müssen die Datensätze zuerst mal einzigartig gemacht werden - mit einer ID

      XML-Quellcode

      1. <NewDataSet>
      2. <Person ID="1" name="Gustav" />
      3. <Person ID="2" name="Sigi" />
      4. <Person ID="3" name="Flo" />
      5. <Person ID="4" name="Simone" />
      6. <Person ID="5" name="Klaus" />
      7. <Beruf ID="1" bezeichnung="Polizist" />
      8. <Beruf ID="2" bezeichnung="Punk" />
      9. </NewDataSet>
      Die Simulation der Verschachtelung dreht gewissermaßen die objektorientierte Denke um: nicht der Beruf enthält die Personen (wie im anfänglichen Xml), sondern jede Person enthält einen Verweis auf den Beruf, dem sie angehört:

      XML-Quellcode

      1. <NewDataSet>
      2. <Person ID="1" BerufID="1" name="Gustav" />
      3. <Person ID="2" BerufID="2" name="Sigi" />
      4. <Person ID="3" BerufID="2" name="Flo" />
      5. <Person ID="4" BerufID="1" name="Simone" />
      6. <Person ID="5" BerufID="1" name="Klaus" />
      7. <Beruf ID="1" bezeichnung="Polizist" />
      8. <Beruf ID="2" bezeichnung="Punk" />
      9. </NewDataSet>
      Hier ist die Baumstruktur durch 2 Tabellen simuliert, zwischen denen eine 1:n - Relation besteht. n - Seite der Relation (alias "untergeordnete Tabelle" alias "ChildTable") ist die Tabelle "Person", denn einem Beruf ("ParentTable") können über den Fremdschlüsselwert "BerufID" mehrere Personen zugeordnet sein, jedoch ist einer Person nur genau 1 Beruf zu-ordnebar.

      Tabellen in Tabellen werden simuliert, indem jeder Child-Datensatz einen Verweis erhält auf den Parent-Datensatz, dem er untergeordnet ist. Das Verweisen erfolgt über zusätzlich anzulegende Tabellenspalten, sog. Schlüsselspalten. Jede Tabelle erhält eine PrimaryKey-Schlüsselspalte, deren Werte in dieser Tabelle einzigartig sein müssen. Eine ihr untergeordnete Tabelle erhält eine ForeignKey-Schlüsselspalte, deren Werte natürlich mehrfach vorkommen können, dann nämlich, wenn mehrere Child-Datensätze denselben Parent haben.

      Wie bringen wir nun Gustavs Doppel-Leben in die Datenbank? Adäquat modellieren halt, und das bedeutet hier: wir brauchen eine Mittler-Table, die beiden Tables untergeordnet ist.

      XML-Quellcode

      1. <NewDataSet>
      2. <Person ID="1" name="Gustav" />
      3. <Person ID="2" name="Sigi" />
      4. <Person ID="3" name="Flo" />
      5. <Person ID="4" name="Simone" />
      6. <Person ID="5" name="Klaus" />
      7. <PersonBeruf PersonID="1" BerufID="1" />
      8. <PersonBeruf PersonID="1" BerufID="2" />
      9. <PersonBeruf PersonID="2" BerufID="2" />
      10. <PersonBeruf PersonID="3" BerufID="2" />
      11. <PersonBeruf PersonID="4" BerufID="1" />
      12. <PersonBeruf PersonID="5" BerufID="1" />
      13. <Beruf ID="1" bezeichnung="Polizist" />
      14. <Beruf ID="2" bezeichnung="Punk" />
      15. </NewDataSet>


      Können Sie's erkennen, wie Gustav mittels zweier PersonBeruf-Datensätze nun sowohl dem Polizisten-Beruf als auch dem eines Punk zugeordnet ist? Hier mal eine Transformation in Tabellen-Ansicht - vlt. etwas anschaulicher:

      Wir haben jetzt die 2 Relationen: "Person->PersonBeruf" und "Beruf->PersonBeruf", und diese symmetrische Konstruktion kann sowohl einer Person keinen oder auch mehrere Berufe zuordnen, als auch einem Beruf keine oder auch mehrere Personen. Deshalb spricht man hier von einer m:n - Relation.

      Die Wirklichkeit bringt nunmal sowohl 1:n Strukturen hervor ("Bäume", zB: Staaten - Städte), als auch m:n Strukturen (zB: Personen - Berufe).

      Bei relationaler Strukturierung kann eine Tabelle sowohl mehreren anderen übergeordnet sein, als auch mehreren anderen untergeordnet. Dies ist der entscheidende Vorzug gegenüber echter Verschachtelung, wo eine Tabelle nur genau einer anderen untergeordnet sein kann.



      Begriffe
      Primärschlüssel:
      alias PrimKey, alias PK, alias ID, ist eine Integer-Spalte der Tabelle, deren Werte eindeutig sein müssen. Jede Tabelle sollte einen haben
      In Datenbanken dienen PKs nicht nur als Anker für Fremdschlüssel anderer Tabellen, sondern auch zum Ändern bestehender Datensätze (Update). Weil üblicherweise wird bei einer Änderung anhand des PKs bestimmt, welcher Datensatz "gemeint" ist.
      FremdSchlüssel:
      alias ForeignKey, alias FK, ist eine Integer-Spalte der untergeordneten Tabelle, deren Werte mit einem der PKs der übergeordneten Tabelle identisch sein müssen. Werte einer FK-Spalte sind keine Unikate - es muss aber in der übergeordneten Tabelle einen PK geben, der dem FK-Wert entspricht.



      Nun wäre es natürlich schlimm, wenn man sich als Programmierer mit obigem Zahlensalat auseinandersetzen müsste - zum Glück gibts da auf verschiedenen Ebenen mächtige Unterstützung.
      ZB das Dataset ist eine komplette kleine Datenbank, mit Datensätzen, Tabellen und Relationen. Wenn man da nun alle Polizisten haben möchte, braucht man nur die richtige DataRelation anzugeben, und man fühlt sich wie in einer klassischen OOP-Baumstruktur

      VB.NET-Quellcode

      1. Dim rwPolizist As DataRow = NewDataset.Tables("Beruf").Rows(0)
      2. Dim polizisten As DataRow() = rwPolizist.GetChildRows("FK_BerufPerson")
      3. for each rw as DataRow in polizisten
      4. '...



      Beim typisierten Dataset sind die String-Keys in Extra-Properties und Methoden verkapselt, und Rows verschiedener Tabellen unterscheiden sich schon am Datentyp:

      VB.NET-Quellcode

      1. Dim rwPolizist As BerufRow = NewDataset.Beruf.Rows(0)
      2. Dim polizisten As PersonRow() = rwPolizist.GetPersonRows()
      3. for each pers as PersonRow in polizisten
      4. '...
      Man kann es also verwenden wie (verschachtelte) Listen. Jede DataRow hat Zugriff auf ihre ChildRows (und ebenso auch auf ihre ParentRow). Da im Hintergrund aber mit flachen Tabellen gearbeitet wird, kommt es zu keinen doppelten Datensätzen - es ist derselbe Gustav, der sowohl Polizist sein darf als auch Punk.

      Um zu zeigen, wie im typisierten Dataset die relationale Datenstruktur vollkommen in den OOP-Kontext von vb.net transformiert ist, hier eine Ausgabe aller Personen und ihrer Berufe:

      VB.NET-Quellcode

      1. Dim OutputLines As New List(Of String)()
      2. For Each rwPerson As PersonRow In PersonProfessionDts.Person
      3. OutputLines.Add(rwPerson.Name & ": ")
      4. For Each rwPersonProfession As PersonProfessionRow In rwPerson.GetPersonProfessionRows()
      5. OutputLines.Add(" " & rwPersonProfession.ProfessionRow.Name)
      6. Next
      7. Next
      8. MessageBox.Show(String.Join(Environment.NewLine, OutputLines), "Professions of Persons")

      Wie versprochen: Vom ganzen Primary- und Foreign- ID - Geraffel taucht im Code nichts mehr auf. Wir müssen es zwar zunächstmal im relationalen Modell korrekt anlegen, aber dann - werkelt es fein im Hintergrund man kann sich auf sauberes OOP konzentrieren.
      Ausgabe:

      Brainfuck-Quellcode

      1. Professions of Persons
      2. ---------------------------
      3. Gustav:
      4. Police-Man
      5. Punk
      6. Sigi:
      7. Police-Man
      8. Flo:
      9. Punk
      10. Simone:
      11. Punk
      12. Klaus:
      13. Police-Man
      14. Ansgar:



      Die Umkehrung der Sicht - also Ausgabe aller Berufe, und der Personen, die sie ausüben - wäre in klassischem OOP nur mittels redundanter Datenhaltung machbar. Im typisierten Dataset hingegen ist's kein Thema:

      VB.NET-Quellcode

      1. Dim OutputLines As New List(Of String)()
      2. For Each rwProfession As ProfessionRow In PersonProfessionDts.Profession
      3. OutputLines.Add(rwProfession.Name & ": ")
      4. For Each rwPersonProfession As PersonProfessionRow In rwProfession.GetPersonProfessionRows()
      5. OutputLines.Add(" " & rwPersonProfession.PersonRow.Name)
      6. Next
      7. Next
      8. MessageBox.Show(String.Join(Environment.NewLine, OutputLines), "Persons of Professions")

      Ausgabe:

      Brainfuck-Quellcode

      1. Persons of Professions
      2. ---------------------------
      3. Police-Man:
      4. Gustav
      5. Klaus
      6. Sigi
      7. Punk:
      8. Gustav
      9. Simone
      10. Flo

      Anbei das TestProjekt (in vb + c#). Neben dem hier gezeigten Ausgabe-Code stellt es noch eine vollständige Datenverarbeitung für dieses Datenmodell dar, also mit Laden, Speichern, Zufügen, Ändern, Löschen und Sortieren aller Datensätze.
      Das alles in weniger als 50 Zeilen Code!

      Hinweis: Dieselbe Theorie behandele ich auch hier: From Relational Datamodel to Rich Application (3 articles)
      Dort gehe ich aber auch auf die Konventionen ein, Regeln, Fachbegriffe und gebe ganz konkrete Handlungsanweisungen zum Nach- und Eigenbau.
      Bilder
      • PersonBerufTables.png

        9,28 kB, 512×216, 1.772 mal angesehen
      Dateien
      • PersonBerufCs.zip

        (23,66 kB, 194 mal heruntergeladen, zuletzt: )
      • PersonBerufVB.zip

        (26,07 kB, 289 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „ErfinderDesRades“ ()