17

3列のテーブルがあります:

ID, PARENT_ID, NAME

PARENT_IDIDは、同じテーブル内でと外部キー関係を持っています。このテーブルは階層をモデル化しています。

場合によってIDは、レコードの名前が変更されることがあります。IDレコードのを更新してから、依存レコードを更新PARENT_IDして新しい を指すようにしたいと考えていますID

問題は、レコードを更新しようとするとID、整合性が失われ、すぐに失敗することです。

new を使用して新しいレコードを挿入しID、子を更新してから古いレコードを削除できることはわかっていますが、そうすると台無しになるトリガーがたくさん用意されています。

外部キーを一時的に無効にすることなく、子を更新することを約束して親を一時的に更新する方法はありますか (明らかにコミット時に失敗します)。

4

6 に答える 6

20

必要なのは「遅延制約」です。

デフォルトの動作を駆動するために、「INITIALLY IMMEDIATE」と「INITIALLY DEFERRED」の 2 種類の遅延可能な制約を選択できます。データベースがデフォルトですべてのステートメントの後に制約をチェックするか、または最後に制約のみをチェックするようにデフォルトにするか。トランザクションの。

于 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

このようなシナリオでの一般的なアドバイスは、遅延可能な制約を採用することです。ただし、これらの状況はほとんどの場合、アプリケーション ロジックまたはデータ モデルの障害であると思います。たとえば、子レコードと親レコードを同じトランザクションに挿入すると、2 つのステートメントとして実行すると問題が発生する可能性があります。

私のテストデータ:

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 ステートメントの構文は少し不格好ですが、通常は不格好です。ポイントは、主キー列を頻繁に更新する必要がないことです。実際、不変性は「主キー性」の特徴の 1 つであるため、それらを更新する必要はまったくありません。そうする必要があるのは、データモデルの失敗です。このような失敗を回避する 1 つの方法は、合成 (代理) 主キーを使用し、一意の制約を使用して自然 (別名ビジネス) キーの一意性を単純に強制することです。

では、なぜオラクルは遅延可能な制約を提供するのでしょうか? これらは、データの移行や一括データのアップロードを行う際に役立ちます。これにより、テーブルをステージングせずにデータベース内のデータをクレンジングできます。通常のアプリケーション タスクには必要ありません。

于 2010-06-24T11:39:49.193 に答える
3

代理キーを使用するための推奨事項は優れています、IMO。

より一般的には、このテーブルの問題は、主キーがないことです。主キーは次の3つでなければならないことを思い出してください。

  1. 個性的
  2. null以外
  3. 変わらない

私が精通しているデータベースは(1)と(2)を強制しますが、(3)を強制するとは思わないので、残念です。そして、それがお尻を蹴っているのです。「主キー」を変更する場合は、そのキーフィールドへのすべての参照を追跡し、整合性を壊したくない場合は同等の変更を行う必要があります。他の人が言っているように、解決策は真の主キーを持つことです-一意で、nullではなく、変更されないものです。

これらすべての小さなルールには理由があります。これは、主キールールの「変わらない」部分を理解する絶好の機会です。

共有してお楽しみください。

于 2010-06-24T15:18:04.633 に答える
1

遅延可能な制約を使用する必要があります(Chiの回答を参照)。
それ以外の場合、外部キー制約に失敗する値を追加するには、外部キー制約を無効にするか、削除して再作成する必要があります。

このような状況では、参照整合性に影響を与えることなく、ユーザーが必要に応じて変更できる代理キーを使用します。このアイデアを拡張するために、現在のセットアップは次のとおりです。

  • ID (pk)
  • PARENT_ID (外部キー、ID 列を参照 -- 自己参照にする)

..そしてビジネス ルールは、ID は変更できるということです。これは、設計の観点からは根本的に悪いことです。主キーは不変で一意であり、null にすることはできません。したがって、データ モデルを構築するときの状況に対する解決策は、次を使用することです。

  • 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 に答える