如何创建多个一对一

发布于 2025-01-03 09:22:20 字数 483 浏览 2 评论 0原文

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

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

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

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

I have a database set up with many tables and it all looks good apart from one bit...

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

The Storage table is used since the Van and Warehouse table are similar but how do I create a relationship between Storage and Warehouse/Van tables? It would make sense they need to be 1 to 1 as a Storage object can only be 1 Storage place and type.
I did have the Van/Warehouse table link to the StorageId primary key and then add a constraint to make sure the Van and Warehouse tables dont have the same StorageId, but this seems like it could be done a better way.

I can see several ways of doing this but they all seem wrong, so any help would be good!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

微暖i 2025-01-10 09:22:20

您正在使用继承(在实体关系建模中也称为“子类”或“类别”)。一般来说,有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)
    )
)

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

不幸的是,MS SQL Server不支持延迟约束,但您也许可以将整个操作“隐藏”在存储过程后面并禁止客户端直接修改表。


只需强制执行排他性,无需延迟约束:

在此处输入图像描述

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

VAN.STORAGE_TYPEWAREHOUSE.STORAGE_TYPE 可以计算(又名“已计算”)列,以节省存储空间并避免需要 CHECK s。

--- 编辑 ---

计算列将在 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 需要在外键中使用的计算列才能持久化。其他数据库可能没有这个限制(例如Oracle的虚拟列),这样可以节省一些存储空间。

You are using the inheritance (also known in entity-relationship modeling as "subclass" or "category"). In general, there are 3 ways to represent it in the database:

  1. "All classes in one table": Have just one table "covering" the parent and all child classes (i.e. with all parent and child columns), with a CHECK constraint to ensure the right subset of fields is non-NULL (i.e. two different children do not "mix").
  2. "Concrete class per table": Have a different table for each child, but no parent table. This requires parent's relationships (in your case Inventory <- Storage) to be repeated in all children.
  3. "Class per table": Having a parent table and a separate table for each child, which is what you are trying to do. This is cleanest, but can cost some performance (mostly when modifying data, not so much when querying because you can join directly from child and skip the parent).

I usually prefer the 3rd approach, but enforce both the presence and the exclusivity of a child at the application level. Enforcing both at the database level is a bit cumbersome, but can be done if the DBMS supports deferred constraints. For example:

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)
    )
)

This will enforce both the exclusivity (due to the CHECK) and the presence (due to the combination of CHECK and FK1/FK2) of the child.

Unfortunately, MS SQL Server does not support deferred constraints, but you may be able to "hide" the whole operation behind stored procedures and forbid clients from modifying the tables directly.


Just the exclusivity can be enforced without deferred constraints:

enter image description here

The STORAGE_TYPE is a type discriminator, usually an integer to save space (in the example above, 0 and 1 are "known" to your application and interpreted accordingly).

The VAN.STORAGE_TYPE and WAREHOUSE.STORAGE_TYPE can be computed (aka. "calculated") columns to save storage and avoid the need for the CHECKs.

--- EDIT ---

Computed columns would work under SQL Server like this:

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

Unfortunately, SQL Server requires for a computed column which is used in a foreign key to be PERSISTED. Other databases may not have this limitation (e.g. Oracle's virtual columns), which can save some storage space.

一个人的旅程 2025-01-10 09:22:20

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

这样,您就可以减少表的数量,并使查询保持良好和简单。如果存储变得紧张,请准备好重新考虑您的决定。

As you say, there are many solutions. I would recommend starting with the simplest solution, then optimising later if performance or storage become problems. The simplest solution (but not optimal in terms of storage) would be to have a Storage table that has a column for storage type (indicating whether the row represents a van or a warehouse), plus columns for Van attributes as well as Warehouse attributes. In a row that represents a Van, the columns for the Warehouse attributes will all be null. In a row that represents a Warehouse, the columns for the Van attributes will all be null.

That way, you cut down on the number of tables, and keep your queries nice and simple. Be prepared to revisit your decision if storage becomes tight.

梓梦 2025-01-10 09:22:20

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

在此处输入图像描述

Somehow seems to me that inventory-items may change locations, so I would go with something like this.

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文