建立一对一关系

时间:2009-08-05 20:25:45

标签: oracle database-design

可以在Oracle中使用约束完全建模一对一关系吗?换句话说,PARENT实体总是完全具有子实体的n-CHILDREN,并且每个孩子只有一个父母。

将n视为数据库常量。

7 个答案:

答案 0 :(得分:5)

即使多个会话正在进行更新,这样做也是正确无误的。如果你尝试使用触发器,你会陷入混乱,而Oracle的声明性约束并不足以表达这一点。

可以按如下方式完成: -

  1. 在父表和子表上创建物化视图日志
  2. 创建将它们连接在一起的物化连接视图,并计算按父项分组的子项数。这必须是REFRESH FAST ON COMMIT
  3. 在物化连接视图上设置一个约束,即子记录的计数必须等于“n”(您的数据库常量)
  4. 然后,您可以执行一系列插入/更新/删除语句。提交时,物化视图将刷新,如果不满足条件,则此时将出现约束违规错误。

    特别值得一提的是,只包括未通过约束的行进入物化视图(HAVING count(ChildId)<> 5),这样就不会浪费任何存储空间。

答案 1 :(得分:3)

建立在管理者“鸡+鸡蛋”点上,您可以创建可延迟的约束,这些约束在提交时间之前未经过验证...这些可能会有所帮助吗?

e.g。

ALTER TABLE AGREEMENTS ADD CONSTRAINT name FOREIGN KEY (column) REFERENCES table (column) DEFERRABLE INITIALLY DEFERRED;

答案 2 :(得分:1)

我不知道怎么做。这是一个古老的问题“首先是鸡还是鸡蛋?”。如果还没有添加子项,如何约束父项?如何在没有父项的情况下添加子项?

您可以创建一个新表,称为“ValidParents”,其父ID只有N个孩子,并与触发器保持同步。

答案 3 :(得分:1)

有一种替代解决方案可以强制每个父级使用只有检查,外键和唯一性约束而没有物化视图的0或n个子项。为此,必须为子项编号并添加包含下一个兄弟的编号的字段。这里有一个n = 5的例子在PostgreSQL中有效,对于其他DBS,它必须适应类型 serial

create table Tree(
  id serial,
  parent_id integer not null references Tree(id),
  child_nr integer check(child_nr between 1 and 5),
  next_sibling_nr integer,
  unique (parent_id, child_nr),
  check(next_sibling_nr in (child_nr+1, child_nr-4)),
  check(((parent_id is null) and (child_nr is null) and
    (next_sibling_nr is null)) or ((parent_id is not null)
    and (child_nr is not null) and (next_sibling_nr is not null))),
  foreign key (parent_id, next_sibling_nr) references Tree(parent_id, child_nr),
  primary key (id)
);

最后一个(长)检查约束确保字段parent_id,child_nr和next_sibling_nr都为null或全部不为null。唯一性约束和child_nr字段的检查会照顾父母最多有5个孩子。另一个检查约束和对上的外键约束(parent_id,next_sibling_nr)确保不少于5个子项。

使用命令

插入具有自动生成的id 1的根
insert into Tree (parent_id)
  values (null);

可以将孩子总是以5个包装添加:

insert into Tree (parent_id, child_nr, next_sibling_nr)
  values (1, 1, 2),
         (1, 2, 3),
         (1, 3, 4),
         (1, 4, 5),
         (1, 5, 1);

此解决方案源于我在几周内ago询问的类似问题的答案。

答案 4 :(得分:0)

这可能不是你想要的,但我确实有一种方法可以做类似的事情。

一对多的通常安排是这样的:

Primary Table:
primary_id (PK)
primary_stuff

Secondary Table:
secondary_id (PK)
primary_id (FK)
secondary_stuff

另一种方法是,建立严格的一对一模型:

Primary Table:
primary_id (PK)
secondary_id (FK, non-null)
primary_stuff

Secondary Table:
secondary_id (PK)
secondary_stuff

可能有点奇怪,但它确实有效。如果有一对多的一对一,例如为客户提供多个地址,但只有一个帐单地址,则此变体可能很有用。

答案 5 :(得分:0)

鸡和蛋问题的另一种解决方案是使用INSERT ALL。因为它是单个语句,所以它不需要可延迟的外键约束。它还提供了一种插入精确数量的从属行的机制。其他约束阻止插入其他行。但我们需要一个带有外键的子表来防止意外删除感兴趣的行。

在此示例中,n = 3。

SQL> create table parent
  2   ( pk_col number not null
  3     , col1 varchar2(20)
  4     , constraint par_pk primary key (pk_col)
  5    )
  6  /

Table created.

SQL> 
SQL> create table child
  2   ( pk_col number not null
  3     , seqno number(1,0) not null
  4     , col2 date
  5     , constraint ch_pk primary key
  6          (pk_col, seqno)
  7     , constraint ch_par_fk foreign key
  8          (pk_col) references parent (pk_col)
  9     , constraint ch_ck check (seqno between 1 and 3)
 10    )
 11  /

Table created.

SQL> 
SQL> create table child_lock
  2   ( pk_col_1 number not null
  3     , seqno_1 number(1,0) not null
  4     , pk_col_2 number not null
  5     , seqno_2 number(1,0) not null
  6     , pk_col_3 number not null
  7     , seqno_3 number(1,0) not null
  8     , constraint chlk_pk primary key
  9          (pk_col_1, seqno_1, pk_col_2, seqno_2, pk_col_3, seqno_3)
 10     , constraint chlk_par_1_fk foreign key
 11          (pk_col_1, seqno_1) references child (pk_col, seqno)
 12     , constraint chlk_par_2_fk foreign key
 13          (pk_col_2, seqno_2) references child (pk_col, seqno)
 14     , constraint chlk_par_3_fk foreign key
 15          (pk_col_3, seqno_3) references child (pk_col, seqno)
 16    )
 17  /

Table created.

SQL> 
SQL> insert all
  2      into parent values (pk_val, val_1)
  3      into child values (pk_val, 1, sysdate)
  4      into child values (pk_val, 2, sysdate+1)
  5      into child values (pk_val, 3, sysdate+2)
  6      into child_lock values (pk_val, 1, pk_val, 2, pk_val, 3)
  7  select 999 as pk_val
  8         , 'APPLE PIE' as val_1
  9  from dual
 10  /

5 rows created.

SQL> 
SQL> insert into child values (999, 4, sysdate+4)
  2  /
insert into child values (999, 4, sysdate+4)
*
ERROR at line 1:
ORA-02290: check constraint (APC.CH_CK) violated 


SQL> insert into child values (999, 3, sysdate+4)
  2  /
insert into child values (999, 3, sysdate+4)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> insert into child values (999, 2.5, sysdate+4)
  2  /
insert into child values (999, 2.5, sysdate+4)
*
ERROR at line 1:
ORA-00001: unique constraint (APC.CH_PK) violated 


SQL> delete from child
  2  /
delete from child
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.CHLK_PAR_1_FK) violated - child record found 


SQL>

我接受这个解决方案是一个小小的设计,也是不灵活的,但原来的要求也是如此。它也远非防弹 - 从CHILD_LOCK中删除行,您可以删除一个或多个CHILD记录。

答案 6 :(得分:0)

您可以使用1:M关系正常创建表,然后在子表上有一个计数列,其中包含一个检查约束,用于确定父项可以存在的子项数,以及对父项的唯一约束ID +计数列。 e.g:

CREATE TABLE Parent (PID NUMBER PRIMARY KEY);

CREATE TABLE Child (
    PID NUMBER NOT NULL,
    Count NUMBER(1) NOT NULL,
    CONSTRAINT count_check CHECK (Count BETWEEN 1 AND 5),
    CONSTRAINT parent_child_fk FOREIGN KEY (PID) REFERENCES Parent (PID),
    CONSTRAINT count_unique UNIQUE (PID, Count));

唯一不能保证的是,每个父母至少有五个孩子;为了解决这个问题,你可以创建一个具有WW建议约束的物化视图,或者在应用程序中构建额外的东西(例如“错误”报告)。