Inventory Table <*-----1> Storage Table <1-----1> Van Table ^ 1 |-------1> Warehouse Table
由于Van和Warehouse表相似,因此使用Storage表,但如何在Storage和Warehouse / Van表之间创建关系?有意义的是,它们需要为1比1,因为存储对象只能是1个存储位置和类型.
我确实有Van / Warehouse表链接到StorageId主键,然后添加一个约束,以确保Van和Warehouse表没有相同的StorageId,但这似乎可以做得更好.
解决方法
>“一个表中的所有类”:只有一个表“覆盖”父类和所有子类(即包含所有父列和子列),具有CHECK约束以确保正确的字段子集为非NULL(即两个不同的孩子不要“混合”).
>“每个表的具体类”:为每个子项创建一个不同的表,但没有父表.这需要在所有孩子中重复父母的关系(在您的情况下为Inventory< - Storage).
>“每个表的类”:为每个孩子设置一个父表和一个单独的表,这正是您要做的.这是最干净的,但可能会花费一些性能(主要是在修改数据时,而不是在查询时,因为您可以直接从子进程加入并跳过父进程).
我通常更喜欢第三种方法,但在应用程序级别强制执行子项的存在和排他性.在数据库级别执行这两者有点麻烦,但如果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(也称为“计算”)列以节省存储空间并避免需要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,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的虚拟列),这可以节省一些存储空间.