【完全ガイド】ORA-02292: integrity constraint (child record found) の原因と解決方法|親削除・CASCADE・階層削除 徹底解説

【完全ガイド】ORA-02292: integrity constraint (child record found) の原因と解決方法|親削除・CASCADE・階層削除 徹底解説

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 を根本から解決できるようになります。


目次

結論:子レコードが存在する

時間がない方向けに、最速の対処を先に示します。

エラーメッセージの読み方

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-02291ORA-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 公式


まとめ

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-02291ORA-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)もあわせてご確認ください。