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

aw *_*rud 17 sql oracle referential-integrity hierarchical-data

我有一个有3列的表:

ID, PARENT_ID, NAME
Run Code Online (Sandbox Code Playgroud)

PARENT_IDID在同一个表中具有外键关系.该表正在为层次结构建模.

有时ID记录会改变.我希望能够更新记录ID,然后更新依赖记录' PARENT_ID以指向新记录ID.

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

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

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

Chi*_*Chi 19

你想要的是一个' 可延迟的约束 '.

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

  • +1:为了回答这个问题,但是应该构建数据模型,这样就不需要这些黑客. (8认同)
  • @OMG小马:然后DBA就是白痴.只要我完整的参与完整性完成我的交易,我应该如何到达那里并不重要.而你的代理键是一个可怕的黑客,以更多的自我而不是感觉,恕我直言,或仅仅不够健壮管理它的软件(MySql?)来绕过DBA. (3认同)
  • 关于延迟约束的一个警告:如果在尝试提交时约束无效,则事务将自动回滚.这是数据库从用户(或应用程序)手中获取事务控制的少数几个地方之一. (2认同)
  • @Chris Kaminski你似乎不明白的是,DBA的工作是保护数据不会让程序员更方便.那不会让他们成为白痴. (2认同)

Sha*_*nce 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
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)


APC*_*APC 7

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

我的测试数据:

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>
Run Code Online (Sandbox Code Playgroud)

做错的方法:

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>
Run Code Online (Sandbox Code Playgroud)

但是,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>
Run Code Online (Sandbox Code Playgroud)

您所处的情况类似:您希望更新父记录的主键但由于子记录的存在而无法更新:并且您无法更新子记录,因为没有父键.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>
Run Code Online (Sandbox Code Playgroud)

再次解决方案是在单个语句中执行:

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>
Run Code Online (Sandbox Code Playgroud)

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

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