【问题标题】:How to create multiple one to one's如何创建多个一对一
【发布时间】:2012-02-28 18:13:48
【问题描述】:

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

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

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

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

【问题讨论】:

  • 如果您真的需要一对一的关系,请彻底评估。大多数时候,当你认为你需要一个时,你真的不需要。
  • 使用 EER。这里的关键是识别关系类型is ahas/belongs to,或者它们是同一个超类型的子类型。然后将您的实体简化为表格。
  • 啊,好吧,那么 Storage Table 是一个超类,而 Van/Warehouse Tables 是子类型,但现在呢?大声笑
  • 如果你想让你的子类型互斥并且它们属于不同的表,我想,没有直接的方法。

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


【解决方案1】:

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

  1. “一张表中的所有类”:只有一张表“覆盖”父类和所有子类(即包含所有父子列),并带有 CHECK 约束以确保正确的字段子集是非 NULL(即两个不同的孩子不会“混合”)。
  2. “每个表的具体类”:为每个子表设置不同的表,但没有父表。这需要在所有子级中重复父级关系(在您的情况下为 Inventory
  3. “每个表的班级”:为每个孩子有一个父表和一个单独的表,这就是您想要做的。这是最干净的,但会消耗一些性能(主要是在修改数据时,而不是在查询时,因为您可以直接从子节点加入并跳过父节点)。

我通常更喜欢第 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)和存在(由于CHECKFK1/FK2 的组合)。

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


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

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

【讨论】:

  • 嗨@Branko,感谢您的出色回答。但是,我对您回复的最后几句话感到困惑。您是否介意详细说明:(1)为什么在您的上一张图中,VAN 和 WAREHOUSE 表有一个 STORAGE_TYPE 列(我想,所有的值都相同:VAN 为 0,WAREHOUSE 为 1),(2)究竟是什么应该进入计算/计算列的内容(我对这里计算的内容感到困惑)?非常感谢:)
  • @youngrrrr VAN 和 WAREHOUSE 有一个 STORAGE_TYPE,因此他们可以在其上声明一个 CHECK。这样,如果 STORAGE 行是 VAN 行的父级,它不能也是 WAREHOUSE 行的父级(因为 STORAGE.STORAGE_TYPE 为 0,WAREHOUSE 中的 CHECK 失败)。反之亦然:如果 STORAGE 是 WAREHOUSE,则 STORAGE TYPE 为 1,这意味着如果有人试图在 VAN 中插入相应的行,则 VAN 中的 CHECK 将失败。计算列只是为了节省空间 - 因为它在给定表中始终具有相同的值,所以无需在每一行中物理地重复该值。
  • @youngrrrr 请注意,VAN(STORAGE_ID, STORAGE) 上有一个引用 STORAGE(STORAGE_ID, STORAGE_TYPE) 的 FK。在 WAREHOUSE 上也是如此。这样,paren 行(在 STORAGE 中)和子行(在 VAN 或 WAREHOUSE,但不是两者中)总是具有相同的 STORAGE_TYPE。
  • 嘿@Branko,感谢您抽出宝贵时间回答我的问题。我仍然不清楚的一件事是:使用计算列如何节省空间?看起来我们仍然在物理上重复每一行中的 STORAGE_TYPE。原谅我;我对计算列不太熟悉,使用 Google 进行解释也无济于事。再次感谢您的回复!!!
  • @youngrrrr 没问题!回答您的问题:计算列是即时计算的,而不是物理存储在数据库中(除非 PERSISTED)。未存储在数据库中 = 节省空间。是否可以在非持久计算列上实际定义 FK 是特定于 DBMS 的。不幸的是,SQL Server 需要 FK 的持久计算列,所以我关于“节省空间”的猜想在 SQL Server 下实际上是错误的(参见编辑)。 OTOH,我相信 Oracle 等效的计算列(所谓的虚拟列)可以在 FK 上使用,而无需持久化。
【解决方案2】:

正如您所说,有很多解决方案。我建议从最简单的解决方案开始,然后在性能或存储出现问题时进行优化。最简单的解决方案(但在存储方面不是最佳的)是有一个存储表,其中包含存储类型列(指示该行是代表货车还是仓库),以及用于货车属性和仓库属性的列。在代表货车的行中,仓库属性的列都将为空。在代表 Warehouse 的行中,Van 属性的列都将为空。

这样,您可以减少表的数量,并让您的查询保持简洁。如果存储空间紧张,请准备好重新考虑您的决定。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-29
      • 2017-11-11
      • 2021-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多