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)しようとしたときに発生します。
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
);
制約作成時に名前を指定しなかった場合、Oracleが自動生成した名前(SYS_C######)が付きます。新規テーブル作成時は
CONSTRAINT pk_employees PRIMARY KEY (employee_id) のように明示的に命名しておくと、エラー発生時に原因をすぐ特定できます。
MERGE 文で UPSERT(INSERT or UPDATE)する
「存在しなければINSERT、存在すればUPDATE」というパターン(UPSERT)は MERGE 文で実現できます。ORA-00001を回避しつつデータの同期が行えます。
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);
-- ステージングテーブル 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文のON条件には結合キーを指定します。UNIQUE制約のある列がON条件と異なる場合(例: ON条件はPKだがemail列にUNIQUE制約がある場合)、WHEN NOT MATCHED のINSERTでemail重複のORA-00001が発生する可能性があります。ON条件と制約の整合性を確認してください。
IGNORE_ROW_ON_DUPKEY_INDEX ── 重複行をスキップする
Oracle 11g R2 以降で使えるヒント句です。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 が既存なら行がスキップされ、エラーにならない
IGNORE_ROW_ON_DUPKEY_INDEX:重複行をスキップする(既存データは更新しない)MERGE ... WHEN MATCHED THEN UPDATE:重複行を更新する(既存データを上書き)「新規データだけ追加」なら前者、「既存データも最新に更新」なら後者を選んでください。
LOG ERRORS INTO ── エラー行をテーブルに退避する
ORA-00001に限らず、INSERT/UPDATE/DELETE/MERGEで発生したエラー行を別テーブルに退避できるOracle 10g R2以降の機能です。バッチ処理で「成功行は投入し、エラー行はログに残す」パターンを実現します。
-- ステップ 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の振り分けを行うパターンです。
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 設定とノード間の採番競合
-- シーケンスの現在値を確認 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)しておくとトラブルシューティングが容易になる

