Používáte dědičnost (také známou v modelování vztahů mezi entitami jako „podtřída“ nebo „kategorie“). Obecně existují 3 způsoby, jak jej reprezentovat v databázi:
- "Všechny třídy v jedné tabulce": Mějte pouze jednu tabulku „pokrývající“ nadřazené a všechny podřízené třídy (tj. se všemi nadřazenými a podřízenými sloupci) s omezením CHECK, aby se zajistilo, že správná podmnožina polí není NULL (tj. dvě různé podřízené položky se „nemíchají“).
- "Konkrétní třída na stůl": Mít jiný stůl pro každé dítě, ale žádný rodičovský stůl. To vyžaduje, aby se vztahy rodičů (ve vašem případě Inventář <- Úložiště) opakovaly u všech dětí.
- "Třída podle stolu": Mít nadřazený stůl a samostatný stůl pro každé dítě, o což se snažíte. To je nejčistší, ale může to stát určitý výkon (většinou při úpravě dat, ne tolik při dotazování, protože se můžete připojit přímo z potomka a přeskočit rodiče).
Obvykle preferuji 3. přístup, ale prosazuji oba přítomnost a exkluzivita dítěte na aplikační úrovni. Vynucování obojího na úrovni databáze je trochu těžkopádné, ale lze jej provést, pokud DBMS podporuje odložená omezení. Například:
CHECK (
(
(VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID)
AND WAREHOUSE_ID IS NULL
)
OR (
VAN_ID IS NULL
AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID)
)
)
Tím se prosadí jak exkluzivita (kvůli CHECK
) a přítomnost (kvůli kombinaci CHECK
a FK1
/FK2
) dítěte.
Bohužel MS SQL Server nepodporuje odložená omezení, ale můžete být schopni "skrýt" celou operaci za uložené procedury a zakázat klientům přímo upravovat tabulky.
Pouze exkluzivitu lze prosadit bez odložených omezení:
STORAGE_TYPE
je typový diskriminátor, obvykle celé číslo pro úsporu místa (ve výše uvedeném příkladu jsou 0 a 1 vaší aplikaci „známé“ a podle toho jsou interpretovány).
VAN.STORAGE_TYPE
a WAREHOUSE.STORAGE_TYPE
lze vypočítat (aka. "vypočítané") sloupce, aby se ušetřilo úložiště a nemuselo se používat CHECK
s.
--- UPRAVIT ---
Vypočítané sloupce by fungovaly pod SQL Serverem takto:
CREATE TABLE STORAGE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE tinyint NOT NULL,
UNIQUE (STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE VAN (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
CREATE TABLE WAREHOUSE (
STORAGE_ID int PRIMARY KEY,
STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED,
FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE)
);
-- We can make a new van.
INSERT INTO STORAGE VALUES (100, 0);
INSERT INTO VAN VALUES (100);
-- But we cannot make it a warehouse too.
INSERT INTO WAREHOUSE VALUES (100);
-- Msg 547, Level 16, State 0, Line 24
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE".
Bohužel SQL Server vyžaduje pro vypočítaný sloupec který se používá v cizím klíč být PERSISTED. Jiné databáze nemusí mít toto omezení (např. virtuální sloupce Oracle), což může ušetřit určitý úložný prostor.