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では、エラーメッセージに (スキーマ名.制約名) が出ます。この制約名が、削除を止めている外部キー制約です。
ORA-02292: integrity constraint (APP.FK_ORDERS_CUSTOMERS) violated - child record found
上の例なら、APP スキーマの FK_ORDERS_CUSTOMERS が原因です。この名前から、どの子テーブルがどの親テーブルを参照しているかを確認します。
制約名から子テーブルと親テーブルを特定するSQL
まず ALL_CONSTRAINTS で、エラーに出た外部キー制約を検索します。constraint_type = 'R' は参照整合性、つまり外部キー制約を表します。
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 で列順を合わせるのがポイントです。
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) の例なら次のように確認します。
SELECT o.* FROM orders o WHERE o.customer_id = :customer_id;
エラーメッセージの制約名だけでは、実際にどの親キーが引っかかっているかまではわかりません。削除しようとしている親行の主キー値を使って、子テーブル側の件数を確認します。画面削除やバッチ削除でORA-02292になった場合は、この件数確認が最短の切り分けになります。
SELECT COUNT(*) AS child_count FROM orders WHERE customer_id = :customer_id; -- child_count が 1 以上なら、この親行はそのまま削除できない
削除対象が複数行なら、子テーブルと親テーブルを結合して影響範囲を見ます。いきなり削除せず、まず SELECT で件数と内容を確認します。
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;
基本の直し方は子を先に処理してから親を削除する
もっとも基本的な解決方法は、子レコードを先に削除し、その後に親レコードを削除することです。削除処理はトランザクション内で行い、事前に件数を確認してから実行します。
-- 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 を付ける設計があります。たとえば、注文ヘッダーを削除したら注文明細も必ず不要になる、というような親子関係です。
ALTER TABLE order_items ADD CONSTRAINT fk_order_items_orders FOREIGN KEY (order_id) REFERENCES orders (order_id) ON DELETE CASCADE;
既存の外部キーに後から ON DELETE CASCADE を足す場合、通常は制約を削除して作り直します。本番環境では影響が大きいため、削除される子テーブル、件数、トリガー、監査要件を確認してから行います。
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 の場合は使えません。
ALTER TABLE tickets ADD CONSTRAINT fk_tickets_assignee FOREIGN KEY (assignee_user_id) REFERENCES users (user_id) ON DELETE SET NULL;
制約を無効化して削除するのは慎重にする
Oracle公式の対処には、依存関係を先に削除するか、制約を無効化するという方向性があります。ただし、制約を無効化して親だけ削除すると、子テーブルに参照先のない不整合データが残る可能性があります。通常の業務処理では、安易に制約を無効化しないほうが安全です。
-- 原則として通常運用の削除処理では避ける 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_flg や status で論理削除にする設計も多いです。子レコードが残っていても親行自体は残るため、外部キー整合性を保ったまま画面上は非表示にできます。
UPDATE customers
SET deleted_flg = '1',
deleted_at = SYSTIMESTAMP
WHERE customer_id = :customer_id;
論理削除は便利ですが、すべての検索SQLで削除済みを除外する必要があります。また、一意制約や集計、外部連携で削除済みデータをどう扱うかも決めておきます。
削除前に影響範囲を一覧化する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段だけとは限りません。たとえば CUSTOMERS → ORDERS → ORDER_ITEMS のように、削除したい親の下に子、さらに孫テーブルがある場合があります。この場合は、孫、子、親の順に処理しないと途中でORA-02292になります。
-- 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 ACTION、CASCADE、SET NULL |
| 8 | トランザクション内で実行して検証する | COMMIT 前に件数を確認する |
よくある質問
ORA-02292はどのテーブルが原因かわかりますか?
エラーメッセージに出ている制約名を ALL_CONSTRAINTS で検索すると、子テーブルを特定できます。列まで確認するには ALL_CONS_COLUMNS と結合します。
子テーブルを消せば必ず解決しますか?
技術的には解決することが多いですが、業務上消してよいデータとは限りません。注文履歴、請求、監査ログなどは論理削除や参照先変更を検討します。
ON DELETE CASCADEを付ければ楽になりますか?
親削除と同時に子も消してよい関係なら便利です。ただし、意図しない大量削除につながるため、履歴や監査が必要なテーブルには慎重に使います。
制約を無効化して削除してもよいですか?
通常の業務削除ではおすすめしません。参照先のない子レコードが残り、再度制約を有効化できない状態になることがあります。データ移行などで一時的に使う場合も、検証と復旧手順を用意します。
画面から削除するとORA-02292になる場合はどうしますか?
画面上の対象データだけでなく、裏側の子テーブルに関連データが残っている可能性があります。エラーの制約名から子テーブルを特定し、業務的に削除可能か、論理削除にすべきかを確認します。
子テーブルを削除したのに、またORA-02292になるのはなぜですか?
別の子テーブル、または孫テーブルが残っている可能性があります。親テーブルを参照する外部キーが複数ある場合、1つの制約を解消しても別の制約で止まります。削除前に外部キー一覧で参照元テーブルを洗い出してください。
まとめ
ORA-02292は、親レコードを削除しようとしたときに、その親を参照する子レコードが残っているため発生する外部キー制約エラーです。まずエラーメッセージの制約名を確認し、ALL_CONSTRAINTS と ALL_CONS_COLUMNS で子テーブル・子列・親テーブルを特定します。
基本対応は、子レコードを先に削除または更新してから親レコードを削除することです。ただし、履歴や監査が必要なデータでは、物理削除ではなく論理削除や参照先変更を検討します。ON DELETE CASCADE は便利ですが、削除範囲が広がるため、本当に子も消してよい関係にだけ使いましょう。
