表关系的最佳模式设计,可强制执行完整性

mea*_*ode 6 database schema-design

给定一个模型'A'表,可以有多个子模型'B',其中'B'将有一个或多个子模型'C'......这听起来很简单,但我需要对每个'A'强制执行',任何'B'必须有一个独特的'C'集合..例如,C不能是两个'B'的孩子,它们是同一个父母'A'的一部分..但是'C'可以是一个孩子多个'B'给出每个'B的父'A'是不同的...

这有意义还是我应该对我的场景进行模糊处理?提前喝彩!

请注意,我知道此策略将在应用程序中强制执行,但我不希望数据库处于无效状态.

编辑:大家好,很棒的反馈,所以首先我要感谢大家与我分享你的知识.

为了澄清这种情况,我将解释一下情况,但这里有一些注意事项:

'A'具有零个或多个'B','B'隐含地与'A'相关联,并且因此总是只有一个'A'的孩子.'C'在某种程度上是与许多'B'以及数据库中的其他元素相关联的根实体.


这是真实的故事:

这是一个包含许多简报(A)的网站,许多成员(C),简报可以有很多提交(B),其中提交将始终有一个或多个相关成员.这个想法是提交实际上可以是一种协作,每个成员都没有比其他成员更多的"权力",但是会有一个不同的系统来验证成员如何一起工作的政策.

因此,简而言之,成员只能提交一份提交,而提交可以有许多成员(协作者).

希望有所帮助,但我想你已经给了我很多帮助!

史蒂夫.

Jon*_*ler 3

我认为您需要 SQL 标准断言,(不幸的是)实际的 DBMS 很大程度上未实现这些断言。

所有答案都同意存在三个主表,称为 TableA、TableB 和 TableC,每个表都包含自己的 ID 列:

TableA (A_ID PRIMARY KEY, ...)
TableB (B_ID PRIMARY KEY, ...)
TableC (C_ID PRIMARY KEY, ...)
Run Code Online (Sandbox Code Playgroud)

从问题的描述中并不清楚单个B值是否可以有多个A父条目。很明显,单个 C 可以有多个 B 父条目。如果 B 与单个 A 绑定,则 TableB 的设计可以修改为:

TableB (B_ID, ..., A_ID REFERENCES TableA)
Run Code Online (Sandbox Code Playgroud)

如果一个 B 可以与多个不同的 A 关联,那么这种连接最好用连接表来表示:

A_and_B (A_ID REFERENCES TableA,
         B_ID REFERENCES TableB,
         PRIMARY KEY (A_ID, B_ID)
        )
Run Code Online (Sandbox Code Playgroud)

从描述中也不清楚与 B 关联的 C 是否对于与 B 关联的每个 A 都必须相同,或者不同的 A 是否可以引用相同的 B,以及与 A1 的 B 关联的 C 集合可以不同于与 A2 的 B 相关联的 C 集合。(当然,如果一个B只能与一个A关联,这个问题就没有意义了。)

出于本答案的目的,我将假设任何 B 都与单个 A 关联,因此 TableB 的结构包括 A_ID 作为外键。由于单个C可以与多个B相关联,因此相关结构是一个新的连接表:

B_and_C (B_ID REFERENCES TableB,
         C_ID REFERENCES TableC,
         PRIMARY KEY (B_ID, C_ID)
        )
Run Code Online (Sandbox Code Playgroud)

简化(通过省略有关可延迟性和即时性的规则)断言如下所示:

CREATE ASSERTION assertion_name CHECK ( <search_condition> )
Run Code Online (Sandbox Code Playgroud)

因此,一旦我们有了一组设计决策,我们就可以编写一个断言来验证数据。给定表 TableA、TableB(带有外键 A_ID)、TableC 和 B_and_C,要求给定 C_ID 在完整 A 中出现的次数为 1。

CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
     NOT EXISTS (
         SELECT A_ID, COUNT(C_ID)
             FROM TableB JOIN B_and_C USING (C_ID)
             GROUP BY A_ID
             HAVING COUNT(C_ID) > 1
     )
)
Run Code Online (Sandbox Code Playgroud)

[修正:我认为这更准确:

CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
     NOT EXISTS (
         SELECT A_ID, C_ID, COUNT(*)
             FROM TableB JOIN B_and_C USING (C_ID)
             GROUP BY A_ID, C_ID
             HAVING COUNT(*) > 1
     )
)
Run Code Online (Sandbox Code Playgroud)

]

连接条件集随表连接方式的其他规则而变化,但总体约束结构保持不变 - 对于特定 A_ID,不得存在对给定 C_ID 的多个引用。


在下面的评论中,meanmycode 指出:

我感觉我的设计有缺陷。我现实世界的逻辑是“B”总是至少有一个孩子“C”。这是没有意义的,因为“B”必须存在才能附加其子级。数据库目前允许将“B”附加到“A”,而无需至少有一个“C”..孩子,我将修改“B”,以便它有一个引用其的字段主要子项“C”,以及具有附加“C”的子集合,但现在我有一个集合,该集合还可以包含“B”指定的主“C”,这是......错误的。

是否有一种数据库模式可以推断“一个或多个子项”规则,而不是零个或多个?

我认为你的模型确实有问题。如果必须已经存在一个引用新创建的 B 的 C,则很难创建 B,特别是如果 C 必须仅引用现有的 B。我想到了“先有鸡还是先有蛋”这句话。因此,通常情况下,您允许 B 在这样的上下文中具有零个或多个 C。

您还没有规定 TableB 是否有 A_ID 外键,或者是否有像 A_and_B 这样的链接表。如果它有外键,那么在创建它引用的 A 之前,您可能无法创建 B。

我不认为在表 B 中包含一个 C ID 是一个好主意 - 它会导致非对称处理(更难的 SQL)。这也意味着,如果您需要删除该 C,则必须进行更新,以便从其当前所在的表中删除其他 C 引用之一,然后更新 B 记录中的值。这很混乱,要有礼貌。

我认为您需要修改您的问题来定义您正在查看的实际表结构 - 沿着各种答案中显示的线路;您可以使用三点来表示其他但不相关的列。我建议的断言可能必须作为某种触发器来实现——它会进入 DBMS 特定的符号。


从摘要 (A)、提交内容 (B) 和成员 (C) 的修改后的描述中可以清楚地看出,单个提交内容仅适用于一个摘要,因此提交内容可以有一个简单的外键来标识该摘要是提交内容为了。并且成员只能就特定简报的一份提交内容进行协作。将有一个“submission_collaborators”表,其中包含用于标识提交和成员的列,该组合是主键,每列是外键。

Briefs(Brief_ID, ...)
Submissions(Submission_ID, Brief_ID REFERENCES Briefs, ...)
Members(Member_ID, ...)
Submission_Collaborators(Submission_ID REFERENCES Submissions,
                         Member_ID REFERENCES Members,
                         PRIMARY KEY (Submission_ID, Member_ID)
                        )
Run Code Online (Sandbox Code Playgroud)

因此,要求以下查询不得返回任何行:

SELECT s.brief_id, c.member_id, COUNT(*)
    FROM submissions AS s JOIN submission_collaborators AS c
         ON s.submission_id = c.submission_id
    GROUP BY s.brief_id, c.member_id
    HAVING COUNT(*) > 1
Run Code Online (Sandbox Code Playgroud)

这与我嵌入到 CREATE ASSERTION (第二个变体)中的查询相同。您还可以挖掘额外的信息(简短标题、提交标题、会员姓名、各种日期等),但问题的核心是显示的查询必须不返回任何数据。