Wie können Sie die Vererbung in einer Datenbank darstellen?

235

Ich denke darüber nach, wie eine komplexe Struktur in einer SQL Server-Datenbank dargestellt werden kann.

Stellen Sie sich eine Anwendung vor, die Details einer Objektfamilie speichern muss, die einige Attribute gemeinsam haben, aber viele andere nicht gemeinsam haben. Zum Beispiel kann ein gewerbliches Versicherungspaket Haftpflicht-, Motor-, Sach- und Entschädigungsschutz in demselben Versicherungsdatensatz enthalten.

Es ist trivial, dies in C # usw. zu implementieren, da Sie eine Richtlinie mit einer Sammlung von Abschnitten erstellen können, wobei Abschnitt nach Bedarf für die verschiedenen Deckungsarten vererbt wird. Relationale Datenbanken scheinen dies jedoch nicht einfach zuzulassen.

Ich kann sehen, dass es zwei Hauptoptionen gibt:

  1. Erstellen Sie eine Richtlinientabelle und dann eine Abschnittstabelle mit allen erforderlichen Feldern für alle möglichen Variationen, von denen die meisten null wären.

  2. Erstellen Sie eine Richtlinientabelle und zahlreiche Abschnittstabellen, eine für jede Art von Deckung.

Diese beiden Alternativen scheinen unbefriedigend zu sein, insbesondere da Abfragen über alle Abschnitte hinweg geschrieben werden müssen, was zahlreiche Verknüpfungen oder zahlreiche Nullprüfungen beinhalten würde.

Was ist die beste Vorgehensweise für dieses Szenario?

Steve Jones
quelle

Antworten:

430

@ Bill Karwin beschreibt drei Vererbungsmodelle in seinem Buch SQL Antipatterns , wenn er Lösungen für das Antipattern SQL Entity-Attribute-Value vorschlägt. Dies ist eine kurze Übersicht:

Vererbung einzelner Tabellen (auch Vererbung von Tabellen pro Hierarchie genannt):

Die Verwendung einer einzelnen Tabelle wie in Ihrer ersten Option ist wahrscheinlich das einfachste Design. Wie Sie bereits erwähnt haben, müssen viele subtypspezifische Attribute NULLin Zeilen, in denen diese Attribute nicht zutreffen , mit einem Wert versehen werden. Mit diesem Modell hätten Sie eine Richtlinientabelle, die ungefähr so ​​aussehen würde:

+------+---------------------+----------+----------------+------------------+
| id   | date_issued         | type     | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
|    1 | 2010-08-20 12:00:00 | MOTOR    | 01-A-04004     | NULL             |
|    2 | 2010-08-20 13:00:00 | MOTOR    | 02-B-01010     | NULL             |
|    3 | 2010-08-20 14:00:00 | PROPERTY | NULL           | Oxford Street    |
|    4 | 2010-08-20 15:00:00 | MOTOR    | 03-C-02020     | NULL             |
+------+---------------------+----------+----------------+------------------+

\------ COMMON FIELDS -------/          \----- SUBTYPE SPECIFIC FIELDS -----/

Das Design einfach zu halten ist ein Plus, aber die Hauptprobleme bei diesem Ansatz sind die folgenden:

  • Wenn Sie neue Untertypen hinzufügen möchten, müssen Sie die Tabelle ändern, um die Attribute zu berücksichtigen, die diese neuen Objekte beschreiben. Dies kann schnell problematisch werden, wenn Sie viele Untertypen haben oder wenn Sie regelmäßig Untertypen hinzufügen möchten.

  • Die Datenbank kann nicht erzwingen, welche Attribute zutreffen und welche nicht, da keine Metadaten vorhanden sind, um zu definieren, welche Attribute zu welchen Untertypen gehören.

  • Sie können auch keine NOT NULLAttribute eines Subtyps erzwingen , die obligatorisch sein sollten. Sie müssten dies in Ihrer Anwendung behandeln, was im Allgemeinen nicht ideal ist.

Vererbung von Betontabellen:

Ein anderer Ansatz zur Bekämpfung der Vererbung besteht darin, für jeden Subtyp eine neue Tabelle zu erstellen und alle gemeinsamen Attribute in jeder Tabelle zu wiederholen. Beispielsweise:

--// Table: policies_motor
+------+---------------------+----------------+
| id   | date_issued         | vehicle_reg_no |
+------+---------------------+----------------+
|    1 | 2010-08-20 12:00:00 | 01-A-04004     |
|    2 | 2010-08-20 13:00:00 | 02-B-01010     |
|    3 | 2010-08-20 15:00:00 | 03-C-02020     |
+------+---------------------+----------------+
                          
--// Table: policies_property    
+------+---------------------+------------------+
| id   | date_issued         | property_address |
+------+---------------------+------------------+
|    1 | 2010-08-20 14:00:00 | Oxford Street    |   
+------+---------------------+------------------+

Dieses Design löst im Wesentlichen die Probleme, die für die Einzeltabellenmethode identifiziert wurden:

  • Obligatorische Attribute können jetzt mit erzwungen werden NOT NULL.

  • Das Hinzufügen eines neuen Untertyps erfordert das Hinzufügen einer neuen Tabelle, anstatt einer vorhandenen Spalte Spalten hinzuzufügen.

  • Es besteht auch kein Risiko, dass ein unangemessenes Attribut für einen bestimmten Subtyp festgelegt wird, z. B. das vehicle_reg_noFeld für eine Eigenschaftsrichtlinie.

  • Das typeAttribut ist nicht wie bei der Einzeltabellenmethode erforderlich . Der Typ wird jetzt durch die Metadaten definiert: den Tabellennamen.

Dieses Modell hat jedoch auch einige Nachteile:

  • Die allgemeinen Attribute werden mit den subtypspezifischen Attributen gemischt, und es gibt keine einfache Möglichkeit, sie zu identifizieren. Die Datenbank wird es auch nicht wissen.

  • Beim Definieren der Tabellen müssten Sie die allgemeinen Attribute für jede Untertypentabelle wiederholen. Das ist definitiv nicht trocken .

  • Das Suchen nach allen Richtlinien unabhängig vom Subtyp wird schwierig und erfordert eine Reihe von UNIONs.

Auf diese Weise müssten Sie alle Richtlinien unabhängig vom Typ abfragen:

SELECT     date_issued, other_common_fields, 'MOTOR' AS type
FROM       policies_motor
UNION ALL
SELECT     date_issued, other_common_fields, 'PROPERTY' AS type
FROM       policies_property;

Beachten Sie, dass für das Hinzufügen neuer Untertypen die obige Abfrage mit einem zusätzlichen UNION ALLfür jeden Untertyp geändert werden muss . Dies kann leicht zu Fehlern in Ihrer Anwendung führen, wenn dieser Vorgang vergessen wird.

Vererbung von Klassentabellen (auch bekannt als Vererbung von Tabellen pro Typ):

Dies ist die Lösung, die @David in der anderen Antwort erwähnt . Sie erstellen eine einzelne Tabelle für Ihre Basisklasse, die alle allgemeinen Attribute enthält. Dann würden Sie für jeden Subtyp bestimmte Tabellen erstellen, deren Primärschlüssel auch als Fremdschlüssel für die Basistabelle dient. Beispiel:

CREATE TABLE policies (
   policy_id          int,
   date_issued        datetime,

   -- // other common attributes ...
);

CREATE TABLE policy_motor (
    policy_id         int,
    vehicle_reg_no    varchar(20),

   -- // other attributes specific to motor insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

CREATE TABLE policy_property (
    policy_id         int,
    property_address  varchar(20),

   -- // other attributes specific to property insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

Diese Lösung löst die in den beiden anderen Designs identifizierten Probleme:

  • Obligatorische Attribute können mit erzwungen werden NOT NULL.

  • Das Hinzufügen eines neuen Untertyps erfordert das Hinzufügen einer neuen Tabelle, anstatt einer vorhandenen Spalte Spalten hinzuzufügen.

  • Kein Risiko, dass für einen bestimmten Subtyp ein unangemessenes Attribut festgelegt wird.

  • Das typeAttribut ist nicht erforderlich .

  • Jetzt werden die allgemeinen Attribute nicht mehr mit den subtypspezifischen Attributen gemischt.

  • Wir können endlich trocken bleiben. Es ist nicht erforderlich, die allgemeinen Attribute für jede Subtyp-Tabelle beim Erstellen der Tabellen zu wiederholen.

  • Das Verwalten eines automatischen Inkrementierens idfür die Richtlinien wird einfacher, da dies von der Basistabelle verwaltet werden kann, anstatt dass jede Subtyp-Tabelle sie unabhängig generiert.

  • Das Suchen nach allen Richtlinien unabhängig vom Subtyp wird jetzt sehr einfach: Keine UNIONs erforderlich - nur a SELECT * FROM policies.

Ich halte den Klassentabellenansatz in den meisten Situationen für am besten geeignet.


Die Namen dieser drei Modelle stammen aus Martin Fowlers Buch Patterns of Enterprise Application Architecture .

Daniel Vassallo
quelle
97
Ich benutze dieses Design auch, aber Sie erwähnen die Nachteile nicht. Im Einzelnen: 1) Sie sagen, dass Sie den Typ nicht benötigen; true, aber Sie können den tatsächlichen Zeilentyp nur identifizieren, wenn Sie alle Untertypentabellen durchsuchen, um eine Übereinstimmung zu finden. 2) Es ist schwierig, die Mastertabelle und die Subtyptabellen synchron zu halten (man kann z. B. die Zeile in der Subtyptabelle und nicht in der Mastertabelle entfernen). 3) Sie können mehr als einen Untertyp für jede Hauptzeile haben. Ich benutze Trigger, um 1 zu umgehen, aber 2 und 3 sind sehr schwierige Probleme. Tatsächlich ist 3 kein Problem, wenn Sie die Komposition modellieren, sondern dient der strikten Vererbung.
19
+1 für @ Tibos Kommentar, das ist ein ernstes Problem. Die Vererbung von Klassentabellen ergibt tatsächlich ein nicht normalisiertes Schema. Wo dies bei der Vererbung von Betontabellen nicht der Fall ist und ich dem Argument nicht zustimme, dass die Vererbung von Betontabellen DRY behindert. SQL behindert DRY, da es keine Metaprogrammierungsfunktionen hat. Die Lösung besteht darin, ein Datenbank-Toolkit zu verwenden (oder ein eigenes zu schreiben), um das schwere Heben durchzuführen, anstatt SQL direkt zu schreiben (denken Sie daran, es ist eigentlich nur eine DB-Schnittstellensprache). Schließlich schreiben Sie Ihre Unternehmensanwendung auch nicht in Assembly.
Jo So
18
@Tibo, zu Punkt 3 können Sie den hier erläuterten Ansatz verwenden: sqlteam.com/article/… , Überprüfen Sie den Abschnitt Modellierung von Eins-zu-Entweder-Einschränkungen .
Andrew
4
@DanielVassallo Zunächst einmal vielen Dank für die beeindruckende Antwort. Ich bezweifle, dass eine Person eine Richtlinie hat. Wie kann man wissen, ob ihr Richtlinienmotor oder ihre Richtlinieneigenschaft? Eine Möglichkeit besteht darin, die policyId in allen Untertabellen zu durchsuchen, aber ich denke, dies ist der schlechte Weg, nicht wahr? Was sollte der richtige Ansatz sein?
ThomasBecker
11
Ihre dritte Option gefällt mir sehr gut. Ich bin jedoch verwirrt, wie SELECT funktionieren wird. Wenn Sie * FROM-Richtlinien AUSWÄHLEN, erhalten Sie Richtlinien-IDs zurück, wissen jedoch immer noch nicht, zu welcher Subtyp-Tabelle die Richtlinie gehört. Müssen Sie nicht noch einen JOIN mit allen Untertypen durchführen, um alle Richtliniendetails zu erhalten?
Adam
14

Die dritte Option besteht darin, eine "Policy" -Tabelle und dann eine "SectionsMain" -Tabelle zu erstellen, in der alle Felder gespeichert sind, die für die verschiedenen Abschnittsarten gleich sind. Erstellen Sie dann für jeden Abschnittstyp andere Tabellen, die nur die Felder enthalten, die nicht gemeinsam sind.

Die Entscheidung, welches am besten ist, hängt hauptsächlich davon ab, wie viele Felder Sie haben und wie Sie Ihre SQL schreiben möchten. Sie würden alle arbeiten. Wenn Sie nur ein paar Felder haben, würde ich wahrscheinlich mit # 1 gehen. Mit "vielen" Feldern würde ich mich zu # 2 oder # 3 neigen.

David
quelle
+1: 3. Option ist dem Vererbungsmodell am nächsten und am
normalsten
Ihre Option Nr. 3 ist wirklich genau das, was ich mit Option Nr. 2 gemeint habe. Es gibt viele Felder und einige Abschnitte würden auch untergeordnete Entitäten haben.
Steve Jones
9

Mit den bereitgestellten Informationen würde ich die Datenbank folgendermaßen modellieren:

POLITIK

  • POLICY_ID (Primärschlüssel)

VERBINDLICHKEITEN

  • LIABILITY_ID (Primärschlüssel)
  • POLICY_ID (Fremdschlüssel)

EIGENSCHAFTEN

  • PROPERTY_ID (Primärschlüssel)
  • POLICY_ID (Fremdschlüssel)

... und so weiter, weil ich erwarten würde, dass jedem Abschnitt der Richtlinie unterschiedliche Attribute zugeordnet sind. Andernfalls könnte es ein einziger seinen SECTIONSTisch und zusätzlich zu dem policy_id, würde es sein , section_type_code...

In beiden Fällen können Sie optionale Abschnitte pro Richtlinie unterstützen ...

Ich verstehe nicht, was Sie an diesem Ansatz als unbefriedigend empfinden. Auf diese Weise speichern Sie Daten, während Sie die referenzielle Integrität beibehalten und keine Daten duplizieren. Der Begriff ist "normalisiert" ...

Da SQL SET-basiert ist, ist es prozeduralen / OO-Programmierkonzepten eher fremd und erfordert Code für den Übergang von einem Bereich in den anderen. ORMs werden oft in Betracht gezogen, funktionieren jedoch in komplexen Systemen mit hohem Volumen nicht gut.

OMG Ponys
quelle
Ja, ich verstehe die Normalisierungssache ;-) Bei einer so komplexen Struktur, bei der einige Abschnitte einfach sind und andere eine eigene komplexe Unterstruktur haben, ist es unwahrscheinlich, dass ein ORM funktioniert, obwohl es schön wäre.
Steve Jones
6

Wenn Sie bei der Daniel Vassallo-Lösung SQL Server 2016+ verwenden, gibt es außerdem eine andere Lösung, die ich in einigen Fällen ohne nennenswerten Leistungsverlust verwendet habe.

Sie können nur eine Tabelle mit nur dem gemeinsamen Feld erstellen und eine einzelne Spalte mit der JSON- Zeichenfolge hinzufügen , die alle subtypspezifischen Felder enthält.

Ich habe dieses Design für die Verwaltung der Vererbung getestet und freue mich sehr über die Flexibilität, die ich in der jeweiligen Anwendung verwenden kann.

Überwinder
quelle
1
Das ist eine interessante Idee. Ich habe JSON in SQL Server noch nicht verwendet, aber es wird häufig an anderer Stelle verwendet. Danke für die Warnung.
Steve Jones
5

Die andere Möglichkeit besteht darin, die INHERITSKomponente zu verwenden. Beispielsweise:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

Somit ist es möglich, eine Vererbung zwischen Tabellen zu definieren.

Marco Paulo Ollivier
quelle
Unterstützt andere DBs INHERITSneben PostgreSQL ? MySQL zum Beispiel?
Giannis Christofakis
1
@giannischristofakis: MySQL ist nur eine relationale Datenbank, während Postgres eine objektrelationale Datenbank ist. Kein MySQL unterstützt dies also nicht. Tatsächlich denke ich, dass Postgres das einzige aktuelle DBMS ist, das diese Art der Vererbung unterstützt.
a_horse_with_no_name
2
@ marco-paulo-ollivier, die Frage des OP bezieht sich auf SQL Server, daher verstehe ich nicht, warum Sie eine Lösung anbieten, die nur mit Postgres funktioniert. Offensichtlich nicht das Problem ansprechen.
Mapto
@mapto Diese Frage ist zu einer Art "Wie führt man eine Vererbung im OO-Stil in einer Datenbank durch?" dass es ursprünglich um SQL Server ging, ist jetzt wahrscheinlich irrelevant
Caius Jard
0

Ich neige zu Methode 1 (einer einheitlichen Abschnittstabelle), um ganze Richtlinien mit all ihren Abschnitten effizient abzurufen (von denen ich annehme, dass Ihr System viel tun wird).

Außerdem weiß ich nicht, welche Version von SQL Server Sie verwenden, aber ab 2008 helfen Sparse Columns dabei, die Leistung in Situationen zu optimieren, in denen viele der Werte in einer Spalte NULL sind.

Letztendlich müssen Sie entscheiden, wie "ähnlich" die Richtlinienabschnitte sind. Wenn sie sich nicht wesentlich unterscheiden, denke ich, dass eine normalisierte Lösung mehr Probleme bereiten kann, als es wert ist ... aber nur Sie können diesen Anruf tätigen. :) :)

Dan J.
quelle
Es wird viel zu viele Informationen geben, um die gesamte Richtlinie auf einmal zu präsentieren, sodass es niemals notwendig wäre, den gesamten Datensatz abzurufen. Ich denke, es ist 2005, obwohl ich 2008 in anderen Projekten spärlich verwendet habe.
Steve Jones
Woher kommt der Begriff "einheitliche Abschnittsübersicht"? Google zeigt fast keine Ergebnisse dafür und es gibt hier bereits genug verwirrende Begriffe.
Stephan-v
-1

Alternativ können Sie Dokumentendatenbanken (z. B. MongoDB) verwenden, die native Datenstrukturen und Verschachtelungen nativ unterstützen.

Grigori Melnik
quelle