您正在使用继承(在实体关系建模中也称为“子类”或“类别”)。一般来说,在数据库中有3种方式来表示它:
-
“一张表中的所有类”:只有一张表“覆盖”父类和所有子类(即包含所有父子列),并带有 CHECK 约束以确保正确的字段子集是非 NULL(即两个不同的孩子不会“混合”)。
-
“每个表的具体类”:为每个子表设置不同的表,但没有父表。这需要在所有子级中重复父级关系(在您的情况下为 Inventory
-
“每个表的班级”:为每个孩子有一个父表和一个单独的表,这就是您想要做的。这是最干净的,但会消耗一些性能(主要是在修改数据时,而不是在查询时,因为您可以直接从子节点加入并跳过父节点)。
我通常更喜欢第 3 种方法,但在应用程序级别强制执行子项的存在和排他性。在数据库级别强制执行这两个操作有点麻烦,但如果 DBMS 支持延迟约束,则可以完成。例如:
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 does not support deferred constraints,但您也许可以将整个操作“隐藏”在存储过程后面,并禁止客户端直接修改表。
可以在没有延迟约束的情况下强制执行排他性:
STORAGE_TYPE 是一个类型鉴别器,通常是一个整数以节省空间(在上面的示例中,0 和 1 对您的应用程序来说是“已知的”并进行相应的解释)。
VAN.STORAGE_TYPE 和 WAREHOUSE.STORAGE_TYPE 可以计算(也称为“计算”)列以节省存储空间并避免需要 CHECKs。
--- 编辑---
计算列在 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 要求在外键中使用的计算列 是 PERSISTED。其他数据库可能没有这个限制(例如Oracle的虚拟列),可以节省一些存储空间。