【Oracle】ORA-02291: 親キーがありません の原因と解決方法完全ガイド|外部キー制約・参照先の確認・ON DELETE CASCADE・DEFERRABLE

【Oracle】ORA-02291: 親キーがありません の原因と解決方法完全ガイド|外部キー制約・参照先の確認・ON DELETE CASCADE・DEFERRABLE Oracle

ORA-02291: integrity constraint (スキーマ.制約名) violated – parent key not found は、外部キー(FOREIGN KEY)制約が定義されたテーブルにINSERTまたはUPDATEしようとしたとき、参照先の親テーブルに対応する値が存在しない場合に発生します。データ移行やバッチ処理で頻繁に遭遇するエラーです。本記事では制約の特定方法から、CASCADE設定やDEFERRABLEによるロード時の回避策まで体系的に解説します。

この記事でわかること

  • ORA-02291 の発生メカニズム(外部キー制約の仕組み)
  • エラーメッセージから制約名・親テーブル・参照列を特定する方法
  • 親テーブルに存在しない値(不整合データ)を検出するSQL
  • ON DELETE CASCADE / ON DELETE SET NULL の使い方
  • DEFERRABLE INITIALLY DEFERRED で大量ロード時に一括検証する方法
  • 外部キー制約を一時的に無効化する方法(DISABLE / ENABLE)
  • ORA-02292(子レコードが見つかりました)との違い
スポンサーリンク

ORA-02291 の発生メカニズム

外部キー(FK)制約は「子テーブルの値は、必ず親テーブルに存在しなければならない」という参照整合性を保証します。この制約に違反するINSERTまたはUPDATEを行うとORA-02291が発生します。

ORA-02291 が発生する例
-- 親テーブル
CREATE TABLE departments (
    department_id  NUMBER PRIMARY KEY,
    department_name VARCHAR2(100)
);
INSERT INTO departments VALUES (10, '営業部');
INSERT INTO departments VALUES (20, '開発部');

-- 子テーブル(FK制約あり)
CREATE TABLE employees (
    employee_id    NUMBER PRIMARY KEY,
    name           VARCHAR2(100),
    department_id  NUMBER,
    CONSTRAINT fk_emp_dept FOREIGN KEY (department_id)
        REFERENCES departments (department_id)
);

-- 存在する部門IDで INSERT → 成功
INSERT INTO employees VALUES (1, '佐藤太郎', 10);

-- 存在しない部門IDで INSERT → ORA-02291
INSERT INTO employees VALUES (2, '鈴木花子', 99);
-- → ORA-02291: integrity constraint (HR.FK_EMP_DEPT) violated - parent key not found
-- → departments テーブルに department_id = 99 が存在しないため

制約名から親テーブル・参照列を特定する

エラーメッセージに含まれる制約名(例: HR.FK_EMP_DEPT)から、どのテーブルのどの列が関係しているかを調べます。

外部キー制約の詳細を確認する
-- 制約名から子テーブル・親テーブル・列名を特定
SELECT
    c.constraint_name,
    c.table_name          AS child_table,        -- 子テーブル(FK側)
    cc.column_name        AS child_column,       -- 子テーブルの列
    c.r_constraint_name   AS parent_constraint,  -- 参照先の制約名
    rc.table_name         AS parent_table,       -- 親テーブル(参照先)
    rcc.column_name       AS parent_column       -- 親テーブルの列
FROM user_constraints c
JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
JOIN user_constraints rc ON c.r_constraint_name = rc.constraint_name
JOIN user_cons_columns rcc ON rc.constraint_name = rcc.constraint_name
                          AND cc.position = rcc.position
WHERE c.constraint_name = 'FK_EMP_DEPT'
ORDER BY cc.position;

親テーブルに存在しない値を検出する

データ移行やバッチ処理で「どの行が制約に違反しているか」を事前に確認するSQLです。

NOT EXISTS で不整合データを検出する
-- 親テーブル(departments)に存在しない department_id を持つ子行を検出
SELECT e.employee_id, e.name, e.department_id
FROM staging_employees e
WHERE NOT EXISTS (
    SELECT 1 FROM departments d
    WHERE d.department_id = e.department_id
);

-- LEFT JOIN でも同じ結果が得られる
SELECT e.employee_id, e.name, e.department_id
FROM staging_employees e
LEFT JOIN departments d ON e.department_id = d.department_id
WHERE d.department_id IS NULL
  AND e.department_id IS NOT NULL;  -- NULLはFK違反にはならない
NULLは制約違反にならない
外部キー列がNULLの場合、ORA-02291は発生しません。NULLは「参照先が不明」という意味であり、「存在しない値を参照している」とは異なるためです。NULLを許可したくない場合は、FK列に NOT NULL 制約を別途追加してください。

INSERT時の対処法

親テーブルに先にデータを投入する

最も基本的な対処法です。外部キー制約がある場合は親テーブル → 子テーブルの順でINSERTします。

正しい INSERT 順序
-- ステップ 1: 親テーブル(departments)にデータを投入
INSERT INTO departments VALUES (99, '新規事業部');

-- ステップ 2: 子テーブル(employees)にデータを投入
INSERT INTO employees VALUES (2, '鈴木花子', 99);
-- → 親テーブルに department_id=99 が存在するので成功

親テーブルに存在する値だけ投入する

EXISTS で親テーブルに存在するデータだけ INSERT する
INSERT INTO employees (employee_id, name, department_id)
SELECT employee_id, name, department_id
FROM staging_employees s
WHERE EXISTS (
    SELECT 1 FROM departments d
    WHERE d.department_id = s.department_id
);
-- 親テーブルに対応するdepartment_idが存在する行だけ投入される

ON DELETE CASCADE / ON DELETE SET NULL

外部キー制約のON DELETEオプションは、親テーブルの行が削除されたときの子テーブルの挙動を定義します。なお、ORA-02291はINSERT/UPDATE時のエラーであり、ON DELETEの設定はDELETE時の挙動にのみ影響します。関連するORA-02292(子レコードが見つかりました)を防ぐ設定として知っておく価値があります。

オプション 親行DELETE時の動作 用途
(指定なし / デフォルト) 子行が存在する場合、親行の DELETE を拒否する(ORA-02292) データの整合性を厳格に保ちたい場合
ON DELETE CASCADE 親行と一緒に子行も自動的に削除される 親に従属するデータ(注文明細・ログ等)
ON DELETE SET NULL 子行のFK列がNULLに更新される 親が削除されても子のデータは残したい場合
ON DELETE CASCADE / SET NULL の定義
-- CASCADE: 親行削除時に子行も自動削除
CREATE TABLE order_items (
    item_id    NUMBER PRIMARY KEY,
    order_id   NUMBER,
    product    VARCHAR2(100),
    CONSTRAINT fk_items_orders FOREIGN KEY (order_id)
        REFERENCES orders (order_id)
        ON DELETE CASCADE
);
-- orders の行を DELETE すると order_items の対応行も自動削除される

-- SET NULL: 親行削除時に子行のFK列をNULLにする
CREATE TABLE employees (
    employee_id    NUMBER PRIMARY KEY,
    name           VARCHAR2(100),
    department_id  NUMBER,
    CONSTRAINT fk_emp_dept FOREIGN KEY (department_id)
        REFERENCES departments (department_id)
        ON DELETE SET NULL
);
-- departments の行を DELETE すると employees.department_id が NULL になる

DEFERRABLE 制約で大量ロード時に一括検証する

通常、FK制約はINSERT/UPDATE実行の即座にチェックされます。大量データのロードで投入順序を制御しにくい場合、DEFERRABLE INITIALLY DEFERRED を使うとCOMMIT時にまとめてチェックできます。

DEFERRABLE INITIALLY DEFERRED ── COMMITまで検証を遅延
-- DEFERRABLE な外部キー制約を定義する
ALTER TABLE employees
ADD CONSTRAINT fk_emp_dept FOREIGN KEY (department_id)
    REFERENCES departments (department_id)
    DEFERRABLE INITIALLY DEFERRED;

-- 投入順序を気にせずINSERTできる
INSERT INTO employees VALUES (10, '新入社員', 99);  -- department_id=99 はまだ存在しない
INSERT INTO departments VALUES (99, '新規事業部');    -- ここで親データを投入

COMMIT;  -- ★ この時点で FK 整合性がチェックされる → 99 は存在するので成功
セッション単位で検証タイミングを切り替える
-- 現在のセッションで全ての DEFERRABLE 制約を遅延モードにする
SET CONSTRAINTS ALL DEFERRED;

-- 特定の制約だけ即時チェックに戻す
SET CONSTRAINTS fk_emp_dept IMMEDIATE;

外部キー制約を一時的に無効化する

データ移行等で一時的に制約チェックを外したい場合、DISABLE / ENABLE が使えます。

制約の無効化と再有効化
-- 制約を無効化する(FK チェックが行われなくなる)
ALTER TABLE employees DISABLE CONSTRAINT fk_emp_dept;

-- データ投入(順序を気にせず自由に INSERT できる)
INSERT INTO employees VALUES (5, '田中一郎', 999);
-- → FK チェックが無効なのでエラーにならない(不整合データが入る可能性あり)

-- 制約を再有効化する(既存データの整合性チェックあり)
ALTER TABLE employees ENABLE CONSTRAINT fk_emp_dept;
-- → 既存データに不整合があると ORA-02298: 親キーが見つかりません が発生して有効化に失敗する
不整合データがあっても制約を有効化する(ENABLE NOVALIDATE)
-- 既存データはチェックせず、今後の DML からチェックを再開する
ALTER TABLE employees ENABLE NOVALIDATE CONSTRAINT fk_emp_dept;
-- → 既存の不整合データはそのまま残るが、以降の INSERT/UPDATE はチェックされる
DISABLE / ENABLE NOVALIDATE の注意
制約を無効化している間は不整合データが投入される可能性があります。再有効化時に ENABLE VALIDATE(デフォルト)でエラーが出る場合は、先に不整合データを修正してください。ENABLE NOVALIDATE は既存データをチェックしないため、不整合を見逃すリスクがあります。

関連エラー:ORA-02292(子レコードが見つかりました)

ORA-02291 と対になるエラーです。親テーブルの行を DELETE しようとしたとき、子テーブルに参照している行が存在すると発生します。

エラー 操作 意味
ORA-02291 子テーブルへの INSERT / UPDATE 参照先の親キーが存在しない
ORA-02292 親テーブルからの DELETE / UPDATE 子テーブルに参照している行が存在する
ORA-02292 の対処法
-- 子テーブルの行を先に削除してから親テーブルの行を削除する
DELETE FROM employees WHERE department_id = 10;
DELETE FROM departments WHERE department_id = 10;

-- または ON DELETE CASCADE を設定しておけば自動削除される

テーブルの外部キー制約を一覧で確認する

テーブルに定義された外部キー制約を全て確認する
SELECT
    c.constraint_name,
    cc.column_name       AS child_column,
    rc.table_name        AS parent_table,
    rcc.column_name      AS parent_column,
    c.status,            -- ENABLED / DISABLED
    c.validated,         -- VALIDATED / NOT VALIDATED
    c.delete_rule        -- CASCADE / SET NULL / NO ACTION
FROM user_constraints c
JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
JOIN user_constraints rc ON c.r_constraint_name = rc.constraint_name
JOIN user_cons_columns rcc ON rc.constraint_name = rcc.constraint_name
                          AND cc.position = rcc.position
WHERE c.constraint_type = 'R'   -- R = Referential (外部キー)
  AND c.table_name = 'EMPLOYEES'
ORDER BY c.constraint_name, cc.position;

まとめ

ORA-02291 は外部キー制約によって参照整合性が保証されている証拠です。エラーを無闇に回避するのではなく、データの整合性を保つ方向で対処するのが原則です。

  • エラーメッセージの制約名から USER_CONSTRAINTS で親テーブル・列を特定する
  • NOT EXISTS で親テーブルに存在しない不整合データを事前に検出する
  • INSERT は親テーブル → 子テーブルの順序で行う
  • 削除時にORA-02292が出る場合は ON DELETE CASCADE / ON DELETE SET NULL を検討する
  • 大量ロード時は DEFERRABLE INITIALLY DEFERRED でCOMMIT時に一括検証できる
  • データ移行時の一時的な回避には DISABLE CONSTRAINT → ロード → ENABLE CONSTRAINT のパターンが使える
  • FK列のNULLは制約違反にならない。NULLを禁止するには NOT NULL を別途追加する