【Oracle】ORA-00001: 一意制約に反しています の原因と解決方法完全ガイド|重複データの特定・MERGE・UPSERT・LOG ERRORSまで解説

【Oracle】ORA-00001: 一意制約に反しています の原因と解決方法完全ガイド|重複データの特定・MERGE・UPSERT・LOG ERRORSまで解説 Oracle

ORA-00001: unique constraint (スキーマ名.制約名) violated は、INSERT文やUPDATE文で一意制約(UNIQUE / PRIMARY KEY)に違反するデータを挿入・更新しようとしたときに発生する、Oracleで最もよく遭遇するエラーの1つです。本記事では、エラーメッセージから違反制約と重複データを特定する方法、エラーを回避しながらデータを安全に投入するパターンまで体系的に解説します。

この記事でわかること

  • ORA-00001 の発生原因(PRIMARY KEY / UNIQUE 制約の仕組み)
  • エラーメッセージから制約名・対象列・重複データを特定する方法
  • MERGE 文で INSERT or UPDATE(UPSERT)を実現する方法
  • IGNORE_ROW_ON_DUPKEY_INDEX ヒントで重複行をスキップする方法
  • LOG ERRORS INTO でエラー行をエラーテーブルに退避する方法
  • PL/SQL での DUP_VAL_ON_INDEX 例外処理
  • シーケンスの欠番・重複による ORA-00001 の対策
スポンサーリンク

ORA-00001 の発生原因

このエラーは、テーブルに定義されたPRIMARY KEY制約またはUNIQUE制約に違反するデータを挿入(INSERT)または更新(UPDATE)しようとしたときに発生します。

ORA-00001 が発生する例
CREATE TABLE employees (
    employee_id  NUMBER PRIMARY KEY,
    email        VARCHAR2(100) UNIQUE,
    name         VARCHAR2(100)
);

INSERT INTO employees VALUES (1, 'sato@example.com', '佐藤太郎');
-- → 成功

INSERT INTO employees VALUES (2, 'sato@example.com', '佐藤次郎');
-- → ORA-00001: unique constraint (HR.SYS_C007856) violated
-- → email 列の UNIQUE 制約に違反(同じメールアドレスが既に存在する)

INSERT INTO employees VALUES (1, 'suzuki@example.com', '鈴木花子');
-- → ORA-00001: unique constraint (HR.SYS_C007855) violated
-- → employee_id 列の PRIMARY KEY 制約に違反(ID=1 が既に存在する)

違反した制約と重複データを特定する方法

エラーメッセージに表示される HR.SYS_C007856 のような制約名から、対象の列を特定できます。

制約名から対象列を特定する

制約名から対象テーブル・列を確認する
-- 制約名で検索してテーブル名と列名を確認する
SELECT
    c.constraint_name,
    c.table_name,
    c.constraint_type,   -- P=PRIMARY KEY, U=UNIQUE
    cc.column_name,
    cc.position
FROM user_constraints c
JOIN user_cons_columns cc ON c.constraint_name = cc.constraint_name
WHERE c.constraint_name = 'SYS_C007856'
ORDER BY cc.position;

重複しているデータを特定する

重複データを検出する
-- 対象列(email)で重複を検出
SELECT email, COUNT(*) AS cnt
FROM employees
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY cnt DESC;

-- INSERT しようとしているデータと既存データの重複を確認する
SELECT s.employee_id, s.email, s.name
FROM staging_employees s
WHERE EXISTS (
    SELECT 1 FROM employees e
    WHERE e.email = s.email
);
制約名が SYS_C###### の場合
制約作成時に名前を指定しなかった場合、Oracleが自動生成した名前(SYS_C######)が付きます。新規テーブル作成時は CONSTRAINT pk_employees PRIMARY KEY (employee_id) のように明示的に命名しておくと、エラー発生時に原因をすぐ特定できます。

MERGE 文で UPSERT(INSERT or UPDATE)する

「存在しなければINSERT、存在すればUPDATE」というパターン(UPSERT)は MERGE 文で実現できます。ORA-00001を回避しつつデータの同期が行えます。

MERGE文 ── 存在すればUPDATE、なければINSERT
MERGE INTO employees e
USING (
    SELECT 1 AS employee_id, 'sato_new@example.com' AS email, '佐藤太郎' AS name
    FROM dual
) src ON (e.employee_id = src.employee_id)
WHEN MATCHED THEN
    UPDATE SET e.email = src.email, e.name = src.name
WHEN NOT MATCHED THEN
    INSERT (employee_id, email, name)
    VALUES (src.employee_id, src.email, src.name);
MERGE文 ── ステージングテーブルから一括UPSERT
-- ステージングテーブル staging_employees から本テーブルへ一括反映
MERGE INTO employees e
USING staging_employees s ON (e.employee_id = s.employee_id)
WHEN MATCHED THEN
    UPDATE SET e.email = s.email, e.name = s.name
WHEN NOT MATCHED THEN
    INSERT (employee_id, email, name)
    VALUES (s.employee_id, s.email, s.name);

COMMIT;
MERGE 文の注意点
MERGE文のON条件には結合キーを指定します。UNIQUE制約のある列がON条件と異なる場合(例: ON条件はPKだがemail列にUNIQUE制約がある場合)、WHEN NOT MATCHED のINSERTでemail重複のORA-00001が発生する可能性があります。ON条件と制約の整合性を確認してください。

IGNORE_ROW_ON_DUPKEY_INDEX ── 重複行をスキップする

Oracle 11g R2 以降で使えるヒント句です。INSERT時に一意制約に違反する行をエラーにせずスキップします。新規データだけを取り込み、既存データはそのまま残したい場合に使います。

IGNORE_ROW_ON_DUPKEY_INDEX ── 重複行をスキップしてINSERT
-- PRIMARY KEY(employee_id)が重複する行はスキップされる
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(employees, pk_employees) */
INTO employees (employee_id, email, name)
SELECT employee_id, email, name
FROM staging_employees;

-- インデックス名ではなく列名で指定する方法
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(employees(employee_id)) */
INTO employees (employee_id, email, name)
VALUES (1, 'test@example.com', 'テスト');
-- employee_id=1 が既存なら行がスキップされ、エラーにならない
MERGE との使い分け
IGNORE_ROW_ON_DUPKEY_INDEX:重複行をスキップする(既存データは更新しない)
MERGE ... WHEN MATCHED THEN UPDATE:重複行を更新する(既存データを上書き)
「新規データだけ追加」なら前者、「既存データも最新に更新」なら後者を選んでください。

LOG ERRORS INTO ── エラー行をテーブルに退避する

ORA-00001に限らず、INSERT/UPDATE/DELETE/MERGEで発生したエラー行を別テーブルに退避できるOracle 10g R2以降の機能です。バッチ処理で「成功行は投入し、エラー行はログに残す」パターンを実現します。

LOG ERRORS INTO ── エラー行をログテーブルに退避
-- ステップ 1: エラーログテーブルを作成する
BEGIN
    DBMS_ERRLOG.CREATE_ERROR_LOG(
        dml_table_name  => 'EMPLOYEES',
        err_log_table_name => 'ERR_EMPLOYEES'  -- 任意のログテーブル名
    );
END;
/

-- ステップ 2: LOG ERRORS INTO 付きで INSERT を実行
INSERT INTO employees (employee_id, email, name)
SELECT employee_id, email, name
FROM staging_employees
LOG ERRORS INTO err_employees ('BATCH_20260331')
REJECT LIMIT UNLIMITED;
-- ORA-00001 が発生する行は err_employees に退避され、残りの行はINSERTされる

-- ステップ 3: エラー行を確認する
SELECT ora_err_mesg$, ora_err_tag$, employee_id, email, name
FROM err_employees
WHERE ora_err_tag$ = 'BATCH_20260331';
エラーログテーブルの列 説明
ORA_ERR_NUMBER$ ORAエラー番号(1=ORA-00001)
ORA_ERR_MESG$ エラーメッセージ全文
ORA_ERR_ROWID$ エラー行のROWID(UPDATEの場合)
ORA_ERR_OPTYP$ DML種別(I=INSERT, U=UPDATE, D=DELETE)
ORA_ERR_TAG$ ユーザー指定のタグ文字列

PL/SQL での DUP_VAL_ON_INDEX 例外処理

PL/SQLではORA-00001を DUP_VAL_ON_INDEX 定義済み例外でキャッチできます。行単位でINSERT or UPDATEの振り分けを行うパターンです。

DUP_VAL_ON_INDEX ── INSERT失敗時にUPDATEする
BEGIN
    INSERT INTO employees (employee_id, email, name)
    VALUES (1, 'sato@example.com', '佐藤太郎');
EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
        -- 一意制約違反の場合は UPDATE にフォールバック
        UPDATE employees
        SET email = 'sato@example.com', name = '佐藤太郎'
        WHERE employee_id = 1;
END;
/
パフォーマンス上の注意
大量データに対してループで INSERT → 例外 → UPDATE を繰り返すのは非効率です。大量データの場合は MERGE 文の方が圧倒的に高速です。DUP_VAL_ON_INDEX 方式は行数が少ない場合や、既存データの重複が稀な場合に適しています。

シーケンスに起因する ORA-00001 の対策

シーケンスで採番したIDをPRIMARY KEYに使っている場合でも、次のケースでORA-00001が発生することがあります。

  • 手動INSERTでシーケンスを使わずにIDを直接指定し、シーケンスの現在値と衝突した
  • データ移行時にIDを直接指定し、移行後にシーケンスの値をリセットしなかった
  • RAC環境でシーケンスの CACHE 設定とノード間の採番競合
シーケンスの現在値とテーブルの最大IDを確認する
-- シーケンスの現在値を確認
SELECT last_number FROM user_sequences WHERE sequence_name = 'EMP_SEQ';

-- テーブルの最大IDを確認
SELECT MAX(employee_id) FROM employees;

-- シーケンスがテーブルの最大IDより小さい場合、衝突が発生する
シーケンスの値を安全に進める
-- テーブルの最大IDより大きい値にシーケンスを進める
DECLARE
    v_max_id NUMBER;
    v_seq_val NUMBER;
BEGIN
    SELECT MAX(employee_id) INTO v_max_id FROM employees;
    LOOP
        SELECT emp_seq.NEXTVAL INTO v_seq_val FROM dual;
        EXIT WHEN v_seq_val > v_max_id;
    END LOOP;
END;
/
-- Oracle 12c以降なら ALTER SEQUENCE emp_seq RESTART START WITH 値; も使える

ORA-00001 の回避方法まとめ

方法 動作 適用場面
MERGE ... WHEN MATCHED / NOT MATCHED 存在すれば更新、なければ挿入 UPSERT処理(最も汎用的)
IGNORE_ROW_ON_DUPKEY_INDEX 重複行をスキップ(既存は変更しない) 新規データのみ追加したい場合
LOG ERRORS INTO エラー行をログテーブルに退避 バッチ処理でエラー行を後から確認したい場合
DUP_VAL_ON_INDEX 例外 PL/SQLでINSERT失敗時にUPDATEへ分岐 少量データ・例外的な重複がある場合

まとめ

ORA-00001 は一意制約に違反するデータの挿入・更新で発生する、最も基本的なOracleエラーです。

  • エラーメッセージの制約名から USER_CONSTRAINTS / USER_CONS_COLUMNS で対象列を特定する
  • 重複データは GROUP BY ... HAVING COUNT(*) > 1 で検出する
  • MERGE文:存在すればUPDATE、なければINSERTの最も汎用的なUPSERTパターン
  • IGNORE_ROW_ON_DUPKEY_INDEX:重複行をエラーにせずスキップする(11g R2+)
  • LOG ERRORS INTO:エラー行をログテーブルに退避して正常行はINSERTを継続する
  • DUP_VAL_ON_INDEX:PL/SQLでINSERT失敗時にUPDATEへフォールバック(少量データ向け)
  • シーケンスに起因する場合は、シーケンスの現在値とテーブルの最大IDを比較して調整する
  • 制約名は明示的に命名(CONSTRAINT pk_xxx PRIMARY KEY)しておくとトラブルシューティングが容易になる