@Bill Karwin beskriver tre arvemodeller i sin SQL Antipatterns-bog, når han foreslår løsninger til SQL Entity-Attribute-Value-antimønsteret. Dette er en kort oversigt:
Enkeltbordsarv (også kaldet tabel pr. hierarkiarv):
At bruge et enkelt bord som i din første mulighed er nok det enkleste design. Som du nævnte, skal mange attributter, der er undertypespecifikke, have en NULL
værdi på rækker, hvor disse attributter ikke gælder. Med denne model ville du have én politiktabel, som ville se nogenlunde sådan ud:
+------+---------------------+--------+--- --------------+------------------------+| id | dato_udstedt | type | køretøjs_reg_nr | ejendomsadresse |+------+---------------------+------------ ----------+------------------------+| 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 | EJENDOM | NULL | Oxford Street || 4 | 2010-08-20 15:00:00 | MOTOR | 03-C-02020 | NULL |+------+------------------------+--------+------- --------------------------+\------ FÆLLES FELTER -------/ \--- -- SUBTYPESPECIFIKKE FELT -----/
At holde designet enkelt er et plus, men hovedproblemerne med denne tilgang er følgende:
-
Når det kommer til at tilføje nye undertyper, skal du ændre tabellen for at rumme de attributter, der beskriver disse nye objekter. Dette kan hurtigt blive problematisk, når du har mange undertyper, eller hvis du planlægger at tilføje undertyper på en regelmæssig basis.
-
Databasen vil ikke være i stand til at håndhæve, hvilke attributter der gælder, og hvilke der ikke gør, da der ikke er nogen metadata til at definere hvilke attributter der hører til hvilke undertyper.
-
Du kan heller ikke gennemtvinge
NOT NULL
på attributter af en undertype, der burde være obligatorisk. Du skal håndtere dette i din ansøgning, hvilket generelt ikke er ideelt.
Betonbordsarv:
En anden tilgang til at tackle arv er at oprette en ny tabel for hver undertype, der gentager alle de fælles attributter i hver tabel. For eksempel:
--// Tabel:policies_motor+------+----------------------------+-------- --------+| id | dato_udstedt | køretøjs_reg_nr |+------+----------------------------+----------------+| 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 |+------+------------------- --+ --// Tabel:policies_property +------+---------------------+-------- --------+| id | dato_udstedt | ejendomsadresse |+------+---------------------+------------------------ +| 1 | 2010-08-20 14:00:00 | Oxford Street | +------+----------------------+----------------+
Dette design vil grundlæggende løse de problemer, der er identificeret for metoden med en enkelt tabel:
-
Obligatoriske attributter kan nu håndhæves med
NOT NULL
. -
Tilføjelse af en ny undertype kræver tilføjelse af en ny tabel i stedet for at tilføje kolonner til en eksisterende.
-
Der er heller ingen risiko for, at der indstilles en upassende attribut for en bestemt undertype, såsom
vehicle_reg_no
felt for en ejendomspolitik. -
Der er ikke behov for
typen
attribut som i single table-metoden. Typen er nu defineret af metadataene:tabelnavnet.
Denne model har dog også nogle få ulemper:
-
De almindelige attributter er blandet med de undertypespecifikke attributter, og der er ingen nem måde at identificere dem på. Databasen ved det heller ikke.
-
Når du definerer tabellerne, skal du gentage de fælles attributter for hver undertypetabel. Det er bestemt ikke TØRT.
-
Det bliver vanskeligt at søge efter alle politikkerne uanset undertypen og vil kræve en masse
UNION
s.
Sådan skal du forespørge alle politikkerne uanset typen:
SELECT date_issued, other_common_fields, 'MOTOR' AS typeFROM policies_motorUNION ALLSELECT date_issued, other_common_fields, 'PROPERTY' AS typeFROM policies_property;
Bemærk, hvordan tilføjelse af nye undertyper kræver, at ovenstående forespørgsel ændres med en ekstra UNION ALL
for hver undertype. Dette kan nemt føre til fejl i din applikation, hvis denne handling bliver glemt.
Klasse tabel arv (også kaldet tabel pr. type arv):
Det er den løsning, som @David nævner i det andet svar. Du opretter en enkelt tabel til din basisklasse, som inkluderer alle de almindelige attributter. Så ville du oprette specifikke tabeller for hver undertype, hvis primærnøgle også fungerer som en fremmednøgle til basistabellen. Eksempel:
CREATE TABLE policys ( policy_id int, date_issued datetime, -- // other common attributes ...); CREATE TABLE policy_motor ( policy_id int, vehicle_reg_no varchar(20), -- // andre attributter, der er specifikke for motor forsikring ... FOREIGN KEY (policy_id) REFERENCER policer (policy_id)); CREATE TABLE policy_property ( police_id int, property_address varchar(20), -- // andre attributter, der er specifikke for ejendomsforsikring ... FOREIGN KEY (policy_id) REFERENCER policer ( policy_id));
Denne løsning løser de problemer, der er identificeret i de to andre designs:
-
Obligatoriske attributter kan håndhæves med
NOT NULL
. -
Tilføjelse af en ny undertype kræver tilføjelse af en ny tabel i stedet for at tilføje kolonner til en eksisterende.
-
Ingen risiko for, at en upassende attribut er indstillet til en bestemt undertype.
-
Intet behov for
type
attribut. -
Nu er de almindelige attributter ikke længere blandet med de undertypespecifikke attributter.
-
Vi kan forblive TØRE, endelig. Det er ikke nødvendigt at gentage de fælles attributter for hver undertypetabel, når du opretter tabellerne.
-
Håndtering af et automatisk stigende
id
for politikkerne bliver nemmere, fordi dette kan håndteres af basistabellen, i stedet for at hver undertypetabel genererer dem uafhængigt. -
At søge efter alle politikkerne uanset undertypen bliver nu meget let:Ingen
UNION
er nødvendigt - bare enSELECT * FROM policys
.
Jeg betragter klassebordstilgangen som den bedst egnede i de fleste situationer.
Navnene på disse tre modeller kommer fra Martin Fowlers bog Patterns of Enterprise Application Architecture.