@Bill Karwin opisuje trzy modele dziedziczenia w swojej książce SQL Antipatterns , proponując rozwiązania antipatternu SQL Entity-Attribute-Value . Oto krótki przegląd:
Dziedziczenie pojedynczej tabeli (alias Table Per Hierarchy Inheritance):
Korzystanie z jednego stołu jak w pierwszej opcji jest prawdopodobnie najprostszym projektem. Jak wspomniałeś, wiele atrybutów specyficznych dla podtypów 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 -----/
Prostota projektu to plus, ale główne problemy z tym podejściem są następujące:
Jeśli chodzi o dodawanie nowych podtypów, musiałbyś zmienić tabelę, aby uwzględnić atrybuty opisujące te nowe obiekty. Może to szybko stać się problematyczne, jeśli masz wiele podtypów lub 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 poszczególnych podtypów.
Nie można również wymuszać NOT NULL
atrybutów podtypu, które powinny być obowiązkowe. Będziesz musiał sobie z tym poradzić w swojej aplikacji, co ogólnie 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 z pojedynczą tabelą:
Obowiązkowe atrybuty można teraz egzekwować NOT NULL
.
Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.
Nie ma również ryzyka, że ustawiony zostanie nieodpowiedni atrybut dla konkretnego podtypu, takiego jak vehicle_reg_no
pole dla polityki właściwości.
type
Atrybut ten nie jest potrzebny, jak w metodzie pojedynczej tabeli. Typ jest teraz zdefiniowany przez metadane: nazwa 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 zidentyfikowania. Baza danych też nie będzie wiedziała.
Podczas definiowania tabel należy powtórzyć wspólne atrybuty dla każdej tabeli podtypu. To zdecydowanie nie jest SUCHE .
Wyszukiwanie wszystkich zasad niezależnie od podtypu staje się trudne i wymagałoby kilku UNION
s.
W ten sposób będziesz musiał wysłać zapytanie do wszystkich zasad 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 modyfikacji powyższego zapytania o dodatkowe UNION ALL
dla każdego podtypu. Może to łatwo prowadzić do błędów w aplikacji, jeśli operacja zostanie zapomniana.
Dziedziczenie tabeli klas (zwanej także dziedziczeniem tabeli według typu):
Jest to rozwiązanie, o którym @David wspomina w innej odpowiedzi . Tworzysz pojedynczą tabelę dla swojej klasy podstawowej, która zawiera wszystkie typowe atrybuty. Następnie utworzyłbyś 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 dwóch pozostałych projektach:
Atrybuty obowiązkowe można egzekwować NOT NULL
.
Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.
Brak ryzyka, że dla określonego podtypu zostanie ustawiony niewłaściwy atrybut.
Nie ma potrzeby tego type
atrybutu.
Teraz wspólne atrybuty nie są już mieszane z atrybutami specyficznymi dla podtypu.
W końcu możemy pozostać SUCHY. Podczas tworzenia tabel nie trzeba powtarzać wspólnych atrybutów dla każdej tabeli podtypu.
Zarządzanie automatyczną inkrementacją id
zasad staje się łatwiejsze, ponieważ może to być obsługiwane przez tabelę podstawową, zamiast generowania ich niezależnie przez każdą tabelę podtypów.
Wyszukiwanie wszystkich polityk niezależnie od podtypu staje się teraz bardzo łatwe: nie UNION
są potrzebne - wystarczy SELECT * FROM policies
.
Podejście oparte na stole klasowym uważam za najbardziej odpowiednie w większości sytuacji.
Nazwy tych trzech modeli pochodzą z książki Martina Fowlera Wzory architektury aplikacji korporacyjnych .