我认为您需要 SQL 标准断言,这些断言(不幸的是)在很大程度上没有被实际的 DBMS 实现。
所有答案都同意存在三个名为 TableA、TableB 和 TableC 的主表,每个表都包含自己的 ID 列:
TableA (A_ID PRIMARY KEY, ...)
TableB (B_ID PRIMARY KEY, ...)
TableC (C_ID PRIMARY KEY, ...)
从问题的描述中并不清楚单个B值是否可以有多个A父条目。很明显,单个 C 可以有多个 B 父条目。如果 B 与单个 A 绑定,则 TableB 的设计可以修改为:
TableB (B_ID, ..., A_ID REFERENCES TableA)
如果一个 B 可以与多个不同的 A 相关联,则连接最好由连接表表示:
A_and_B (A_ID REFERENCES TableA,
B_ID REFERENCES TableB,
PRIMARY KEY (A_ID, B_ID)
)
从描述中也不清楚与 B 关联的 C 是否对于与 B 关联的每个 A 都必须相同,或者不同的 A 是否可以引用相同的 B,以及与 B 关联的 C 的集合对于 A1可以不同于与 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)
)
简化(通过省略有关可延迟性和即时性的规则)断言看起来像:
CREATE ASSERTION assertion_name CHECK ( <search_condition> )
因此,一旦我们有了一组设计决策,我们就可以编写一个断言来验证数据。给定表 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
)
)
[修正:我认为这更准确:
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
)
)
]
连接条件集随表连接方式的其他规则而异,但总体约束结构保持不变 - 对于特定 A_ID,对给定 C_ID 的引用不得超过一个。
在下面的评论中,meandmycode 注释:
我觉得我的设计有缺陷。我的真实世界逻辑是“B”总是至少有一个孩子“C”。鉴于“B”必须在其子项可以附加之前存在,这没有任何意义。数据库目前允许在没有至少一个“C”的情况下将“B”附加到“A”。孩子,我将修改“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)
)
因此,要求是以下查询必须不返回任何行:
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
这是我在 CREATE ASSERTION(第二个变体)中嵌入的相同查询。您也可以挖掘出额外的信息(简短标题、提交标题、成员名称、各种日期等),但问题的核心是显示的查询必须不返回任何数据。