182 Stimmen

Wie kann man Vererbung in einer Datenbank effektiv modellieren?

Was sind die besten Praktiken für die Modellierung der Vererbung in Datenbanken?

Was sind die Kompromisse (z.B. Abfragefähigkeit)?

(Am meisten interessiere ich mich für SQL Server und .NET, aber ich möchte auch verstehen, wie andere Plattformen dieses Problem angehen).

211voto

Brad Wilson Punkte 64944

Es gibt mehrere Möglichkeiten, die Vererbung in einer Datenbank zu modellieren. Welche Sie wählen, hängt von Ihren Bedürfnissen ab. Hier sind ein paar Optionen:

Tabelle-per-Typ (TPT)

Jede Klasse hat ihre eigene Tabelle. Die Basisklasse enthält alle Elemente der Basisklasse, und jede von ihr abgeleitete Klasse hat ihre eigene Tabelle mit einem Primärschlüssel, der auch ein Fremdschlüssel zur Tabelle der Basisklasse ist; die Klasse der abgeleiteten Tabelle enthält nur die verschiedenen Elemente.

So zum Beispiel:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Dies würde zu Tabellen wie der folgenden führen:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Tabelle-per-Hierarchie (TPH)

Es gibt eine einzige Tabelle, die die gesamte Vererbungshierarchie abbildet, was bedeutet, dass einige der Spalten wahrscheinlich spärlich sind. Es wird eine Diskriminatorspalte hinzugefügt, die dem System mitteilt, um welche Art von Zeile es sich handelt.

Mit den oben genannten Klassen ergibt sich diese Tabelle:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Für alle Zeilen, die den Zeilentyp 0 (Person) haben, ist das Startdatum immer Null.

Tisch-Per-Beton (TPC)

Jede Klasse hat ihre eigene, vollständig ausgeformte Tabelle, die keine Verweise auf andere Tabellen enthält.

Mit den oben genannten Klassen erhalten Sie diese Tabellen:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate

184voto

Jeffrey L Whitledge Punkte 55524

Richtiges Datenbankdesign ist nichts anderes als richtiges Objektdesign.

Wenn Sie planen, die Datenbank für etwas anderes als die einfache Serialisierung Ihrer Objekte zu verwenden (z. B. für Berichte, Abfragen, die Verwendung mehrerer Anwendungen, Business Intelligence usw.), dann empfehle ich keine einfache Zuordnung von Objekten zu Tabellen.

Viele Menschen betrachten eine Zeile in einer Datenbanktabelle als eine Entität (ich habe viele Jahre lang in diesem Sinne gedacht), aber eine Zeile ist keine Entität. Sie ist eine Proposition. Eine Datenbankbeziehung (d. h. eine Tabelle) stellt eine Aussage über die Welt dar. Das Vorhandensein der Zeile bedeutet, dass die Tatsache wahr ist (und umgekehrt, dass ihr Fehlen bedeutet, dass die Tatsache falsch ist).

Mit diesem Verständnis können Sie erkennen, dass ein einziger Typ in einem objektorientierten Programm in einem Dutzend verschiedener Relationen gespeichert sein kann. Und eine Vielzahl von Typen (vereint durch Vererbung, Assoziation, Aggregation oder völlig unverbunden) kann teilweise in einer einzigen Relation gespeichert sein.

Am besten fragen Sie sich, welche Fakten Sie speichern wollen, welche Fragen Sie beantworten wollen, welche Berichte Sie erstellen wollen.

Sobald der richtige DB-Entwurf erstellt ist, ist es eine einfache Angelegenheit, Abfragen/Views zu erstellen, die es Ihnen ermöglichen, Ihre Objekte mit diesen Beziehungen zu serialisieren.

Beispiel:

In einem Hotelbuchungssystem müssen Sie möglicherweise die Tatsache speichern, dass Jane Doe eine Reservierung für ein Zimmer im Seaview Inn für den 10. bis 12. April hat. Ist dies ein Attribut der Kundenentität? Ist es ein Attribut der Hotelentität? Handelt es sich um eine Reservierungsentität mit Eigenschaften, die Kunde und Hotel umfassen? In einem objektorientierten System kann dies alles sein. In einer Datenbank ist es nichts von alledem. Es handelt sich einfach um eine bloße Tatsache.

Um den Unterschied zu sehen, betrachten Sie die folgenden zwei Abfragen. (1) Wie viele Hotelreservierungen hat Jane Doe für das nächste Jahr? (2) Wie viele Zimmer sind für den 10. April im Seaview Inn gebucht?

In einem objektorientierten System ist die Abfrage (1) ein Attribut der Entität "Kunde" und die Abfrage (2) ein Attribut der Entität "Hotel". Dies sind die Objekte, die diese Eigenschaften in ihren APIs offenlegen würden. (Obwohl die internen Mechanismen, mit denen diese Werte ermittelt werden, natürlich Verweise auf andere Objekte beinhalten können).

In einem relationalen Datenbanksystem würden beide Abfragen die Reservierungsbeziehung untersuchen, um ihre Nummern zu erhalten, und konzeptionell gibt es keine Notwendigkeit, sich mit einer anderen "Entität" zu beschäftigen.

Eine richtige relationale Datenbank wird also dadurch aufgebaut, dass man versucht, Fakten über die Welt zu speichern - und nicht, Entitäten mit Attributen zu speichern. Und wenn sie erst einmal richtig konzipiert ist, können nützliche Abfragen, an die man in der Entwurfsphase nicht gedacht hat, leicht erstellt werden, da alle Fakten, die zur Erfüllung dieser Abfragen benötigt werden, an ihrem richtigen Platz sind.

12voto

imang Punkte 111

Die von Brad Wilson erwähnten Muster TPT, TPH und TPC sind der richtige Weg. Aber ein paar Anmerkungen:

  • Unterklassen, die von einer Basisklasse erben, können als schwache Entitäten der Basisklassendefinition in der Datenbank betrachtet werden, d. h. sie sind von ihrer Basisklasse abhängig und können ohne diese nicht existieren. Ich habe schon oft gesehen, dass für jede untergeordnete Tabelle eindeutige IDs gespeichert werden, wobei auch die FK zur übergeordneten Tabelle beibehalten wird. Eine FK ist gerade genug, und es ist noch besser, die On-Delete-Kaskade für die FK-Beziehung zwischen der Kind- und der Basistabelle zu aktivieren.

  • Wenn Sie in TPT nur die Datensätze der Basistabelle sehen, können Sie nicht feststellen, welche untergeordnete Klasse der Datensatz repräsentiert. Dies ist manchmal notwendig, wenn Sie eine Liste aller Datensätze laden wollen (ohne select auf jedem einzelnen Kindertisch). Eine Möglichkeit, damit umzugehen, besteht darin, eine Spalte zu haben, die den Typ der untergeordneten Klasse darstellt (ähnlich wie das Feld rowType im TPH), also TPT und TPH irgendwie zu vermischen.

Angenommen, wir wollen eine Datenbank entwerfen, die das folgende Shape-Klassendiagramm enthält:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

Der Datenbankentwurf für die oben genannten Klassen kann folgendermaßen aussehen:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;

11voto

Marcin Punkte 46457

Kurze Antwort: Sie haben keine.

Wenn Sie Ihre Objekte serialisieren müssen, verwenden Sie ein ORM, oder noch besser etwas wie activerecord oder prevaylence.

Wenn Sie Daten speichern müssen, speichern Sie sie auf relationale Weise (achten Sie darauf, was Sie speichern, und beachten Sie, was Jeffrey L. Whitledge gerade gesagt hat), nicht auf eine Weise, die von Ihrem Objektdesign beeinflusst wird.

5voto

mattlant Punkte 15146

Es gibt zwei Hauptarten der Vererbung, die Sie in einer DB einrichten können: Tabelle pro Entität und Tabelle pro Hierarchie.

Tabelle pro Entität bedeutet, dass Sie eine Basisentitätstabelle haben, die gemeinsame Eigenschaften für alle untergeordneten Klassen hat. Dann haben Sie pro Kindklasse eine weitere Tabelle, die jeweils nur für diese Klasse geltende Eigenschaften enthält. Sie sind 1:1 durch ihre PK's verknüpft

alt text

Bei einer Tabelle pro Hierarchie teilen sich alle Klassen eine Tabelle, und optionale Eigenschaften sind löschbar. Es gibt auch ein Diskriminatorfeld, das eine Zahl ist, die den Typ angibt, den der Datensatz derzeit hat

alt text SessionTypeID ist ein Unterscheidungsmerkmal

Ziel pro Hierarchie ist schneller abzufragen, da Sie keine Joins (nur den Diskriminatorwert) benötigen, während Ziel pro Entität Sie komplexe Joins durchführen müssen, um zu erkennen, welcher Typ etwas ist, und um alle seine Daten abzurufen.

Edit: Die Bilder, die ich hier zeige, sind Screenshots von einem Projekt, an dem ich arbeite. Das Asset-Bild ist nicht vollständig, daher die Leere, aber es sollte hauptsächlich zeigen, wie es aufgebaut ist, und nicht, was man in die Tabellen einfügen soll. Das bleibt Ihnen überlassen ;). Die Sitzungstabelle enthält Informationen über virtuelle Kooperationssitzungen und kann mehrere Sitzungstypen enthalten, je nachdem, um welche Art der Zusammenarbeit es sich handelt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X