Вы используете наследование (также известное в моделировании отношений сущностей как «подкласс» или «категория»).В общем, существует 3 способа представления его в базе данных:
- «Все классы в одной таблице»: Имеют только одну таблицу, «охватывающую» родительский и все дочерние классы(т. е. со всеми родительскими и дочерними столбцами), с ограничением CHECK, чтобы гарантировать, что правый поднабор полей не равен NULL (т. е. два разных потомка не «смешиваются»).
- «Конкретный класс дляtable ": У каждого ребенка есть отдельная таблица, но нет родительской таблицы.Это требует повторения родительских отношений (в вашем случае Inventory <- Storage) для всех дочерних элементов. </li>
- «Класс на таблицу»: Наличие родительской таблицы и отдельной таблицы для каждого ребенка,это то, что вы пытаетесь сделать.Это наиболее чисто, но может стоить некоторой производительности (в основном при изменении данных, не так много при запросах, потому что вы можете присоединиться напрямую от дочернего элемента и пропустить родительский).
Я обычно предпочитаю третий подход, нообеспечить присутствие и исключительность ребенка на уровне приложения.Выполнение обоих на уровне базы данных немного обременительно, но может быть сделано, если СУБД поддерживает отложенные ограничения.Например:
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
s.
--- EDIT ---
Вычисляемые столбцы будут работать под 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 требуется вычисляемый column , который используется в иностранном ключе , чтобы быть ПЕРЕСЕРИРОВАННЫМ.Другие базы данных могут не иметь этого ограничения (например, виртуальные столбцы Oracle), что может сэкономить место для хранения.