17

我有一个包含 3 列的表:

ID, PARENT_ID, NAME

PARENT_ID与同一张表有外键关系ID。该表正在对层次结构进行建模。

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

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

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

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

4

6 回答 6

20

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

您可以在两种类型的可延迟约束之间进行选择,“INITIALLY IMMEDIATE”和“INITIALLY DEFERRED”来驱动默认行为 - 数据库是否应该默认在每个语句之后检查约束,或者它是否应该默认只在最后检查约束的交易。

于 2010-06-23T21:46:40.650 回答
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.
于 2010-06-23T22:28:03.100 回答
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>

错误的做事方式:

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 语法,它允许我们在同一语句中插入父记录和子记录,从而消除了对可延迟约束的需要:

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 语句中的语法有点笨拙,但通常是杂乱无章的。关键是我们不应该经常更新主键列。事实上,由于不变性是“主键性”的特征之一,我们根本不需要更新它们。需要这样做是数据模型的失败。避免此类故障的一种方法是使用合成(代理)主键,并通过唯一约束简单地强制自然(也称为业务)键的唯一性。

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

于 2010-06-24T11:39:49.193 回答
3

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

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

  1. 独特
  2. 非空
  3. 不变

我熟悉的数据库强制执行(1)和(2),但我不相信它们强制执行(3),这是不幸的。这就是让你大吃一惊的原因——如果你改变你的“主键”,如果你不想破坏完整性,你必须追查对该键字段的所有引用并进行等效的更改。正如其他人所说,解决方案是拥有一个真正的主键 - 一个唯一的、非空的且不会更改的主键。

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

分享和享受。

于 2010-06-24T15:18:04.633 回答
1

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

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

  • 身份证号(PK)
  • PARENT_ID(外键,引用 ID 列——使其自引用)

..并且业务规则是ID可以更改。从设计的角度来看,这从根本上是不好的——主键是不可变的、唯一的,并且不能为空。因此,当您构建数据模型时,解决方案是使用:

  • 身份证号(PK)
  • PARENT_ID(外键,引用 ID 列——使其自引用)
  • SURROGATE_KEY(唯一约束)

SURROGATE_KEY 是支持更改而不影响参照完整性的列 - 父子关系是完整的。这意味着用户可以根据自己的喜好调整代理键,而无需延迟约束、启用/禁用或删除/重新创建外键约束、ON UPDATE CASCADE ...

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

于 2010-06-23T21:39:44.393 回答
1

如果这是除 Oracle 之外的任何其他数据库,您可以使用ON UPDATE CASCADE. 然后,如果您更改父母的 id,它将以原子方式将更改传播到孩子的 parent_id。

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

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

于 2010-06-23T23:02:47.520 回答