【问题标题】:How to add a foreign key referencing a composite primary key when you don't have all the columns, but can get them via another foreign key?当您没有所有列但可以通过另一个外键获取它们时,如何添加引用复合主键的外键?
【发布时间】:2017-09-06 20:33:53
【问题描述】:

我有以下表格:

create table AAA
(
    AAA_ID NUMBER
);

alter table AAA
    add constraint AAA_PK
    primary key (AAA_ID);

create table BBB
(
    BBB_ID NUMBER,
    AAA_ID NUMBER
);

alter table BBB
    add constraint BBB_PK
    primary key (BBB_ID, AAA_ID); --IMPORTANT

alter table BBB
    add constraint BBB_FK_01
    foreign key (AAA_ID)
    references AAA (AAA_ID);

create table CCC
(
    CCC_ID NUMBER,
    AAA_ID NUMBER
);

alter table CCC
    add constraint CCC_PK
    primary key (CCC_ID); --IMPORTANT

alter table CCC
    add constraint CCC_FK_01
    foreign key (AAA_ID)
    references AAA (AAA_ID);

create table CCC_BBB
(
    CCC_ID NUMBER,
    CCC_BBB_ID NUMBER,
    BBB_ID NUMBER
);

alter table CCC_BBB
    add constraint CCC_BBB_PK
    primary key (CCC_ID, CCC_BBB_ID);

alter table CCC_BBB
    add constraint CCC_BBB_FK_01
    foreign key (CCC_ID)
    references CCC (CCC_ID);

我想在引用BBBCCC_BBB 中添加一个外键约束。 BBB_ID 直接存在于CCC_BBB 中,但没有AAA_ID。但是,AAA_ID 存在于 CCC 中,由 CCC_BBB_FK_01 引用。是否可以用 SQL 来表达这个约束?

我更喜欢符合标准的解决方案,但也欢迎使用特定于 Oracle 的解决方案。

编辑。我被要求澄清现实世界的问题是什么,所以这是我的尝试(我不想在这里讨论真正的问题领域):

AAA 是一个过程。 BBB 是一个过程步骤。多个进程的步骤名称相似,但含义不同,因此该表具有复合键。 CCC 是一个流程实例。它们的 ID 是唯一的,因此该表没有复合 PK。 CCC_BBB 是在特定实例中采取的步骤列表。

我需要确保流程实例的步骤列表仅包含该流程允许的那些步骤。

【问题讨论】:

  • 我不相信你所描述的是可能的。
  • 看来你的理论例子没有完全意义。
  • 创建所有表后触发所有更改命令的一种方法
  • @AlexPoole 我已经修复了这个例子。我不能在这里展示问题域的真实对象,我会看看我是否能想出类似的东西。
  • 这就是为什么老手倾向于实现代理(技术,合成)主键并强制复合键作为唯一约束。代理主键避免将复合列级联到依赖表。

标签: sql oracle foreign-keys data-modeling


【解决方案1】:

您的问题似乎源于只应用了一半的复合键。

让我们先从技术性的非复合键开始。我稍微重命名了表名和列名以增强可读性。主键为粗体。

  • A (A_ID, col1, col2, ...)
  • AB (AB_ID, A_ID, col1, col2, ...)
  • AC(AC_ID、A_ID、col1、col2、...)
  • ABC(ABC_ID、AC_ID、AB_ID、col1、col2、...)

这里我们有你描述的情况:AC 和 AB 是 A 的孩子,ABC 是 AB 和 AC 的孩子,但是 DBMS 不能保证 BC 包含属于同一个 A 的 B 和 C。这是纯基于 ID 的数据库设计中的一个众所周知的缺点;它无法保证表层次结构的一致性。

现在复合键也是如此(这在自然键中很常见,但也适用于技术 ID):

  • A (A_ID, col1, col2, ...)
  • AB (A_ID, B_ID, col1, col2, ...)
  • AC(A_ID、C_ID、col1、col2、...)
  • ABC(A_ID、B_ID、C_ID、col1、col2、...)

这里保证了一致性,因为完整的父键始终是主键的一部分。

你正在做的是混合。您正在对最后一个表应用复合键,但在所有父表中都没有,所以为时已晚。您使用了非复合 ID 概念并遭受其一致性缺陷。

  • A (A_ID, col1, col2, ...)
  • AB (AB_ID, A_ID, col1, col2, ...)
  • AC(AC_ID、A_ID、col1、col2、...)
  • ABC (AC_ID, ACSUB_ID, AB_ID, col1, col2, ...)

【讨论】:

  • 所以简短的回答是否定的,除非我将 AAA_ID/A_ID 添加到我的 CCC/AC 表 PK,即使它是多余的?
  • 答案是:如果您使用非复合 ID,则没有您想要的一致性保证。如果您使用复合 ID,那么您会得到它,但您自己要保持一致:给 BBB 和 CCC 复合键,并组合两者的 CCC_BBB 键。这没有什么多余的。
  • 当然你也可以蒙混过关。给 CCC_BBB 冗余的 AAA_ID 并在 BBB(BBB_ID,AAA_ID) 上添加一个(多余的)唯一约束,然后您可以将其用于 CCC_BBB 中的外键。我只是不推荐这个。
【解决方案2】:

您可以使用物化视图连接两个表,然后将外键添加到此:

CREATE MATERIALIZED VIEW LOG ON CCC_BBB
   WITH SEQUENCE, ROWID(CCC_ID,CCC_BBB_ID,BBB_ID)
   INCLUDING NEW VALUES;

CREATE MATERIALIZED VIEW CCC_BBB_MV
   BUILD IMMEDIATE
   REFRESH FAST ON COMMIT
   AS SELECT CCC_ID,
             CCC_BBB_ID,
             BBB_ID,
             AAA_ID
      FROM   CCC_BBB b
             INNER JOIN
             CCC c
             ON ( b.CCC_ID = c.CCC_ID );

ALTER TABLE CCC_BBB_MV ADD CONSTRAINT ccc_bbb_mv__fk
  FOREIGN KEY ( AAA_ID, BBB_ID ) REFERENCES BBB ( AAA_ID, BBB_ID );

以上代码未经测试,但应说明解决方案

虽然它可能有效,但它有点小技巧,您只需将 AAA_ID 添加到您的 CCC_BBB 表即可使用更少的存储空间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多