如何创建多个一对一的

时间:2012-02-07 09:57:52

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

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

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,但这似乎可以做得更好。

我可以看到这样做的几种方法,但它们看起来都错了,所以任何帮助都会很好!

3 个答案:

答案 0 :(得分:17)

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

  1. “一个表中的所有类”:只有一个表“覆盖”父类和所有子类(即所有父列和子列),并使用CHECK约束来确保正确的子集字段是非NULL(即两个不同的孩子不“混合”)。
  2. “每个表的具体类”:为每个子项设置不同的表,但没有父表。这需要在所有孩子中重复父母的关系(在您的情况下为Inventory&lt; - Storage)。
  3. “每个班级的分组”:为每个孩子设置一个父表和一个单独的表,这正是您要尝试的。这是最干净的,但可能会花费一些性能(主要是在修改数据时,而不是在查询时,因为您可以直接从子级加入并跳过父级)。
  4. 我通常更喜欢第三种方法,但在应用程序级别强制执行子项的存在排他性。在数据库级别执行这两者有点麻烦,但如果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的组合)。

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


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

    enter image description here

    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".
    

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

答案 1 :(得分:1)

正如你所说,有很多解决方案。我建议从最简单的解决方案开始,然后在性能或存储成为问题时进行优化。最简单的解决方案(但在存储方面不是最佳的)是使存储表具有存储类型的列(指示行是代表货车还是仓库),还有Van属性列和Warehouse属性。在表示Van的行中,Warehouse属性的列都将为null。在表示Warehouse的行中,Van属性的列都将为null。

这样,您减少了表的数量,并使您的查询保持简洁。如果存储紧张,请准备好重新审视您的决定。

答案 2 :(得分:1)

在我看来,库存商品可能会改变位置,所以我会选择这样的东西。

enter image description here