我有一个包含3列的表格:
ID, PARENT_ID, NAME
PARENT_ID
在同一个表中与ID
具有外键关系。该表正在为层次结构建模。
有时记录的ID
会发生变化。我希望能够更新记录ID
,然后更新相关记录“PARENT_ID
以指向新的ID
。
问题是,当我尝试更新记录的ID
时,它会破坏完整性并立即失败。
我意识到我可以使用新的ID
插入新记录,然后更新子记录,然后删除旧记录,但我们有很多触发器,如果我这样做会搞砸。
有没有办法临时更新父级,承诺更新子级(显然它会在提交时失败)而不会短暂禁用外键?
答案 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)
你需要使用一个可延迟的约束(见Chi的回答) 否则,为了添加将使外键约束失败的值,您必须禁用或删除&重新创建外键约束。
像这样的情况使用代理键,必要时可以由用户更改,而不会影响参照完整性。为了扩展这个想法,目前的设置是:
..业务规则是ID可以更改。从设计角度来看,这根本就是坏事 - 主键是不可变的,唯一的,不能为空。因此,在构建数据模型时,解决问题的方法是使用:
SURROGATE_KEY是支持更改而不影响参照完整性的列 - 父级&儿童关系完好无损。这意味着用户可以在不需要延迟约束,启用/禁用或删除/重新创建外键约束的情况下,将代理键调整到令人高兴的心态,ON UPDATE CASCADE ...
通常,在数据建模中, 从不 会因为这些情况向用户显示主键值。例如,我有一个客户希望他们的工作号码在年初改变,年份在数字的开头(IE:201000001将是2010年创建的第一份工作)。当客户出售公司时会发生什么,新的所有者需要一个不同的会计方案?或者,如果在转换到其他数据库供应商时无法保持编号,该怎么办?
答案 5 :(得分:1)
如果这是除Oracle以外的任何其他数据库,您可以使用ON UPDATE CASCADE
声明外键。然后,如果您更改父级的ID,它会以原子方式将更改传播到子级的parent_id。
不幸的是,Oracle实现了级联删除,但不是级联更新。
(此答案仅供参考,因为它实际上并不能解决您的问题。)