@Bill Karwin opisuje trzy modele dziedziczenia w swojej książce o Antywzorcach SQL, proponując rozwiązania dotyczące antywzorca SQL Entity-Attribute-Value. Oto krótki przegląd:
Dziedziczenie pojedynczej tabeli (inaczej dziedziczenie tabeli na hierarchię):
Korzystanie z jednej tabeli, jak w pierwszej opcji, jest prawdopodobnie najprostszym projektem. Jak wspomniałeś, wiele atrybutów, które są specyficzne dla podtypu, będzie musiało otrzymać NULL
wartość w wierszach, w których te atrybuty nie mają zastosowania. W tym modelu miałbyś jedną tabelę polityk, 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 -----/
Prosty projekt to plus, ale główne problemy związane z tym podejściem są następujące:
-
Jeśli chodzi o dodawanie nowych podtypów, musiałbyś zmienić tabelę, aby pomieścić atrybuty opisujące te nowe obiekty. Może to szybko stać się problematyczne, gdy masz wiele podtypów lub jeśli planujesz regularnie dodawać podtypy.
-
Baza danych nie będzie w stanie wymusić, które atrybuty mają zastosowanie, a które nie, ponieważ nie ma metadanych określających, które atrybuty należą do których podtypów.
-
Nie możesz również wymusić
NOT NULL
na atrybutach podtypu, które powinny być obowiązkowe. Musiałbyś sobie z tym poradzić w swojej aplikacji, co generalnie nie jest idealne.
Dziedziczenie stołu betonowego:
Innym podejściem do rozwiązania 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:
-
Obowiązkowe atrybuty można teraz wymuszać za pomocą
NOT NULL
. -
Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do już istniejącej.
-
Nie ma również ryzyka, że dla określonego podtypu zostanie ustawiony niewłaściwy atrybut, taki jak
vehicle_reg_no
pole dla polityki nieruchomości. -
Nie ma potrzeby stosowania
type
atrybut jak w metodzie pojedynczej tabeli. Typ jest teraz zdefiniowany przez metadane:nazwę tabeli.
Jednak ten model ma również kilka wad:
-
Atrybuty wspólne są mieszane z atrybutami specyficznymi dla podtypu i nie ma łatwego sposobu ich identyfikacji. Baza danych również nie będzie wiedziała.
-
Definiując tabele, musiałbyś powtórzyć wspólne atrybuty dla każdej tabeli podtypów. To zdecydowanie nie jest SUCHE.
-
Wyszukiwanie wszystkich zasad niezależnie od podtypu staje się trudne i wymagałoby wielu
UNION
s.
W ten sposób musiałbyś zapytać o wszystkie zasady, niezależnie od ich 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;
Zwróć uwagę, że dodanie nowych podtypów wymagałoby zmodyfikowania powyższego zapytania za pomocą dodatkowego UNION ALL
dla każdego podtypu. Może to łatwo prowadzić do błędów w Twojej aplikacji, jeśli ta operacja zostanie zapomniana.
Dziedziczenie tabeli klas (inaczej dziedziczenie tabeli według typu):
To jest rozwiązanie, o którym @David wspomina w drugiej odpowiedzi. Tworzysz pojedynczą tabelę dla swojej klasy bazowej, która zawiera wszystkie typowe atrybuty. Następnie utworzysz określone tabele dla każdego podtypu, którego klucz podstawowy służy również jako klucz obcy do tabeli podstawowej. 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 pozostałych dwóch projektach:
-
Atrybuty obowiązkowe można wymusić za pomocą
NOT NULL
. -
Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do już istniejącej.
-
Nie ma ryzyka, że dla określonego podtypu zostanie ustawiony niewłaściwy atrybut.
-
Nie ma potrzeby stosowania
type
atrybut. -
Teraz wspólne atrybuty nie są już mieszane z atrybutami specyficznymi dla podtypu.
-
W końcu możemy pozostać SUCHYM. Nie ma potrzeby powtarzania wspólnych atrybutów dla każdej tabeli podtypów podczas tworzenia tabel.
-
Zarządzanie automatycznym przyrostem
id
dla polityk staje się łatwiejsze, ponieważ może to być obsługiwane przez tabelę bazową, zamiast przez każdą tabelę podtypów generującą je niezależnie. -
Wyszukiwanie wszystkich zasad niezależnie od podtypu staje się teraz bardzo łatwe:Brak
UNION
s potrzebne - wystarczySELECT * FROM policies
.
Uważam, że podejście oparte na stole klasowym jest najbardziej odpowiednie w większości sytuacji.
Nazwy tych trzech modeli pochodzą z książki Martina Fowlera Wzorce architektury aplikacji korporacyjnych.