如何创建多个一对一的

Luc*_*337 7 sql database sql-server database-design relational-database

我有一个设置了许多表的数据库,除了一点之外它看起来都很好......

Inventory Table <*-----1> Storage Table <1-----1> Van Table
                              ^
                              1
                              |-------1> Warehouse Table
Run Code Online (Sandbox Code Playgroud)

由于Van和Warehouse表相似,因此使用了Storage表,但如何在Storage和Warehouse/Van表之间创建关系?因为存储对象只能是1个存储位置和类型,所以它们需要为1比1.我确实有Van/Warehouse表链接到StorageId主键,然后添加一个约束,以确保Van和Warehouse表没有相同的StorageId,但这似乎可以做得更好.

我可以看到这样做的几种方法,但它们都显得不对,所以任何帮助都会很好!

Bra*_*vic 19

您正在使用继承(在实体关系建模中也称为"子类"或"类别").通常,有3种方法可以在数据库中表示它:

  1. "一个表中的所有类":只有一个表"覆盖"父类和所有子类(即包含所有父列和子列),具有CHECK约束以确保正确的字段子集非NULL(即两个不同的孩子们不"混").
  2. "每个表的具体类":为每个子项创建一个不同的表,但没有父表.这需要在所有孩子中重复父母的关系(在您的情况下,库存< - 存储).
  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)
    )
)
Run Code Online (Sandbox Code Playgroud)

这将强制执行孩子的排他性(由于CHECK)和存在(由于CHECKFK1/ 的组合FK2).

遗憾的是,MS SQL Server 不支持延迟约束,但您可以"隐藏"存储过程背后的整个操作,并禁止客户端直接修改表.


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

在此输入图像描述

STORAGE_TYPE是一个类型鉴别器,通常是一个节省空间的整数(在上面的例子中,0和1对应用程序是"已知的"并相应地进行解释).

VAN.STORAGE_TYPEWAREHOUSE.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".
Run Code Online (Sandbox Code Playgroud)

不幸的是,SQL Server要求在外键中使用的计算列为PERSISTED.其他数据库可能没有此限制(例如Oracle的虚拟列),这可以节省一些存储空间.

  • @youngrrrr VAN和WAREHOUSE具有STORAGE_TYPE,因此可以对其声明CHECK。这样,如果STORAGE行是VAN行的父级,则它也不能是WAREHOUSE行的父级(因为STORAGE.STORAGE_TYPE为0,导致WAREHOUSE中的CHECK失败)。反之亦然:如果STORAGE为WAREHOUSE,则STORAGE TYPE为1,这意味着如果有人尝试在其中插入相应的行,则VAN中的CHECK将失败。计算列只是为了节省空间-由于给定表中的列始终具有相同的值,因此无需在每一行中物理重复该值。 (2认同)