如何在事务中简单地破坏引用完整性,而不禁用外键约束?

时间:2010-06-23 21:34:49

标签: sql oracle hierarchical-data referential-integrity

我有一个包含3列的表格:

ID, PARENT_ID, NAME

PARENT_ID在同一个表中与ID具有外键关系。该表正在为层次结构建模。

有时记录的ID会发生变化。我希望能够更新记录ID,然后更新相关记录“PARENT_ID以指向新的ID

问题是,当我尝试更新记录的ID时,它会破坏完整性并立即失败。

我意识到我可以使用新的ID插入新记录,然后更新子记录,然后删除旧记录,但我们有很多触发器,如果​​我这样做会搞砸。

有没有办法临时更新父级,承诺更新子级(显然它会在提交时失败)而不会短暂禁用外键?

6 个答案:

答案 0 :(得分:19)

你想要的是'deferred constraint'。

您可以在两种类型的可延迟约束之间进行选择,“初始立即”和“初始延迟”以驱动默认行为 - 数据库是否应默认在每个语句后检查约束,或者是否应默认仅检查约束在交易结束时。

答案 1 :(得分:10)

回答比Chi慢,但觉得包含代码示例会很好,所以答案可以在SO上找到。

正如Chi回答的那样,可延迟的约束使这成为可能。

SQL> drop table t;

Table dropped.

SQL> create table T (ID number
  2      , parent_ID number null
  3      , name varchar2(40) not null
  4      , constraint T_PK primary key (ID)
  5      , constraint T_HIREARCHY_FK foreign key (parent_ID)
  6          references T(ID) deferrable initially immediate);

Table created.

SQL> insert into T values (1, null, 'Big Boss');

1 row created.

SQL> insert into T values (2, 1, 'Worker Bee');

1 row created.

SQL> commit;

Commit complete.

SQL> -- Since initially immediate, the following statement will fail:
SQL> update T
  2  set ID = 1000
  3  where ID = 1;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint (S.T_HIREARCHY_FK) violated - child record found


SQL> set constraints all deferred;

Constraint set.

SQL> update T
  2  set ID = 1000
  3  where ID = 1;

1 row updated.

SQL> update T
  2  set parent_ID = 1000
  3  where parent_ID = 1;

1 row updated.

SQL> commit;

Commit complete.

SQL> select * from T;

        ID  PARENT_ID NAME
---------- ---------- ----------------------------------------
      1000            Big Boss
         2       1000 Worker Bee

SQL> -- set constraints all deferred during that transaction
SQL> -- and the transaction has commited, the next
SQL> -- statement will fail
SQL> update T
  2  set ID = 1
  3  where ID = 1000;
update T
*
ERROR at line 1:
ORA-02292: integrity constraint S.T_HIREARCHY_FK) violated - child record found

我相信,但找不到参考,延迟是在约束创建时定义的,不能在以后修改。默认值是不可延迟的。要更改为可延迟约束,您需要执行一次删除并添加约束。 (正确安排,控制等)

SQL> drop table t;

Table dropped.

SQL> create table T (ID number
  2      , parent_ID number null
  3      , name varchar2(40) not null
  4      , constraint T_PK primary key (ID)
  5      , constraint T_HIREARCHY_FK foreign key (parent_ID)
  6          references T(ID));

Table created.

SQL> alter table T drop constraint T_HIREARCHY_FK;

Table altered.

SQL> alter table T add constraint T_HIREARCHY_FK foreign key (parent_ID)
  2      references T(ID) deferrable initially deferred;

Table altered.

答案 2 :(得分:7)

这种情况的常见建议是使用deferrable constraints。但是,我认为这些情况几乎总是应用程序逻辑或数据模型的失败。例如,如果我们将它作为两个语句执行,那么在同一个事务中插入子记录和父记录可能会有问题:

我的测试数据:

SQL> select * from t23 order by id, parent_id
  2  /

        ID  PARENT_ID NAME
---------- ---------- ------------------------------
       110            parent 1
       111            parent 2
       210        110 child 0
       220        111 child 1
       221        111 child 2
       222        111 child 3

6 rows selected.

SQL>

做错事的方法:

SQL> insert into t23 (id, parent_id, name) values (444, 333, 'new child')
  2  /
insert into t23 (id, parent_id, name) values (444, 333, 'new child')
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found


SQL> insert into t23 (id, parent_id, name) values (333, null, 'new parent')
  2  /

1 row created.

SQL>

但是,Oracle支持多表INSERT synatx,它允许我们在同一语句中插入父记录和子记录,从而避免了对可延迟约束的需要:

SQL> rollback
  2  /

Rollback complete.

SQL> insert all
  2      into t23 (id, parent_id, name)
  3          values (child_id, parent_id, child_name)
  4      into t23 (id, name)
  5          values (parent_id, parent_name)
  6  select  333 as parent_id
  7          , 'new parent' as parent_name
  8          , 444 as child_id
  9          , 'new child' as child_name
 10  from dual
 11  /

2 rows created.

SQL>

您所处的情况类似:您希望更新父记录的主键但由于子记录的存在而无法更新:并且您无法更新子记录,因为没有父键。捉住22:

SQL> update t23
  2      set id = 555
  3  where id = 111
  4  /
update t23
*
ERROR at line 1:
ORA-02292: integrity constraint (APC.T23_T23_FK) violated - child record found


SQL> update t23
  2      set parent_id = 555
  3  where parent_id = 111
  4  /
update t23
*
ERROR at line 1:
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not
found


SQL>

解决方案再次在一个声明中完成:

SQL> update t23
  2      set id = decode(id, 111, 555, id)
  3          , parent_id = decode(parent_id, 111, 555, parent_id)
  4  where id = 111
  5     or parent_id = 111
  6  /

4 rows updated.

SQL> select * from t23 order by id, parent_id
  2  /

        ID  PARENT_ID NAME
---------- ---------- ------------------------------
       110            parent 1
       210        110 child 0
       220        555 child 1
       221        555 child 2
       222        555 child 3
       333            new parent
       444        333 new child
       555            parent 2

8 rows selected.

SQL>

UPDATE语句中的语法有点笨拙,但通常是kludges。关键是我们不应经常更新主键列。实际上,由于不变性是“主要关键”的特征之一,我们根本不应该真正更新它们。需要这样做是数据模型的失败。避免此类失败的一种方法是使用合成(代理)主键,并使用唯一约束强制执行自然(也称为业务)键的唯一性。

那么为什么Oracle会提供可延迟的约束?当我们进行数据迁移或批量数据上传时,它们非常有用。它们允许我们在没有登台表的情况下清理数据库中的数据。我们真的不应该将它们用于常规应用程序任务。

答案 3 :(得分:3)

使用代理键的建议非常好,IMO。

更一般地说,此表的问题在于它缺少主键。回想一下,主键必须是三件事:

  1. 唯一
  2. 非空
  3. 不变的
  4. 数据库我熟悉强制执行(1)和(2),但我不相信他们强制执行(3),这是不幸的。这就是踢你的屁股 - 如果你改变你的“主键”,你必须追逐所有对该关键字段的引用,并且如果你不想破坏完整性,也要进行相同的改动。正如其他人所说的那样,解决方案是拥有一个真正的主键 - 一个唯一的,非空的,并且不会改变。

    所有这些小规则都有原因。这是理解主键规则“不变”部分的绝佳机会。

    分享并享受。

答案 4 :(得分:1)

你需要使用一个可延迟的约束(见Chi的回答) 否则,为了添加将使外键约束失败的值,您必须禁用或删除&重新创建外键约束。

像这样的情况使用代理键,必要时可以由用户更改,而不会影响参照完整性。为了扩展这个想法,目前的设置是:

  • ID(pk)
  • PARENT_ID(外键,引用ID列 - 使其自我引用)

..业务规则是ID可以更改。从设计角度来看,这根本就是坏事 - 主键是不可变的,唯一的,不能为空。因此,在构建数据模型时,解决问题的方法是使用:

  • ID(pk)
  • PARENT_ID(外键,引用ID列 - 使其自我引用)
  • SURROGATE_KEY(唯一约束)

SURROGATE_KEY是支持更改而不影响参照完整性的列 - 父级&儿童关系完好无损。这意味着用户可以在不需要延迟约束,启用/禁用或删除/重新创建外键约束的情况下,将代理键调整到令人高兴的心态,ON UPDATE CASCADE ...

通常,在数据建模中, 从不 会因为这些情况向用户显示主键值。例如,我有一个客户希望他们的工作号码在年初改变,年份在数字的开头(IE:201000001将是2010年创建的第一份工作)。当客户出售公司时会发生什么,新的所有者需要一个不同的会计方案?或者,如果在转换到其他数据库供应商时无法保持编号,该怎么办?

答案 5 :(得分:1)

如果这是除Oracle以外的任何其他数据库,您可以使用ON UPDATE CASCADE声明外键。然后,如果您更改父级的ID,它会以原子方式将更改传播到子级的parent_id。

不幸的是,Oracle实现了级联删除,但不是级联更新。

(此答案仅供参考,因为它实际上并不能解决您的问题。)