【完全ガイド】ORA-02292: integrity constraint (child record found) の原因と解決方法|親削除・CASCADE・階層削除 徹底解説
- 作成日 2026.07.29
- その他
Oracle 開発者・DBA が必ず遭遇する外部キー削除エラー:
DELETE FROM customers WHERE customer_id = 999;
-- ORA-02292: integrity constraint (APP.FK_ORDERS_CUSTOMER) violated
-- - child record found
「子レコードが残っているよ」とオラクルが言っている。これは ORA-02291(親キーなし)の姉妹エラー、外部キー制約を守るための正常な動作:
customer_id = 999の顧客に紐づくordersが存在- 親を削除すると子が孤児になる
- Oracle がデータ整合性を守るため拒否
現場では厄介なケースが多い:
- 本番運用: 履歴データの累積で削除できない
- テストデータ: TRUNCATE で困る
- マスタメンテナンス: 廃止顧客が消せない
- 論理削除への移行を迫られる
- 依存階層が深く、どこから削除すべきか不明
DROP TABLEで親テーブルを消せないON DELETE CASCADEを後付けしたい- Rails / Java アプリからの意図しない削除
- バッチ処理での大量削除時
- Data Pump インポートでの重複問題
さらに、多くの日本語記事が「子を先に削除しろ」で終わりますが、実務では:
- CASCADE を後付けする方法
- 多階層依存(親→子→孫→ひ孫)の削除順序
DROP TABLE ... CASCADE CONSTRAINTSの使い方- 論理削除への移行戦略
- DEFERRED 制約での対処
- 孤児化を防ぐ SET NULL
- 例外を握りつぶす条件付き削除パターン
- 本番でのメンテナンスウィンドウでの戦略
など、深い運用知識が必要です。
本記事では、ORA-02292: integrity constraint (child record found) の完全な原因と解決方法を、リファレンスとして実用的に整理します。ORA-02291 との対比、10大発生パターン、診断クエリ、5つの解決策、多階層削除、CASCADE 後付け、論理削除への移行、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で ORA-02292 を根本から解決できるようになります。
- 1. 結論:子レコードが存在する
- 2. まず理解する:エラー発生の仕組み
- 3. 【原因①】単純な親 DELETE(最頻出)
- 4. 【原因②】UPDATE で親キー変更
- 5. 【原因③】TRUNCATE 親テーブル
- 6. 【原因④】DROP TABLE 親テーブル
- 7. 【原因⑤】ON DELETE CASCADE の後付け
- 8. 【原因⑥】ON DELETE SET NULL の活用
- 9. 【原因⑦】多階層依存(親→子→孫)
- 10. 【原因⑧】Rails マイグレーション
- 11. 【原因⑨】論理削除への移行
- 12. 【原因⑩】バッチ処理での大量削除
- 13. 診断ツール完全リファレンス
- 14. 5つの解決策 完全リファレンス
- 15. Rails / Java / Python 対応
- 16. 実践シナリオ
- 17. トラブルシューティング
- 18. よくある質問(FAQ)
- 18.1. Q1. ORA-02291 と ORA-02292 の違い
- 18.2. Q2. CASCADE の危険性
- 18.3. Q3. SET NULL の前提
- 18.4. Q4. DROP TABLE CASCADE CONSTRAINTS の効果
- 18.5. Q5. Rails の dependent オプション
- 18.6. Q6. 例外握りつぶしの是非
- 18.7. Q7. TRUNCATE でも発生?
- 18.8. Q8. 論理削除への移行
- 18.9. Q9. パフォーマンス影響
- 18.10. Q10. DEFERRED での挙動
- 18.11. Q11. 監査ログとの関係
- 18.12. Q12. Autonomous DB での対処
- 19. 参考リンク
- 20. まとめ
結論:子レコードが存在する
時間がない方向けに、最速の対処を先に示します。
エラーメッセージの読み方
ORA-02292: integrity constraint (APP.FK_ORDERS_CUSTOMER) violated
- child record found
↑ ↑
スキーマ.制約名 子レコード検出
キー情報:
APP.FK_ORDERS_CUSTOMER: 違反した制約名child record found: 子レコードが残っている
最速の診断
-- ① 制約から子テーブル特定
SELECT c.constraint_name, c.table_name, r_owner, r_constraint_name
FROM user_constraints c
WHERE c.constraint_name = 'FK_ORDERS_CUSTOMER';
-- ② 依存する子レコード検出
SELECT * FROM orders WHERE customer_id = 999;
-- ③ 依存件数
SELECT COUNT(*) FROM orders WHERE customer_id = 999;
5つの解決策
| # | 手法 | 使う場面 |
|---|---|---|
| ① | 子を先に削除 | 通常の対処 |
| ② | ON DELETE CASCADE | 親削除で子も削除 |
| ③ | ON DELETE SET NULL | 孤児残存 |
| ④ | 制約一時無効化 | メンテナンス |
| ⑤ | DROP TABLE … CASCADE CONSTRAINTS | テーブル削除 |
ORA-02291 vs ORA-02292(対のエラー)
| 項目 | ORA-02291 | ORA-02292 |
|---|---|---|
| タイミング | 子 INSERT/UPDATE | 親 DELETE |
| 意味 | 親キー未検出 | 子レコード存在 |
| 対処方向 | 親を作成 | 子を削除 |
| CASCADE | 影響なし | 対処可能 |
両者は表裏一体の外部キー制約エラー。
詳細は以下で解説します。
まず理解する:エラー発生の仕組み
基本例
-- 親テーブル
CREATE TABLE customers (
customer_id NUMBER PRIMARY KEY,
name VARCHAR2(100)
);
-- 子テーブル
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
customer_id NUMBER,
amount NUMBER(15, 2),
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- データ投入
INSERT INTO customers VALUES (1, 'ACME');
INSERT INTO orders VALUES (100, 1, 5000);
-- ❌ 親削除試行
DELETE FROM customers WHERE customer_id = 1;
-- ORA-02292: 子レコードが存在
なぜ拒否されるか
参照整合性の保証:
- orders (100) は customer 1 を参照
- customer 1 を削除すると orders (100) が「孤児」に
- Oracle は整合性を守るため親削除を拒否
対処の3つの方向性
1. 子を削除してから親を削除
2. CASCADE で自動連鎖削除
3. SET NULL で子の参照を切る
【原因①】単純な親 DELETE(最頻出)
症状
DELETE FROM customers WHERE customer_id = 999;
-- ORA-02292
解決A: 子を先に削除
BEGIN
-- 1. 子から削除
DELETE FROM orders WHERE customer_id = 999;
-- 2. 親を削除
DELETE FROM customers WHERE customer_id = 999;
COMMIT;
END;
/
解決B: サブクエリで一括
-- 子を全て削除
DELETE FROM orders
WHERE customer_id IN (
SELECT customer_id FROM customers WHERE last_active < ADD_MONTHS(SYSDATE, -12)
);
-- 親を削除
DELETE FROM customers
WHERE last_active < ADD_MONTHS(SYSDATE, -12);
COMMIT;
例外を握りつぶすパターン
-- 「子があれば親を残す、子がなければ削除」パターン
BEGIN
DELETE FROM customers WHERE customer_id = 999;
DBMS_OUTPUT.PUT_LINE('削除成功');
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -2292 THEN
DBMS_OUTPUT.PUT_LINE('子あり、削除スキップ');
ELSE
RAISE;
END IF;
END;
/
用途: 依存判定のショートカット。
【原因②】UPDATE で親キー変更
症状
-- 親の PK を変更
UPDATE customers SET customer_id = 1000 WHERE customer_id = 1;
-- ORA-02292(orders が customer_id = 1 を参照)
解決
PK は普通変更しないが、必要なら:
-- 1. 一時的に別の親作成
INSERT INTO customers VALUES (1000, ...);
-- 2. 子の参照を移動
UPDATE orders SET customer_id = 1000 WHERE customer_id = 1;
-- 3. 古い親削除
DELETE FROM customers WHERE customer_id = 1;
COMMIT;
MERGE の詳細は Oracle MERGE 文 使い方の記事も参照してください。
【原因③】TRUNCATE 親テーブル
症状
TRUNCATE TABLE customers;
-- ORA-02266: table with enabled foreign key constraints
-- 実は ORA-02292 と別のエラーだが原理は同じ
解決
関連制約を無効化:
-- FK を無効化
ALTER TABLE orders DISABLE CONSTRAINT fk_orders_customer;
-- TRUNCATE
TRUNCATE TABLE customers;
TRUNCATE TABLE orders;
-- FK 再有効化
ALTER TABLE orders ENABLE CONSTRAINT fk_orders_customer;
または: 子から順に TRUNCATE。
【原因④】DROP TABLE 親テーブル
症状
DROP TABLE customers;
-- ORA-02449: unique/primary keys in table referenced by foreign keys
解決
CASCADE CONSTRAINTS:
DROP TABLE customers CASCADE CONSTRAINTS;
-- 関連する外部キー制約も削除
-- ただし orders テーブルは残る(FK 制約のみ削除)
または: 依存を全て削除。
【原因⑤】ON DELETE CASCADE の後付け
状況
既存の FK 制約を CASCADE に変更したい:
-- 現在: ON DELETE NO ACTION
-- 変更: ON DELETE CASCADE
Oracle は ALTER で変更不可
FK は DROP + ADD:
-- 1. 既存 FK 削除
ALTER TABLE orders DROP CONSTRAINT fk_orders_customer;
-- 2. CASCADE 付きで再作成
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE CASCADE;
動作:
DELETE FROM customers WHERE customer_id = 999;
-- orders も自動削除される(CASCADE)
CASCADE の危険性
⚠️ 意図しない大量削除
⚠️ ログ肥大化
⚠️ 業務データの誤削除
慎重に設定。
【原因⑥】ON DELETE SET NULL の活用
使い方
-- FK 作成時
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE SET NULL;
-- 使用
DELETE FROM customers WHERE customer_id = 999;
-- orders.customer_id が NULL になる(レコードは残る)
前提条件
子の外部キー列が NULLABLE必須:
-- ❌ NOT NULL だと SET NULL 不可
CREATE TABLE orders (
customer_id NUMBER NOT NULL, -- ← ここ
...
);
用途
- 履歴保持: 顧客削除しても注文履歴保持
- 論理削除: 参照を切って残す
NOT NULL 関連は ORA-01400: NOT NULL の記事も参照してください。
【原因⑦】多階層依存(親→子→孫)
症状
customers → orders → order_items → order_item_details
↑削除しようとするが、複数階層に依存あり
診断(全依存階層特定)
-- 特定親テーブルへの全依存
SELECT
c.owner, c.table_name AS child_table,
c.constraint_name AS fk_name
FROM user_constraints c
WHERE c.constraint_type = 'R'
AND c.r_constraint_name IN (
SELECT constraint_name FROM user_constraints
WHERE table_name = 'CUSTOMERS' -- 親テーブル
AND constraint_type IN ('P', 'U')
);
解決:階層順に削除
BEGIN
-- 最下層から順に
DELETE FROM order_item_details
WHERE order_item_id IN (
SELECT oi.order_item_id FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
WHERE o.customer_id = 999
);
DELETE FROM order_items
WHERE order_id IN (SELECT order_id FROM orders WHERE customer_id = 999);
DELETE FROM orders WHERE customer_id = 999;
DELETE FROM customers WHERE customer_id = 999;
COMMIT;
END;
/
CASCADE で階層削除
-- 全ての FK に CASCADE 設定すれば
DELETE FROM customers WHERE customer_id = 999;
-- 自動的に全階層削除
【原因⑧】Rails マイグレーション
症状
# 親テーブル削除で失敗
class DropCustomers < ActiveRecord::Migration[8.0]
def change
drop_table :customers # ORA-02292
end
end
解決
A. 子を先に削除:
class DropCustomersAndOrders < ActiveRecord::Migration[8.0]
def change
drop_table :orders
drop_table :customers
end
end
B. FK を削除してからテーブル削除:
class DropCustomers < ActiveRecord::Migration[8.0]
def change
remove_foreign_key :orders, :customers
drop_table :customers
end
end
C. CASCADE:
execute 'DROP TABLE customers CASCADE CONSTRAINTS'
Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、rails db:migrate 使い方の記事、pending migration エラーの記事も参照してください。
【原因⑨】論理削除への移行
状況
物理削除困難、論理削除に移行:
-- 物理削除: ORA-02292
DELETE FROM customers WHERE customer_id = 999;
-- 論理削除: フラグを立てる
UPDATE customers SET deleted_at = SYSTIMESTAMP WHERE customer_id = 999;
実装パターン
-- ① フラグ列追加
ALTER TABLE customers ADD deleted_at TIMESTAMP;
-- ② 論理削除
UPDATE customers SET deleted_at = SYSTIMESTAMP WHERE customer_id = 999;
-- ③ 表示側でフィルタ
SELECT * FROM customers WHERE deleted_at IS NULL;
-- ④ ビューで隠蔽
CREATE VIEW active_customers AS
SELECT * FROM customers WHERE deleted_at IS NULL;
Rails での実装
class Customer < ApplicationRecord
# paranoia gem or discard gem
scope :active, -> { where(deleted_at: nil) }
def soft_delete
update(deleted_at: Time.current)
end
end
【原因⑩】バッチ処理での大量削除
症状
毎晩の古いデータクリーンアップ:
-- 3年以上の古いデータ
DELETE FROM customers WHERE created_at < ADD_MONTHS(SYSDATE, -36);
-- ORA-02292(依存あり)
解決:段階的削除
DECLARE
CURSOR c IS
SELECT customer_id FROM customers
WHERE created_at < ADD_MONTHS(SYSDATE, -36);
BEGIN
FOR rec IN c LOOP
-- 依存を全て削除
DELETE FROM order_item_details WHERE order_item_id IN
(SELECT oi.order_item_id FROM order_items oi JOIN orders o
ON oi.order_id = o.order_id WHERE o.customer_id = rec.customer_id);
DELETE FROM order_items WHERE order_id IN
(SELECT order_id FROM orders WHERE customer_id = rec.customer_id);
DELETE FROM orders WHERE customer_id = rec.customer_id;
DELETE FROM customers WHERE customer_id = rec.customer_id;
-- バッチコミット
IF MOD(c%ROWCOUNT, 1000) = 0 THEN
COMMIT;
END IF;
END LOOP;
COMMIT;
END;
/
分析関数は Oracle 分析関数(OVER/PARTITION BY)の記事も参照してください。
診断ツール完全リファレンス
制約特定
SELECT constraint_name, table_name, r_owner, r_constraint_name, delete_rule
FROM user_constraints
WHERE constraint_name = 'FK_ORDERS_CUSTOMER';
親テーブルの全子孫
-- 特定テーブルを参照する全 FK
SELECT c.owner, c.table_name AS child_table, c.constraint_name, c.delete_rule
FROM all_constraints c
WHERE c.constraint_type = 'R'
AND c.r_constraint_name IN (
SELECT constraint_name FROM all_constraints
WHERE table_name = 'CUSTOMERS' AND constraint_type IN ('P', 'U')
);
階層依存の再帰クエリ
WITH deps AS (
SELECT c.table_name, c.constraint_name, c.r_constraint_name, 1 AS level
FROM user_constraints c
WHERE c.constraint_type = 'R'
AND c.r_constraint_name IN (
SELECT constraint_name FROM user_constraints
WHERE table_name = 'CUSTOMERS'
)
UNION ALL
SELECT c.table_name, c.constraint_name, c.r_constraint_name, d.level + 1
FROM user_constraints c
JOIN deps d ON c.r_constraint_name IN (
SELECT constraint_name FROM user_constraints
WHERE table_name = d.table_name
)
)
SELECT DISTINCT table_name, level FROM deps ORDER BY level;
子レコード数の確認
-- 動的 SQL で全依存テーブルの件数
BEGIN
FOR rec IN (
SELECT c.table_name, cc.column_name
FROM user_constraints c
JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
WHERE c.constraint_type = 'R'
AND c.r_constraint_name = 'PK_CUSTOMERS'
) LOOP
DECLARE
v_count NUMBER;
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' || rec.table_name ||
' WHERE ' || rec.column_name || ' = 999'
INTO v_count;
DBMS_OUTPUT.PUT_LINE(rec.table_name || ': ' || v_count);
END;
END LOOP;
END;
/
5つの解決策 完全リファレンス
解決策① 子を先に削除
DELETE FROM orders WHERE customer_id = 999;
DELETE FROM customers WHERE customer_id = 999;
COMMIT;
解決策② ON DELETE CASCADE
-- 既存 FK を CASCADE に変更
ALTER TABLE orders DROP CONSTRAINT fk_orders_customer;
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE CASCADE;
-- 使用
DELETE FROM customers WHERE customer_id = 999;
-- orders も自動削除
解決策③ ON DELETE SET NULL
ALTER TABLE orders DROP CONSTRAINT fk_orders_customer;
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE SET NULL;
-- 使用
DELETE FROM customers WHERE customer_id = 999;
-- orders.customer_id が NULL に
解決策④ 制約一時無効化
ALTER TABLE orders DISABLE CONSTRAINT fk_orders_customer;
DELETE FROM customers WHERE customer_id = 999;
-- 孤児注文が残る
ALTER TABLE orders ENABLE VALIDATE CONSTRAINT fk_orders_customer;
-- 有効化時にエラー → 孤児を先に処理
-- または NOVALIDATE
ALTER TABLE orders ENABLE NOVALIDATE CONSTRAINT fk_orders_customer;
解決策⑤ DROP TABLE CASCADE CONSTRAINTS
-- テーブル削除時
DROP TABLE customers CASCADE CONSTRAINTS;
-- 関連 FK 制約も削除、依存テーブル自体は残る
Rails / Java / Python 対応
Rails ActiveRecord
dependent オプション:
class Customer < ApplicationRecord
# 削除時の挙動
has_many :orders, dependent: :destroy # 子を削除
# または
has_many :orders, dependent: :delete_all # 高速削除
# または
has_many :orders, dependent: :nullify # FK を NULL
# または
has_many :orders, dependent: :restrict_with_error # 子あれば拒否
end
# 削除
customer = Customer.find(999)
customer.destroy # ORA-02292 回避
エラーハンドリング:
begin
customer.destroy!
rescue ActiveRecord::InvalidForeignKey => e
Rails.logger.error "子レコードあり: #{e.message}"
end
Java (JDBC)
try {
stmt.executeUpdate("DELETE FROM customers WHERE customer_id = 999");
} catch (SQLIntegrityConstraintViolationException e) {
if (e.getErrorCode() == 2292) {
// 子を削除してリトライ
stmt.executeUpdate("DELETE FROM orders WHERE customer_id = 999");
stmt.executeUpdate("DELETE FROM customers WHERE customer_id = 999");
}
}
Python (oracledb)
try:
cursor.execute("DELETE FROM customers WHERE customer_id = :1", (999,))
except oracledb.IntegrityError as e:
error_obj, = e.args
if error_obj.code == 2292:
# 子を先に削除
cursor.execute("DELETE FROM orders WHERE customer_id = :1", (999,))
cursor.execute("DELETE FROM customers WHERE customer_id = :1", (999,))
connection.commit()
実践シナリオ
シナリオ1:ユーザーアカウント削除(GDPR 対応)
CREATE OR REPLACE PROCEDURE delete_user_completely(p_user_id NUMBER) IS
BEGIN
-- 全ての子テーブルから削除
DELETE FROM user_activities WHERE user_id = p_user_id;
DELETE FROM user_sessions WHERE user_id = p_user_id;
DELETE FROM user_preferences WHERE user_id = p_user_id;
DELETE FROM orders WHERE user_id = p_user_id;
DELETE FROM users WHERE user_id = p_user_id;
COMMIT;
END;
/
シナリオ2:ソフト削除への移行
-- 既存 FK を SET NULL に変更
ALTER TABLE orders DROP CONSTRAINT fk_orders_customer;
ALTER TABLE customers ADD deleted_at TIMESTAMP;
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE SET NULL;
-- 削除
UPDATE customers SET deleted_at = SYSTIMESTAMP WHERE customer_id = 999;
シナリオ3:メンテナンスウィンドウでの一括削除
-- 1. 全 FK を無効化
BEGIN
FOR c IN (SELECT constraint_name, table_name FROM user_constraints
WHERE constraint_type = 'R') LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' || c.table_name ||
' DISABLE CONSTRAINT ' || c.constraint_name;
END LOOP;
END;
/
-- 2. 削除
DELETE FROM customers WHERE created_at < SYSDATE - 365 * 5;
-- orders 等に孤児が発生
-- 3. 孤児を全て削除
DELETE FROM orders WHERE customer_id NOT IN (SELECT customer_id FROM customers);
-- 4. 制約再有効化
BEGIN
FOR c IN (SELECT constraint_name, table_name FROM user_constraints
WHERE constraint_type = 'R') LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' || c.table_name ||
' ENABLE VALIDATE CONSTRAINT ' || c.constraint_name;
END LOOP;
END;
/
シナリオ4:DROP TABLE 一括
-- 依存関係を無視して DROP
DROP TABLE customers CASCADE CONSTRAINTS;
DROP TABLE orders CASCADE CONSTRAINTS;
シナリオ5:条件付き削除(例外握りつぶし)
CREATE OR REPLACE PROCEDURE delete_if_no_children(p_customer_id NUMBER) IS
BEGIN
DELETE FROM customers WHERE customer_id = p_customer_id;
DBMS_OUTPUT.PUT_LINE('削除成功');
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -2292 THEN
DBMS_OUTPUT.PUT_LINE('子あり、削除スキップ');
ELSE
RAISE;
END IF;
END;
/
シナリオ6:Rails での安全な削除
class UsersController < ApplicationController
def destroy
user = User.find(params[:id])
if user.orders.exists?
user.update(deleted_at: Time.current) # ソフト削除
redirect_to users_path, notice: '論理削除しました'
else
user.destroy
redirect_to users_path, notice: '削除しました'
end
end
end
Solid Queue の詳細は Solid Queue 使い方の記事も参照してください。
シナリオ7:Data Pump 前後の対処
# エクスポート時、依存も含める
expdp system/pw tables=customers,orders \
dumpfile=data.dmp
# インポート時
impdp system/pw dumpfile=data.dmp \
TABLE_EXISTS_ACTION=REPLACE
Data Pump の詳細は Oracle Data Pump 使い方の記事を参照してください。
シナリオ8:Docker Oracle での再現
docker exec -it oracle-xe sqlplus scott/tiger <<EOF
CREATE TABLE parents (id NUMBER PRIMARY KEY);
CREATE TABLE children (id NUMBER, parent_id NUMBER,
CONSTRAINT fk FOREIGN KEY (parent_id) REFERENCES parents(id));
INSERT INTO parents VALUES (1);
INSERT INTO children VALUES (100, 1);
DELETE FROM parents WHERE id = 1;
-- ORA-02292
EOF
Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。
シナリオ9:Kamal デプロイ後の DB クリーンアップ
docker exec -it db-container sqlplus / as sysdba <<EOF
-- 古いテスト用テーブル削除
DROP TABLE test_customers CASCADE CONSTRAINTS;
EOF
Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事を参照してください。
シナリオ10:Autonomous DB での対処
-- Autonomous DB でも同じ挙動
-- 自動バックアップから復元も選択肢
トラブルシューティング
制約が特定できない
-- テーブルへの全 FK 参照
SELECT c.owner, c.table_name, c.constraint_name
FROM all_constraints c
WHERE c.constraint_type = 'R'
AND c.r_constraint_name IN (
SELECT constraint_name FROM all_constraints
WHERE table_name = 'CUSTOMERS' AND constraint_type = 'P'
);
削除順序が分からない
-- トポロジカルソート的アプローチ
-- 最も深い階層から順に
CASCADE が意図せず動く
-- 現在の CASCADE 状態確認
SELECT constraint_name, table_name, delete_rule
FROM user_constraints
WHERE constraint_type = 'R' AND delete_rule = 'CASCADE';
DEFERRED での挙動
-- DEFERRABLE 制約は COMMIT 時に検証
-- DELETE 文自体は成功、COMMIT でエラー
DEFERRABLE INITIALLY DEFERRED
TRUNCATE 特有のエラー
ORA-02266: table with enabled foreign key constraints
対処: 全 FK 無効化 or 子から順に TRUNCATE
孤児レコードが残った
-- 制約無効化中に発生
SELECT o.* FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.customer_id IS NULL AND o.customer_id IS NOT NULL;
よくある質問(FAQ)
Q1. ORA-02291 と ORA-02292 の違い
- 02291: 子 INSERT/UPDATE で親なし
- 02292: 親 DELETE で子あり
Q2. CASCADE の危険性
意図しない大量削除の可能性。慎重に設定。
Q3. SET NULL の前提
子の FK 列が NULLABLE 必須。
Q4. DROP TABLE CASCADE CONSTRAINTS の効果
制約のみ削除、依存テーブル自体は残る。
Q5. Rails の dependent オプション
:destroy: コールバック実行:delete_all: 高速削除:nullify: FK を NULL:restrict_with_error: 拒否
Q6. 例外握りつぶしの是非
条件付き削除に有効、他は避ける。
Q7. TRUNCATE でも発生?
ORA-02266 が発生(別コードだが同じ原因)。
Q8. 論理削除への移行
推奨戦略。deleted_at フラグ列を追加。
Q9. パフォーマンス影響
CASCADE は大量削除でトリガー・ログに影響。
Q10. DEFERRED での挙動
COMMIT 時に検証、DELETE 自体は成功。
Q11. 監査ログとの関係
CASCADE 削除も監査対象。AUDIT DELETE で追跡。
Q12. Autonomous DB での対処
同じ。自動バックアップからの復元も選択肢。
参考リンク
Oracle 公式
- Oracle Database Error Messages: ORA-02292
- Oracle Database SQL Language Reference: DROP TABLE
- Oracle Database Concepts: Integrity Constraints
まとめ
ORA-02292: integrity constraint (child record found) の要点を再整理します。
エラーの本質
親テーブルの行を削除しようとしたが、
その行を参照する子レコードが存在する
→ 参照整合性違反
→ Oracle が拒否
エラーメッセージの読み方
ORA-02292: integrity constraint (SCHEMA.CONSTRAINT_NAME) violated
- child record found
↑ ↑
違反した制約 子レコード存在
ORA-02291 vs ORA-02292
| 項目 | ORA-02291 | ORA-02292 |
|---|---|---|
| タイミング | 子 INSERT/UPDATE | 親 DELETE |
| 原因 | 親キーなし | 子レコードあり |
| 対処方向 | 親を作る | 子を消す |
診断3ステップ
-- ① 制約情報
SELECT * FROM user_constraints WHERE constraint_name = ...;
-- ② 子テーブル特定
SELECT * FROM user_constraints
WHERE r_constraint_name IN (...);
-- ③ 依存レコード数
SELECT COUNT(*) FROM child WHERE fk_col = ...;
10大原因
| # | 原因 | 対処 |
|---|---|---|
| ① | DELETE 親 | 子先削除 |
| ② | UPDATE 親キー | 参照移動 |
| ③ | TRUNCATE 親 | FK 無効化 |
| ④ | DROP TABLE 親 | CASCADE CONSTRAINTS |
| ⑤ | CASCADE 後付け | DROP + ADD |
| ⑥ | SET NULL 活用 | NULLABLE 前提 |
| ⑦ | 多階層依存 | 階層順削除 |
| ⑧ | Rails マイグレーション | dependent オプション |
| ⑨ | 論理削除移行 | deleted_at 列 |
| ⑩ | バッチ大量削除 | 段階的削除 |
5つの解決策
-- ① 子先削除
DELETE FROM child WHERE ...;
DELETE FROM parent WHERE ...;
-- ② CASCADE
ON DELETE CASCADE
-- ③ SET NULL
ON DELETE SET NULL
-- ④ 制約無効化
ALTER TABLE t DISABLE CONSTRAINT fk;
-- ⑤ CASCADE CONSTRAINTS
DROP TABLE t CASCADE CONSTRAINTS;
全依存テーブル特定クエリ
SELECT c.owner, c.table_name AS child_table, c.constraint_name
FROM all_constraints c
WHERE c.constraint_type = 'R'
AND c.r_constraint_name IN (
SELECT constraint_name FROM all_constraints
WHERE table_name = 'CUSTOMERS' AND constraint_type = 'P'
);
ON DELETE オプション
-- 何もしない(削除拒否)
FOREIGN KEY ... REFERENCES parent(id)
-- 連鎖削除
FOREIGN KEY ... REFERENCES parent(id) ON DELETE CASCADE
-- NULL 設定
FOREIGN KEY ... REFERENCES parent(id) ON DELETE SET NULL
例外握りつぶしパターン
BEGIN
DELETE FROM parent WHERE ...;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -2292 THEN
-- 子あり、スキップ
NULL;
ELSE RAISE;
END IF;
END;
/
これらの知識は、Oracle でのアプリ開発・DBA 業務・データ移行・GDPR 対応・論理削除への移行・Rails / Java / Python 開発・本番トラブル対応など、あらゆる場面で活用できます。本記事をブックマークしておけば、ORA-02292 に出会っても冷静に的確に対処できるようになります。
本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。
-
前の記事
【完全ガイド】ORA-00904: invalid identifier の原因と解決方法|列名タイプミス・予約語・引用符・エイリアス誤用まで徹底解説 2026.07.28
-
次の記事
【完全ガイド】ORA-02291: integrity constraint (parent key not found) の原因と解決方法|外部キー制約・CASCADE・DEFERRED 徹底解説 2026.07.30
コメントを書く