Вие използвате наследяването (известно също в моделирането на същност-връзка като "подклас" или "категория"). Като цяло има 3 начина да го представите в базата данни:
- „Всички класове в една таблица“: Имайте само една таблица, "покриваща" родителските и всички дъщерни класове (т.е. с всички родителски и дъщерни колони), с ограничение CHECK, за да се гарантира, че дясното подмножество от полета не е NULL (т.е. две различни деца не се "смесват").
- „Клас бетон на маса“: Имайте различна маса за всяко дете, но няма таблица за родители. Това изисква връзките на родителите (във вашия случай Inventory <- Storage) да се повтарят във всички деца.
- „Клас на маса“: Да имате родителска маса и отделна маса за всяко дете, което се опитвате да направите. Това е най-чистото, но може да струва известна производителност (най-вече при промяна на данни, не толкова при запитване, защото можете да се присъедините директно от дъщерно и да пропуснете родителя).
Обикновено предпочитам 3-тия подход, но налагам и двете присъствие и ексклузивносттата на дете на ниво приложение. Налагането и на двете на ниво база данни е малко тромаво, но може да се направи, ако СУБД поддържа отложени ограничения. Например:
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)
)
)
Това ще наложи както ексклузивността (поради CHECK
) и присъствието (поради комбинацията от CHECK
). и FK1
/FK2
) на детето.
За съжаление MS SQL Server не поддържа отложени ограничения, но може да успеете да „скриете“ цялата операция зад съхранени процедури и да забраните на клиентите да променят директно таблиците.
Само ексклузивността може да бъде наложена без отложени ограничения:
STORAGE_TYPE
е тип дискриминатор, обикновено цяло число за спестяване на място (в примера по-горе, 0 и 1 са „известни“ на вашето приложение и се интерпретират съответно).
VAN.STORAGE_TYPE
и WAREHOUSE.STORAGE_TYPE
могат да бъдат изчислени (известни още като "изчислени") колони, за да се спести място за съхранение и да се избегне необходимостта от CHECK
с.
--- РЕДАКТИРАНЕ ---
Изчислените колони ще работят под SQL Server по следния начин:
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".
За съжаление SQL Server изисква изчислена колона, която се използва в чужда ключ, който трябва да бъде Устойчив. Други бази данни може да нямат това ограничение (напр. виртуалните колони на Oracle), което може да спести малко място за съхранение.