sql - 如何创建多个一对一

标签 sql sql-server database database-design relational-database

我有一个包含许多表的数据库,除了一点点之外一切看起来都不错......

Inventory Table <*-----1> Storage Table <1-----1> Van Table
                              ^
                              1
                              |-------1> Warehouse Table

使用 Storage 表是因为 Van 和 Warehouse 表很相似,但是如何在 Storage 和 Warehouse/Van 表之间创建关系?它们必须是 1 到 1 是有道理的,因为存储对象只能是 1 个存储位置和类型。 我确实将 Van/Warehouse 表链接到 StorageId 主键,然后添加约束以确保 Van 和 Warehouse 表不具有相同的 StorageId,但这似乎可以通过更好的方式完成。

我可以看到几种方法,但它们似乎都是错误的,所以任何帮助都会很好!

最佳答案

您正在使用继承(在实体关系建模中也称为“子类”或“类别”)。一般来说,在数据库中有3种表示方式:

  1. “一个表中的所有类”:只有一个表“覆盖”父类和所有子类(即具有所有父列和子列),并带有 CHECK 约束以确保正确的子集的字段是非 NULL(即两个不同的 child 不“混合”)。
  2. “每个表的具体类”:每个 child 都有一个不同的表,但没有父表。这需要在所有 child 中重复 parent 的关系(在您的情况下为库存 <- 存储)。
  3. “每张 table 上课”:为每个 child 准备一个父表和一个单独的表,这就是您想要做的。这是最干净的,但可能会降低一些性能(主要是在修改数据时,查询时不是那么多,因为您可以直接从子节点加入并跳过父节点)。

我通常更喜欢第 3 种方法,但在应用程序级别强制 child 的存在排他性。在数据库级别强制执行这两者有点麻烦,但如果 DBMS 支持延迟约束,则可以完成。例如:

enter image description here

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)和存在(由于 CHECKFK1 的组合)/FK2) 的 child 。

不幸的是,MS SQL Server does not support deferred constraints ,但您可以将整个操作“隐藏”在存储过程后面,并禁止客户端直接修改表。


可以在没有延迟约束的情况下强制执行排他性:

enter image description here

STORAGE_TYPE 是一个类型鉴别符,通常是一个整数以节省空间(在上面的示例中,0 和 1 对您的应用程序是“已知的”并相应地进行解释)。

VAN.STORAGE_TYPEWAREHOUSE.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的虚拟列),可以节省一些存储空间。

关于sql - 如何创建多个一对一,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9174200/

相关文章:

c# - Nhibernate - 获取异常执行 SQL 查询

c# - SQL Server 和 C# 之间的性能问题

sql-server - 获取几何区域内的随机点

database - 是否有一种优雅的方式来存储双重关系(即用户 1 和用户 2 是 friend )

c# - 数据未写入MYSQL数据库

c# - SQL Azure 和 SQL Server 2008 有什么区别?

sql - 将 WITH 语句重写为 SQL 中的子查询语句?

sql - 将动态 SQL 的结果放入 sql-server 的变量中

sql-server - SQL Server 日期时间 LIKE 选择?

database - Rails 3 建立与另一个数据库的连接仅适用于一个查询