Jak reprezentować dziedziczenie w bazie danych?

Myślę o tym, jak reprezentować złożoną strukturę w bazie danych SQL Server.

Rozważ aplikację, która musi przechowywać szczegóły rodziny obiektów, które mają pewne atrybuty, ale wiele innych nie jest wspólnych. Na przykład pakiet ubezpieczeń komercyjnych może obejmować ubezpieczenie Odpowiedzialności Cywilnej,samochodowe, majątkowe i odszkodowawcze w ramach tej samej polisy.

Jest trywialne zaimplementowanie tego w C#, itp., ponieważ można utworzyć Politykę z kolekcją sekcji, gdzie Sekcja jest dziedziczona zgodnie z wymaganiami dla różnych rodzajów pokrycia. Wydaje się jednak, że relacyjne bazy danych nie pozwalają na to łatwo.

Widzę, że są dwie główne opcje:

  1. Utwórz tabelę zasad, a następnie tabelę sekcji ze wszystkimi wymaganymi polami dla wszystkich możliwych wariantów, z których większość byłaby null.

  2. Utwórz tabelę zasad i liczne tabele sekcji, po jednej dla każdego rodzaju okładki.

Obie te alternatywy wydają się niezadowalające, tym bardziej, że konieczne jest pisanie zapytań we wszystkich sekcjach, które wymagałyby licznych łączeń lub licznych kontroli null.

Jaka jest najlepsza praktyka w tym scenariuszu?

Author: Eric Lavoie, 2010-08-27

7 answers

@Bill Karwin opisuje trzy modele dziedziczenia w swojej książce SQL Antipatterns , proponując rozwiązania dla SQL Entity-atrybut-Value antipattern. Jest to krótki przegląd:

Dziedziczenie pojedynczej tabeli (aka dziedziczenie tabeli według hierarchii):

Użycie pojedynczej tabeli jak w pierwszej opcji jest prawdopodobnie najprostszym projektem. Jak już wspomniałeś, wiele atrybutów, które są specyficzne dla podtypów, będzie musiało otrzymać wartość NULL w wierszach, w których te atrybuty nie mają zastosowania. W tym modelu można mieć jedną tabelę zasad, która wyglądałaby mniej więcej tak:

+------+---------------------+----------+----------------+------------------+
| 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 -----/
Jest to jeden z głównych problemów, jakie niesie ze sobą takie podejście:

Utrzymanie prostoty projektu jest plusem, ale główne problemy z tym podejściem są następujące:

  • Jeśli chodzi o dodawanie nowych podtypów, trzeba by zmienić tabelę, aby dostosować atrybuty opisujące te nowe obiekty. Może to szybko stać się problematyczne, gdy masz wiele podtypów lub jeśli planujesz dodać podtypy na regularnie.

  • Baza danych nie będzie w stanie wymusić, które atrybuty mają zastosowanie, a które nie, ponieważ nie ma metadanych do określenia, które atrybuty należą do których podtypów.

  • Nie można również wymuszać NOT NULL na atrybutach podtypu, które powinny być obowiązkowe. Musiałbyś sobie z tym poradzić w swojej aplikacji, która w ogóle nie jest idealna.

Dziedziczenie Tabeli Betonowej:

Inne podejście do problemu dziedziczenia jest utworzenie nowej tabeli dla każdego podtypu, powtarzając wszystkie wspólne atrybuty w każdej tabeli. Na przykład:

--// 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    |   
+------+---------------------+------------------+

Ten projekt zasadniczo rozwiąże problemy zidentyfikowane dla metody pojedynczej tabeli:

  • Atrybuty obowiązkowe mogą być teraz egzekwowane za pomocą NOT NULL.

  • Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.

  • Nie ma również ryzyka, że niewłaściwy atrybut zostanie ustawiony dla określonego podtypu, np. pole vehicle_reg_no dla polityki majątkowej.

  • Nie ma potrzeby stosowania atrybutu type jak w metodzie single table. Typ jest teraz definiowany przez metadane: nazwę tabeli.

Jednak ten model ma również kilka wad:]}
  • Wspólne atrybuty są mieszane z atrybutami specyficznymi dla podtypu i nie ma łatwego sposobu ich identyfikacji. Baza danych nie będzie wiedziała ani jedno, ani drugie.

  • Podczas definiowania tabel należy powtórzyć wspólne atrybuty dla każdej tabeli podtypów. To zdecydowanie nie jest suche .

  • Znalezienie wszystkich zasad niezależnie od podtypu staje się trudne i wymaga zbioru UNION s.

W ten sposób będziesz musiał odpytywać wszystkie zasady niezależnie od typu:

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;

Zauważ, że dodanie nowych podtypów wymagałoby, aby powyższe zapytanie było zmodyfikowano dodatkową UNION ALL dla każdego podtypu. Może to łatwo prowadzić do błędów w aplikacji, Jeśli ta operacja zostanie zapomniana.

Dziedziczenie tabeli klas (aka tabela na dziedziczenie typu):

To jest rozwiązanie, które @David wspomina w drugiej odpowiedzi . Tworzysz jedną tabelę dla swojej klasy bazowej, która zawiera wszystkie wspólne atrybuty. Następnie tworzysz konkretne tabele dla każdego podtypu, których klucz podstawowy służy również jako klucz obcy do stołu bazowego. Przykład:

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)
);

To rozwiązanie rozwiązuje problemy zidentyfikowane w dwóch pozostałych projektach:

  • Atrybuty obowiązkowe mogą być egzekwowane za pomocą NOT NULL.

  • Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.

  • Nie ma ryzyka, że nieodpowiedni atrybut zostanie ustawiony dla określonego podtypu.

  • Nie ma potrzeby używania atrybutu type.

  • Teraz atrybuty wspólne nie są już mieszane z atrybutami specyficznymi podtypu.

  • W końcu możemy się wyschnąć. Nie ma potrzeby powtarzania wspólnych atrybutów dla każdej tabeli podtypów podczas tworzenia tabel.

  • Zarządzanie automatyczną inkrementacją id dla zasad staje się łatwiejsze, ponieważ może to być obsługiwane przez tabelę bazową, zamiast każdej tabeli podtypów generującej je niezależnie.

  • Wyszukiwanie wszystkich polis niezależnie z podtypu staje się teraz bardzo łatwe: Nie UNIONs potrzebne-tylko SELECT * FROM policies.

Uważam podejście tabeli klasowej za najbardziej odpowiednie w większości sytuacji.


Nazwy tych trzech modeli pochodzą z Książki Martina Fowlera Patterns of Enterprise Application Architecture .
 350
Author: Daniel Vassallo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:27

Trzecią opcją jest utworzenie tabeli "Policy", a następnie tabeli "SectionsMain", która przechowuje wszystkie pola, które są wspólne dla typów sekcji. Następnie utwórz inne tabele dla każdego typu sekcji, które zawierają tylko pola, które nie są wspólne.

Decyzja, która jest najlepsza, zależy głównie od tego, ile masz pól i jak chcesz napisać swój SQL. Wszystkie by zadziałały. Jeśli masz tylko kilka pól, to prawdopodobnie pójdę z #1. Z "mnóstwem" pól chciałbym pochyl się w kierunku # 2 lub # 3.

 12
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-26 20:15:48

Z podanych informacji, chciałbym model bazy danych mieć następujące:

Polityka

  • POLICY_ID (Klucz Główny)

Pasywa

  • LIABILITY_ID (klucz podstawowy)
  • POLICY_ID (klucz obcy)

Właściwości

  • PROPERTY_ID (klucz podstawowy)
  • POLICY_ID (klucz obcy)

...i tak dalej, ponieważ spodziewałbym się, że będą różne atrybuty związane z każdą sekcją polityki. Inaczej, może istnieć pojedyncza SECTIONS tabela i oprócz policy_id, będzie section_type_code...

Tak czy inaczej, pozwoli to na obsługę opcjonalnych sekcji dla każdej polityki...

Nie rozumiem, co uważasz za niezadowalające w tym podejściu - w ten sposób przechowujesz dane, zachowując integralność odniesienia i nie powielając danych. Termin jest "znormalizowany"...

Ponieważ SQL jest oparty na Ustawieniach, jest raczej obcy pojęciom programowania proceduralnego/OO i wymaga kodu do przejście z jednego królestwa do drugiego. Orm są często brane pod uwagę, ale nie działają dobrze w dużych, złożonych systemach.

 8
Author: OMG Ponies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-26 20:22:50

Innym sposobem jest użycie komponentu INHERITS. Na przykład:

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);

W ten sposób możliwe jest zdefiniowanie dziedziczenia między tabelami.

 4
Author: Marco Paulo Ollivier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 17:09:13
 0
Author: Zoidberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:01

Skłaniam się ku metodzie # 1 (ujednoliconej tabeli sekcji), ze względu na sprawne pobieranie całych zasad ze wszystkimi ich sekcjami(co zakładam, że Twój system będzie robił wiele).

Co więcej, nie wiem, jakiej wersji SQL Server używasz, ale w 2008 + nieliczne Kolumny pomagają zoptymalizować wydajność w sytuacjach, w których wiele wartości w kolumnie będzie NULL.

Ostatecznie będziesz musiał zdecydować, jak "podobne" są sekcje zasad. Chyba że różnią się znacznie, myślę, że bardziej znormalizowane rozwiązanie może być więcej kłopotów niż warto... ale tylko Ty możesz wykonać ten telefon. :)

 0
Author: Dan J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-26 20:22:05

Ponadto w rozwiązaniu Daniela Vassallo, jeśli używasz SQL Server 2016, jest inne rozwiązanie, które używałem w niektórych przypadkach bez znacznej utraty wydajności.

Możesz utworzyć tylko tabelę zawierającą tylko pole wspólne i dodać pojedynczą kolumnę z ciągiem JSON, który zawiera wszystkie pola specyficzne dla podtypu.

Przetestowałem ten projekt pod kątem zarządzania dziedziczeniem i jestem bardzo zadowolony z elastyczności, którą mogę wykorzystać we względnej aplikacji.

 0
Author: overcomer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-01 12:17:09