【Oracle】ORA-02292の原因と解決方法|integrity constraint violated – child record found・子レコードが存在する時の削除エラー

【Oracle】ORA-02292の原因と解決方法|integrity constraint violated – child record found・子レコードが存在する時の削除エラー Oracle

ORA-02292: integrity constraint violated - child record found は、親テーブルの行を削除しようとしたときに、その行を参照している子レコードが残っているため発生するOracleの外部キー制約エラーです。日本語では「整合性制約に違反しました。子レコードが見つかりました」のように扱えます。

典型例は、CUSTOMERS の顧客を削除しようとしたものの、ORDERS にその顧客の注文が残っているケースです。親を先に消すと参照先がなくなるため、Oracleが削除を止めます。

先に結論
ORA-02292は、エラーメッセージに出ている制約名から子テーブルと子列を特定し、子レコードを削除・更新してから親レコードを削除します。設計として子も一緒に消してよい関係なら ON DELETE CASCADE、子の親参照だけ外してよいなら ON DELETE SET NULL を検討します。制約を無効化して削除するのは、整合性を壊しやすいため原則として最後の手段です。
スポンサーリンク

ORA-02292とは

Oracle公式のエラー説明では、ORA-02292は「外部キー依存を持つ親キー値を削除しようとした」状態です。つまり、削除対象の行そのものが壊れているのではなく、その行を参照している別テーブルの行が存在します。

このエラーは DELETE で発生することが多く、INSERT時に親が存在しない ORA-02291 と対になるエラーです。ORA-02291は「子を入れたいが親がない」、ORA-02292は「親を消したいが子がある」と覚えると切り分けやすいです。

エラー 発生しやすい操作 意味
ORA-02291 INSERT / UPDATE 子レコードが参照する親レコードが存在しない
ORA-02292 DELETE 削除したい親レコードを参照する子レコードが存在する

親が見つからない側のエラーは ORA-02291の原因と解決方法 にまとめています。外部キー制約そのものの基本は Oracleの制約の使い方 も参考になります。

まずエラーメッセージの制約名を見る

ORA-02292では、エラーメッセージに (スキーマ名.制約名) が出ます。この制約名が、削除を止めている外部キー制約です。

ora02292-message.txt
ORA-02292: integrity constraint (APP.FK_ORDERS_CUSTOMERS) violated - child record found

上の例なら、APP スキーマの FK_ORDERS_CUSTOMERS が原因です。この名前から、どの子テーブルがどの親テーブルを参照しているかを確認します。

制約名から子テーブルと親テーブルを特定するSQL

まず ALL_CONSTRAINTS で、エラーに出た外部キー制約を検索します。constraint_type = 'R' は参照整合性、つまり外部キー制約を表します。

find-fk-constraint.sql
SELECT owner,
       constraint_name,
       table_name AS child_table,
       r_owner,
       r_constraint_name,
       delete_rule,
       status
FROM all_constraints
WHERE owner = 'APP'
  AND constraint_name = 'FK_ORDERS_CUSTOMERS'
  AND constraint_type = 'R';

次に、子テーブルの列と親テーブルの列を対応づけて確認します。複合キーの場合もあるため、position で列順を合わせるのがポイントです。

find-fk-columns.sql
SELECT c.owner,
       c.constraint_name,
       c.table_name AS child_table,
       cc.column_name AS child_column,
       p.table_name AS parent_table,
       pc.column_name AS parent_column,
       c.delete_rule
FROM all_constraints c
JOIN all_cons_columns cc
  ON cc.owner = c.owner
 AND cc.constraint_name = c.constraint_name
JOIN all_constraints p
  ON p.owner = c.r_owner
 AND p.constraint_name = c.r_constraint_name
JOIN all_cons_columns pc
  ON pc.owner = p.owner
 AND pc.constraint_name = p.constraint_name
 AND pc.position = cc.position
WHERE c.owner = 'APP'
  AND c.constraint_name = 'FK_ORDERS_CUSTOMERS'
ORDER BY cc.position;

子レコードが残っているか確認する

原因の子テーブルと列がわかったら、削除したい親キーを参照している行が本当に残っているか確認します。親が CUSTOMERS(customer_id)、子が ORDERS(customer_id) の例なら次のように確認します。

check-child-records.sql
SELECT o.*
FROM orders o
WHERE o.customer_id = :customer_id;

エラーメッセージの制約名だけでは、実際にどの親キーが引っかかっているかまではわかりません。削除しようとしている親行の主キー値を使って、子テーブル側の件数を確認します。画面削除やバッチ削除でORA-02292になった場合は、この件数確認が最短の切り分けになります。

count-child-records-before-delete.sql
SELECT COUNT(*) AS child_count
FROM orders
WHERE customer_id = :customer_id;

-- child_count が 1 以上なら、この親行はそのまま削除できない

削除対象が複数行なら、子テーブルと親テーブルを結合して影響範囲を見ます。いきなり削除せず、まず SELECT で件数と内容を確認します。

check-delete-impact.sql
SELECT c.customer_id,
       COUNT(o.order_id) AS child_count
FROM customers c
JOIN orders o
  ON o.customer_id = c.customer_id
WHERE c.status = 'INACTIVE'
GROUP BY c.customer_id
ORDER BY child_count DESC;

基本の直し方は子を先に処理してから親を削除する

もっとも基本的な解決方法は、子レコードを先に削除し、その後に親レコードを削除することです。削除処理はトランザクション内で行い、事前に件数を確認してから実行します。

delete-child-then-parent.sql
-- 1. 子レコードを先に削除
DELETE FROM orders
WHERE customer_id = :customer_id;

-- 2. 親レコードを削除
DELETE FROM customers
WHERE customer_id = :customer_id;

-- 3. 問題なければコミット
COMMIT;

DELETE文の基本や安全な削除手順は OracleのINSERT・UPDATE・DELETE、SQL共通の削除パターンは SQLのDELETE文の使い方 も参考になります。

子を削除してよいか、親だけ消したいのかを分ける

ORA-02292の対応で大事なのは、「子を削除してよい関係か」を決めることです。注文履歴、監査ログ、請求明細などは、親を消したいからといって子も削除してよいとは限りません。

方針 向いているケース 注意点
子を先に削除 親が消えるなら子も不要な明細 削除対象件数を必ず確認する
子の参照を別の親へ変更 担当者変更、所属変更など 履歴として正しい親へ付け替える
子の外部キーをNULLにする 親が任意項目の関係 子列がNULL許可である必要がある
親を論理削除にする 履歴や監査を残す必要がある 検索条件で削除済みを除外する設計が必要
物理削除をやめる 法務・会計・監査上、削除できないデータ アーカイブやステータス管理を検討する

ON DELETE CASCADEを使う場合

親を削除したら子も自動で削除してよい関係なら、外部キーに ON DELETE CASCADE を付ける設計があります。たとえば、注文ヘッダーを削除したら注文明細も必ず不要になる、というような親子関係です。

on-delete-cascade.sql
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_orders
FOREIGN KEY (order_id)
REFERENCES orders (order_id)
ON DELETE CASCADE;

既存の外部キーに後から ON DELETE CASCADE を足す場合、通常は制約を削除して作り直します。本番環境では影響が大きいため、削除される子テーブル、件数、トリガー、監査要件を確認してから行います。

recreate-fk-with-cascade.sql
ALTER TABLE order_items DROP CONSTRAINT fk_order_items_orders;

ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_orders
FOREIGN KEY (order_id)
REFERENCES orders (order_id)
ON DELETE CASCADE;

ON DELETE SET NULLを使う場合

親が消えても子レコード自体は残したい場合は、ON DELETE SET NULL を使えることがあります。親への参照をNULLにして、子レコードだけ残す設計です。ただし、子列が NOT NULL の場合は使えません。

on-delete-set-null.sql
ALTER TABLE tickets
ADD CONSTRAINT fk_tickets_assignee
FOREIGN KEY (assignee_user_id)
REFERENCES users (user_id)
ON DELETE SET NULL;

制約を無効化して削除するのは慎重にする

Oracle公式の対処には、依存関係を先に削除するか、制約を無効化するという方向性があります。ただし、制約を無効化して親だけ削除すると、子テーブルに参照先のない不整合データが残る可能性があります。通常の業務処理では、安易に制約を無効化しないほうが安全です。

disable-constraint-danger.sql
-- 原則として通常運用の削除処理では避ける
ALTER TABLE orders DISABLE CONSTRAINT fk_orders_customers;

DELETE FROM customers
WHERE customer_id = :customer_id;

-- 不整合が残ったままだと、再度ENABLEできないことがある
ALTER TABLE orders ENABLE CONSTRAINT fk_orders_customers;

データ移行や一括補正で一時的に制約を扱う場合でも、事前バックアップ、検証SQL、再有効化後の整合性確認を必ず行います。PL/SQL内で例外処理を組む場合は、PL/SQL例外処理の基本 も確認してください。

論理削除で回避する設計

業務データでは、親を物理削除せず、deleted_flgstatus で論理削除にする設計も多いです。子レコードが残っていても親行自体は残るため、外部キー整合性を保ったまま画面上は非表示にできます。

logical-delete.sql
UPDATE customers
SET deleted_flg = '1',
    deleted_at = SYSTIMESTAMP
WHERE customer_id = :customer_id;

論理削除は便利ですが、すべての検索SQLで削除済みを除外する必要があります。また、一意制約や集計、外部連携で削除済みデータをどう扱うかも決めておきます。

削除前に影響範囲を一覧化するSQL

削除対象の親テーブルを参照している外部キーを一覧化すると、削除前の影響調査に使えます。どの子テーブルがぶら下がっているかを確認してから削除順序を決めます。

list-child-tables-for-parent.sql
SELECT c.owner,
       c.constraint_name,
       c.table_name AS child_table,
       cc.column_name AS child_column,
       c.delete_rule,
       c.status
FROM all_constraints p
JOIN all_constraints c
  ON c.r_owner = p.owner
 AND c.r_constraint_name = p.constraint_name
JOIN all_cons_columns cc
  ON cc.owner = c.owner
 AND cc.constraint_name = c.constraint_name
WHERE p.owner = 'APP'
  AND p.table_name = 'CUSTOMERS'
  AND p.constraint_type IN ('P', 'U')
  AND c.constraint_type = 'R'
ORDER BY c.table_name, cc.position;

子テーブルが複数ある場合、1つの子テーブルを削除しても、別の子テーブルが残っていて再びORA-02292になることがあります。まず外部キー一覧で参照元テーブルを洗い出し、それぞれの子テーブルで件数を確認します。大量削除や退会処理のような業務では、削除順序を作業手順として固定しておくと事故を減らせます。

複数階層の親子関係では孫テーブルにも注意する

親子関係が1段だけとは限りません。たとえば CUSTOMERSORDERSORDER_ITEMS のように、削除したい親の下に子、さらに孫テーブルがある場合があります。この場合は、孫、子、親の順に処理しないと途中でORA-02292になります。

delete-grandchild-child-parent.sql
-- 1. 孫テーブルを先に削除
DELETE FROM order_items
WHERE order_id IN (
    SELECT order_id
    FROM orders
    WHERE customer_id = :customer_id
);

-- 2. 子テーブルを削除
DELETE FROM orders
WHERE customer_id = :customer_id;

-- 3. 親テーブルを削除
DELETE FROM customers
WHERE customer_id = :customer_id;

階層が深い場合は、手作業で順番を考えるより、テーブル設計書や外部キー一覧から削除順序を整理します。ON DELETE CASCADE が一部だけに設定されている環境では、自動削除される範囲と手動削除が必要な範囲が混ざるため、特に注意します。

ORA-02291との違い

ORA-02291とORA-02292は、どちらも外部キー制約のエラーです。ただし、発生する方向が逆です。INSERTやUPDATEで子が親を参照できないならORA-02291、DELETEで親を消せないならORA-02292です。

観点 ORA-02291 ORA-02292
操作 子テーブルへのINSERT/UPDATE 親テーブルのDELETE
原因 参照先の親がない 参照元の子が残っている
確認する表 親テーブル 子テーブル
基本対処 親を先に作る、キーを直す 子を先に処理してから親を削除する

修正チェックリスト

手順 確認すること 見るポイント
1 エラーメッセージの制約名を控える APP.FK_ORDERS_CUSTOMERS のような部分
2 ALL_CONSTRAINTS で子テーブルを特定する constraint_type = 'R'
3 ALL_CONS_COLUMNS で子列と親列を見る 複合キーなら position を合わせる
4 子レコードの件数を確認する 削除対象の親キーを参照している行
5 ほかの子テーブルや孫テーブルも確認する 1つ消しても別制約で止まらないか
6 子を削除・更新・論理削除のどれにするか決める 業務上、履歴を消してよいか
7 必要なら外部キーの削除ルールを見直す NO ACTIONCASCADESET NULL
8 トランザクション内で実行して検証する COMMIT 前に件数を確認する

よくある質問

ORA-02292はどのテーブルが原因かわかりますか?

エラーメッセージに出ている制約名を ALL_CONSTRAINTS で検索すると、子テーブルを特定できます。列まで確認するには ALL_CONS_COLUMNS と結合します。

子テーブルを消せば必ず解決しますか?

技術的には解決することが多いですが、業務上消してよいデータとは限りません。注文履歴、請求、監査ログなどは論理削除や参照先変更を検討します。

ON DELETE CASCADEを付ければ楽になりますか?

親削除と同時に子も消してよい関係なら便利です。ただし、意図しない大量削除につながるため、履歴や監査が必要なテーブルには慎重に使います。

制約を無効化して削除してもよいですか?

通常の業務削除ではおすすめしません。参照先のない子レコードが残り、再度制約を有効化できない状態になることがあります。データ移行などで一時的に使う場合も、検証と復旧手順を用意します。

画面から削除するとORA-02292になる場合はどうしますか?

画面上の対象データだけでなく、裏側の子テーブルに関連データが残っている可能性があります。エラーの制約名から子テーブルを特定し、業務的に削除可能か、論理削除にすべきかを確認します。

子テーブルを削除したのに、またORA-02292になるのはなぜですか?

別の子テーブル、または孫テーブルが残っている可能性があります。親テーブルを参照する外部キーが複数ある場合、1つの制約を解消しても別の制約で止まります。削除前に外部キー一覧で参照元テーブルを洗い出してください。

まとめ

ORA-02292は、親レコードを削除しようとしたときに、その親を参照する子レコードが残っているため発生する外部キー制約エラーです。まずエラーメッセージの制約名を確認し、ALL_CONSTRAINTSALL_CONS_COLUMNS で子テーブル・子列・親テーブルを特定します。

基本対応は、子レコードを先に削除または更新してから親レコードを削除することです。ただし、履歴や監査が必要なデータでは、物理削除ではなく論理削除や参照先変更を検討します。ON DELETE CASCADE は便利ですが、削除範囲が広がるため、本当に子も消してよい関係にだけ使いましょう。

参考

ORA-02292 – Oracle Database Error Help

Oracle SQL Language Reference – Constraint