@Bill Karwin beskriver tre arvsmodeller i sin SQL Antipatterns-bok, när han föreslår lösningar till SQL Entity-Attribute-Value-antimönster. Detta är en kort översikt:
Enkeltabellsarv (alias tabell per hierarkiarv):
Att använda ett enda bord som i ditt första alternativ är förmodligen den enklaste designen. Som du nämnde måste många attribut som är undertypsspecifika ges en NULL
värde på rader där dessa attribut inte gäller. Med den här modellen skulle du ha en policytabell som skulle se ut ungefär så här:
+------+---------------------+----------+----------------+------------------+
| 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 -----/
Att hålla designen enkel är ett plus, men de största problemen med detta tillvägagångssätt är följande:
-
När det gäller att lägga till nya undertyper måste du ändra tabellen för att ta emot attributen som beskriver dessa nya objekt. Detta kan snabbt bli problematiskt när du har många undertyper, eller om du planerar att lägga till undertyper regelbundet.
-
Databasen kommer inte att kunna genomdriva vilka attribut som gäller och vilka som inte gör det, eftersom det inte finns någon metadata för att definiera vilka attribut som hör till vilka undertyper.
-
Du kan inte heller tvinga fram
NOT NULL
på attribut av en undertyp som bör vara obligatoriska. Du skulle behöva hantera detta i din ansökan, vilket i allmänhet inte är idealiskt.
Betongbordsarv:
Ett annat tillvägagångssätt för att ta itu med arv är att skapa en ny tabell för varje undertyp, och upprepa alla vanliga attribut i varje tabell. Till exempel:
--// 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 |
+------+---------------------+------------------+
Denna design kommer i princip att lösa de problem som identifierats för metoden med en tabell:
-
Obligatoriska attribut kan nu upprätthållas med
NOT NULL
. -
Att lägga till en ny undertyp kräver att man lägger till en ny tabell istället för att lägga till kolumner i en befintlig.
-
Det finns heller ingen risk att ett olämpligt attribut ställs in för en viss undertyp, såsom
vehicle_reg_no
fält för en fastighetspolicy. -
Det finns inget behov av
type
attribut som i enkeltabellmetoden. Typen definieras nu av metadata:tabellnamnet.
Men denna modell har också några nackdelar:
-
De vanliga attributen blandas med de undertypspecifika attributen, och det finns inget enkelt sätt att identifiera dem. Databasen kommer inte att veta det heller.
-
När du definierar tabellerna måste du upprepa de gemensamma attributen för varje undertypstabell. Det är definitivt inte torrt.
-
Att söka efter alla policyer oavsett undertyp blir svårt och skulle kräva en massa
UNION
s.
Så här måste du fråga alla policyer oavsett typ:
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;
Observera att om du lägger till nya undertyper måste ovanstående fråga ändras med en ytterligare UNION ALL
för varje undertyp. Detta kan lätt leda till buggar i din applikation om denna operation glöms bort.
Klasstabellsarv (alias tabell per typarv):
Det här är lösningen som @David nämner i det andra svaret. Du skapar en enda tabell för din basklass, som innehåller alla vanliga attribut. Sedan skulle du skapa specifika tabeller för varje undertyp, vars primärnyckel också fungerar som en främmande nyckel till bastabellen. Exempel:
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)
);
Denna lösning löser problemen som identifierats i de andra två designerna:
-
Obligatoriska attribut kan upprätthållas med
NOT NULL
. -
Att lägga till en ny undertyp kräver att man lägger till en ny tabell istället för att lägga till kolumner i en befintlig.
-
Ingen risk att ett olämpligt attribut ställs in för en viss undertyp.
-
Inget behov av
type
attribut. -
Nu blandas inte de vanliga attributen med de undertypspecifika attributen längre.
-
Vi kan hålla oss TORRA, äntligen. Det finns inget behov av att upprepa de gemensamma attributen för varje undertypstabell när du skapar tabellerna.
-
Hantera ett automatiskt ökande
id
för policyerna blir enklare, eftersom detta kan hanteras av bastabellen, istället för att varje undertypstabell genererar dem oberoende. -
Att söka efter alla policyer oavsett undertyp blir nu väldigt enkelt:Ingen
UNION
behövs - bara enSELECT * FROM policies
.
Jag anser att klasstabellsmetoden är den mest lämpliga i de flesta situationer.
Namnen på dessa tre modeller kommer från Martin Fowlers bok Patterns of Enterprise Application Architecture.