【问题标题】:How to enforce a one-many relationship between a column in one table and a column in two parent tables?如何在一个表中的列和两个父表中的列之间强制执行一对多关系?
【发布时间】:2019-07-21 19:55:38
【问题描述】:

我在现有的数据库应用程序中有三个表。我没有关于表关系的 ERD 或其他文档。没有现有的主键或外键。 我想对关系进行建模,并在理想情况下使用约束来强制执行它。
订单表可以有任意数量的零件订单。 Orders.part 列的值必须存在于 Parts_inv 表或 Parts_noninv 表中。我认为原始设计应该有一个带有类型列的零件表,但这是我所拥有的。有没有办法模拟/实施这些关系?

订单

order     char(10)
part      char(20)

Parts_inv

part_num  char(50)

Parts_noninv

part_num  char(50)

【问题讨论】:

  • 这是一个常见问题解答。在考虑发布之前,请始终在谷歌上搜索任何错误消息以及您的问题/问题/目标的许多清晰、简洁和精确的措辞,有或没有您的特定字符串、名称和行号,然后阅读许多答案。如果您发布问题,请使用一个短语作为标题。请参阅How to Ask 和投票箭头鼠标悬停文本。
  • PARTS_INV.PART_NUM UNION ALL PARTS_NONINV.PARTS_NUM 是唯一的吗?
  • 感谢您的回复。我在看建议。回答提出的问题:两个表格中的零件都是独一无二的。此外,parts_inv 或 parts_noninv 中的任何部分都不超过 20 个字符,因此可以更改数据类型以匹配订单。

标签: oracle database-design referential-integrity


【解决方案1】:

仅考虑到您拥有的表格,您无法做到这一点。但这并不是说它不能完成。 :-)

理想情况下,您应该将 ORDER.PART 更改为 CHAR(50) 以匹配其他表上的 PARTS_NUM 字段 - 否则您将无法在 ORDER 中存储所有可能的 PARTS_NUM。可能有商业原因使它保持原样,也许有人只是一无所知。没关系 - 如果可以,请尝试修复它。不过,它不会完全阻止你。

创建另一个表 - 我们称之为 PARTS_ALL。它有两个字段 - PARTS_NUM 应该与 PARTS_INV 和 PARTS_NONINV 上的 PARTS_NUM 字段的数据类型匹配,而 SOURCE 应该是 VARCHAR2(1)。使 (PARTS_NUM, SOURCE) 的组合成为主键。所以看起来像

CREATE TABLE ALL_PARTS
  (PARTS_NUM   CHAR(50),
   SOURCE      VARCHAR2(1) NOT NULL,
   CONSTRAINT PK_ALL_PARTS
     PRIMARY KEY (PARTS_NUM, SOURCE));

然后将数据从 PARTS_INV 和 PARTS_NONINV 复制到 ALL_PARTS,并添加适当的 SOURCE 值:

INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE)
  SELECT PARTS_NUM, 'I' AS SOURCE FROM PARTS_INV
  UNION ALL
  SELECT PARTS_NUM, 'I' AS SOURCE FROM PARTS_NONINV

现在您在 PARTS_INV 和 PARTS_NONINV 上定义了几个触发器,以将这些表上的插入和删除传播到 ALL_PARTS:

CREATE TRIGGER PARTS_INV_AID
  AFTER INSERT OR DELETE ON PARTS_INV
  FOR EACH ROW
BEGIN
  IF INSERTING THEN
    INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE) VALUES (:NEW.PARTS_NUM, 'I');
  ELSIF DELETING THEN
    DELETE FROM ALL_PARTS WHERE PARTS_NUM = :OLD.PARTS_NUM;
  END IF;
END PARTS_INV_AID;
/

CREATE TRIGGER PARTS_NONINV_AID
  AFTER INSERT OR DELETE ON PARTS_NONINV
  FOR EACH ROW
BEGIN
  IF INSERTING THEN
    INSERT INTO ALL_PARTS (PARTS_NUM, SOURCE) VALUES (:NEW.PARTS_NUM, 'N');
  ELSIF DELETING THEN
    DELETE FROM ALL_PARTS WHERE PARTS_NUM = :OLD.PARTS_NUM;
  END IF;
END PARTS_NONINV_AID;
/

现在您的应用程序套件可以像往常一样继续从 PARTS_INV 和 PARTS_NONINV 插入和删除行,并且这些更改将传播到 ALL_PARTS。

现在(终于!)您可以定义从 ORDERS 到 ALL_PARTS 的外键并获得所需的验证:

ALTER TABLE ORDER
  ADD CONSTRAINT ORDER_FK1
    FOREIGN KEY (PART) REFERENCES ALL_PARTS (PARTS_NUM);

这是理想的吗?不。理想情况下,您会摆脱 PARTS_INV 和 PARTS_NONINV,并用 ALL_PARTS 替换它们,修改所有应用程序和 Web 应用程序和后台软件以使用新表 - 所以可能不会发生 - 但考虑到您自己发现的情况在它可能和你能做的一样好。

dbfiddle here

【讨论】:

  • 在我(公认的非专业人士)看来,这是个糟糕的建议。显然,您还需要为 UPDATE 操作编写触发代码。而且您必须在新创建的表上防止 DML。物化视图没有这些问题,而不是新表和触发器。
  • @mathguy:我最初考虑使用物化视图,但我没有很多经验,所以我不熟悉可以用它们做什么和不能做什么.可以在物化视图上定义主键或唯一键,然后可以在引用 MV 的另一个表上创建外键吗?我尝试在 dbfiddle.uk 上简单地使用它,但发现我无法在那里创建 MV,因此放弃了这个想法。不过,我希望看到使用 MV 的完全充实的实现。 :-)
  • 好的,我刚刚发布了这样一个完全充实(但最少)的实现。
【解决方案2】:

这是实现此目的的一种方法,使用物化视图。

在我的完整工作示例中,我创建了两个表 T1 和 T2。每个都有一个 ID 列,以及两个表中不同的另一列(一个是 NAME,另一个是 PRICE,甚至不是相同的数据类型)。任务是从两个表中的所有列 ID 的 UNION ALL 创建一个主键。

首先,ID 必须是每个表中的 PK。然后我们从两个表的ID列的UNION ALL中创建一个物化视图(MV),并将MV中的列ID声明为MV的主键。然后,在最后一步中,我创建了一个子表 T,它有一个列 ID,外键指向 MV(不是指向 T1 或 T2,而是联合两个表中的所有 ID)。

您可能想要测试您可以对三个表(T1、T2 和 T)执行的各种操作,以了解这一切是如何工作的。

要记住两件事: (1) 表上的 PK 和 FK 约束通常在 INSERT / UPDATE / DELETE 时检查(如果约束是“立即的”);这将是针对 ID 的 PK 约束的情况,分别用于 T1 和 T2。但是,MV 上的 joint PK 约束仅在 COMMIT 时检查。 “立即”检查表 T 上的 FK 约束(在每个 INSERT、UPDATE 或 DELETE - 或 MERGE - 语句之后)。 (2) 检查 MV 上的 PK 约束会为 T1 和 T2 上的 DML 语句增加边际开销。

所以,这里是:

create table t1 (id number primary key, name varchar2(100));
create table t2 (id number primary key, price number not null);

create materialized view log on t1 with primary key, rowid including new values;
create materialized view log on t2 with primary key, rowid including new values;

create materialized view t12 (id, rid, source)
refresh fast on commit
as
select id, rowid, 't1' from t1 union all select id, rowid, 't2' from t2
;

alter materialized view t12 add constraint t12_pk primary key (id);

create table t (id number references t12, eff_date date not null);

【讨论】:

    【解决方案3】:

    很遗憾没有。

    您可以定义两个外键约束,但它们都需要对表中的每一行都有效。

    第二个问题是char数据类型的选择。 ORDERS 中没有char(10) 值将匹配任一父表中的char(50)。可能选择数据类型的人不熟悉。

    【讨论】:

    • 关于比较 CHAR(10) (or (20) or (whatever)) 和 CHAR(50) - 我也这么认为,但是在创建 this dbfiddle 之后我发现我错了。显然,值可以存储在较长的字段中,而不能存储在较短的字段中,但是当非尾随空白部分“相同”时,它们看起来是相等的。
    • @BobJarvis - 这是因为,如文档所述,如果较长的值等于较短的值,则两个 CHAR 值比较相等,并在右侧填充空格。我们可能不喜欢这个定义,但甲骨文就是这么说的。
    • 似乎将CHAR(20) 加入CHAR(50) 的规则与强制FK 约束的规则不同,除非我遗漏了一些东西(完全有可能,因为我从不使用CHAR)。 dbfiddle
    【解决方案4】:

    您还可以在ORDERS 表上创建insert and update Trigger 来满足要求。

    根据您的评论,PART_NUM 在两个表中将是唯一的。因此,您可以按如下方式创建触发器:

    CREATE OR REPLACE TRIGGER ORDERS_TRG BEFORE
        INSERT OR UPDATE ON ORDERS
        FOR EACH ROW
    DECLARE
        LV_COUNT   NUMBER;
    BEGIN
        SELECT
            1
        INTO LV_COUNT
        FROM
            DUAL
        WHERE
            :NEW.PART IN (
                SELECT
                    PART_NUM
                FROM
                    PARTS_INV
                UNION ALL
                SELECT
                    PART_NUM
                FROM
                    PARTS_NONINV
            );
    
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE;
    END ORDERS_TRG;
    /
    

    db<>fiddle demo

    根据您的要求更新异常处理部分。

    干杯!!

    【讨论】:

      猜你喜欢
      • 2011-08-26
      • 2021-09-10
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2021-03-21
      • 2023-03-25
      • 1970-01-01
      • 2018-03-06
      相关资源
      最近更新 更多