Oracle 如何在一个表中的一列和两个父表中的一列之间实施一对多关系?

Oracle 如何在一个表中的一列和两个父表中的一列之间实施一对多关系?,oracle,database-design,referential-integrity,Oracle,Database Design,Referential Integrity,我在现有的数据库应用程序中有三个表。我没有ERDs或其他关于表关系的文档。没有现有的主键或外键。 我希望对关系进行建模,并在理想情况下使用约束强制执行关系。 订单表可以有任意数量的零件订单。Orders.part列的值必须存在于零件库存表或零件库存表中。我认为最初的设计应该有一个带有类型列的parts表,但这就是我所拥有的。是否有方法对这些关系进行建模/实施 命令 零件库存 第四部分 不幸的是没有 您可以定义两个外键约束,但它们都需要对表中的每一行有效 第二个问题是char数据类型的选择。ORD

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

命令 零件库存 第四部分 不幸的是没有

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


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

仅考虑到您现有的表,您不能这样做。但这并不是说不可能做到。:-)

理想情况下,您应该将
ORDER.PART
更改为
CHAR(50)
,以匹配其他表上的
PARTS\u NUM
字段,否则您将无法在订单上存储所有可能的PARTS\u NUM。也许这是商业原因,也许有人只是无知。没关系——如果可以的话,试着把它修好。但这不会完全阻止你

创建另一个表-我们称之为PARTS\u ALL。它有两个字段-PARTS_NUM(应与PARTS_INV和PARTS_noniv上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\U INV和PARTS\U NONIV复制到所有零件中,添加适当的源值:

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\U INV和PARTS\U NONIV上定义了两个触发器,以将这些表上的插入和删除传播到所有零件:

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\u INV和PARTS\u noniv中的行,这些更改将传播到所有PARTS

现在(最后!)您可以定义从订单到所有零件的外键,并获得所需的验证:

ALTER TABLE ORDER
  ADD CONSTRAINT ORDER_FK1
    FOREIGN KEY (PART) REFERENCES ALL_PARTS (PARTS_NUM);
这是否理想?不。理想情况下,你应该去掉PARTS\u INV和PARTS\u nonv,用所有的零件替换它们,修改你的所有应用程序、web应用程序和后台软件以使用新表-所以可能不会发生-但是考虑到你发现自己身处其中的情况,这可能是你能做的最好的事情


这里有一种实现方法,使用物化视图

在我完整的工作示例中,我创建了两个表,T1和T2。每个表都有一个ID列,另一个列在两个表中不相同(一个表中有名称,另一个表中有价格,甚至数据类型也不相同)。任务是从两个表的所有列ID的联合中创建主键

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

您可能需要测试使用三个表(T1、T2和T)可以做的各种事情,以了解这些是如何工作的

需要记住两件事:(1)表上的PK和FK约束通常在插入/更新/删除时检查(如果约束是“立即的”);对于T1和T2,ID上的PK约束将分别为。但是,仅在提交时检查MV上的jointPK约束。表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);

您还可以在
订单
表上创建
插入和更新触发器
,以满足要求

根据您的评论,
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;
/

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


干杯

这是一个常见问题。在考虑发帖之前,请始终用谷歌搜索任何错误消息,以及你的问题/问题/目标的许多清晰、简洁和准确的措辞,有或没有你的特定字符串、名称和行号,然后阅读许多答案。如果你发布一个问题,用一句话作为标题。请参阅文本上方的投票箭头鼠标(&U)。可能重复的是PARTS\u INV.PART\u NUM UNION所有PARTS\u nonv.PARTS\u NUM唯一?感谢您的回复。我正在考虑这些建议。回答问题:两个表中的零件都是唯一的。此外,parts\u inv或parts\u noniv中的零件都不超过20个字符,因此可能会更改数据类型以匹配订单。在我看来(公认为非专业人士),这是一个糟糕的建议。显然,您还需要为更新操作编写触发器代码。您必须防止在新创建的表上使用DML。对于物化视图,而不是新的表加触发器,您不会遇到这些问题。@mathguy:我最初考虑使用materialized视图,但我对它们没有太多经验,所以我不熟悉如何使用它们。是否可以在物化视图上定义主键或唯一键,然后是否可以在引用MV的另一个表上创建外键?我试着在dbfiddle.uk上简单地使用它,但发现我无法在那里制作MV,所以放弃了这个想法。我很想看到一个丰满的
ALTER TABLE ORDER
  ADD CONSTRAINT ORDER_FK1
    FOREIGN KEY (PART) REFERENCES ALL_PARTS (PARTS_NUM);
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);
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;
/