DBMS_ERRLOG と LOG ERRORS は、OracleのDMLで失敗した行だけをエラーログ表へ退避し、成功行の処理を継続するための仕組みです。データ移行、外部データ取り込み、バルク更新、MERGE による差分反映などで「1件の不正データで全体を止めたくない」場面に向いています。
この記事では、CREATE_ERROR_LOG での ERR$_ 表作成、LOG ERRORS INTO ... REJECT LIMIT の使い方、エラー内容の読み方、再処理設計、保持期限、ジョブ運用までをPL/SQL実務向けに整理します。DML全体の基本は INSERT・UPDATE・DELETE完全ガイド、MERGEの詳細は MERGE文完全ガイド も参考になります。
DBMS_ERRLOG.CREATE_ERROR_LOGの使い方ERR$_表の列構造と読み方LOG ERRORS INTOとREJECT LIMITINSERT/UPDATE/MERGE/FORALLでの使い分けskip_unsupported、LOB列、拾えるエラー・拾えないエラー- 再処理、隔離、保持期限、索引、ジョブ運用
DBMS_ERRLOGとは
DBMS_ERRLOG は、DMLエラーログ表を作成するためのパッケージです。作成したエラーログ表をDML文の LOG ERRORS 句で指定すると、行単位のエラーを記録しながら処理を継続できます。
INSERT、UPDATE、DELETE、MERGE などで利用します。ERR$_ で始まるエラーログ表へ保存します。エラーログ表を作成する
まず対象表に対するエラーログ表を作ります。デフォルトでは、対象表名の先頭25文字に ERR$_ を付けた名前になります。既存名と衝突する場合や運用命名を統一したい場合は err_log_table_name を指定します。
BEGIN
DBMS_ERRLOG.CREATE_ERROR_LOG(
dml_table_name => 'SALES',
err_log_table_name => 'ERR$_SALES',
err_log_table_space => 'DATA_TS',
skip_unsupported => TRUE
);
END;
/
公式仕様では、LONG、CLOB、BLOB、BFILE、ADT 型はエラーログ表の対象列としてサポートされません。skip_unsupported => TRUE を指定すると、非対応列をエラーログ表に追加せず処理できます。LOBを別途調査する必要がある場合は、主キーや外部IDをエラーログ側に残せる設計にします。
ERR$_表の列構造
ERR$_ 表には、Oracleが使う制御列と、対象表の列に対応するログ列が作られます。対象表の列は縦持ちではなく、同名に近い列として横持ちで作られ、多くは VARCHAR2(4000) として値が保存されます。
DESC ERR$_SALES -- 代表的な制御列 -- ORA_ERR_NUMBER$ : エラー番号 -- ORA_ERR_MESG$ : エラーメッセージ -- ORA_ERR_ROWID$ : エラー行のROWID。INSERT失敗ではNULLになりやすい -- ORA_ERR_OPTYP$ : 操作種別。I/U/Dなど -- ORA_ERR_TAG$ : LOG ERRORS句で指定した任意タグ SELECT column_name, data_type, data_length FROM user_tab_columns WHERE table_name = 'ERR$_SALES' ORDER BY column_id;
ORA_ERR_ROWID$ は便利ですが、常に入るとは限りません。INSERT失敗では元表のROWIDが存在しないためNULLになりやすく、MERGEや制約違反でも状況により列値から原因を追う方が現実的です。そのため、再処理に必要な業務キーを対象表側に持たせておくことが重要です。
基本構文:LOG ERRORSとREJECT LIMIT
DMLの末尾に LOG ERRORS INTO err$_table ('tag') REJECT LIMIT n を付けます。REJECT LIMIT は許容する失敗件数です。UNLIMITED にすると、可能な限り失敗行をログへ逃して処理を続けます。
INSERT INTO sales(id, cust_id, amount, sale_dt)
SELECT id, cust_id, amount, sale_dt
FROM sales_stg
LOG ERRORS INTO err$_sales ('LOAD_20260527')
REJECT LIMIT UNLIMITED;
COMMIT;
ORA_ERR_TAG$ に入るタグは、ロードID、ジョブID、対象日、ステップ名などを入れるのがおすすめです。同じ ERR$_ 表を複数処理で共有しても、あとから対象実行分だけ抽出できます。
MERGEで使う
MERGE でも LOG ERRORS を使えます。UPSERTで一意制約、NOT NULL、外部キー、型変換などに失敗した行を退避し、他の行を進めたい場合に有効です。
MERGE INTO customers d
USING (
SELECT cust_id, name, email, updated_at
FROM customers_delta
) s
ON (d.cust_id = s.cust_id)
WHEN MATCHED THEN
UPDATE SET d.name = s.name,
d.email = s.email,
d.updated_at = s.updated_at
WHEN NOT MATCHED THEN
INSERT (cust_id, name, email, updated_at)
VALUES (s.cust_id, s.name, s.email, s.updated_at)
LOG ERRORS INTO err$_customers ('CUSTOMER_DELTA_20260527')
REJECT LIMIT UNLIMITED;
MERGEでは入力側の重複により ORA-30926 など文全体の問題になるケースもあります。MERGEのON句設計や重複排除は MERGE文の高度利用、一意制約違反の整理は ORA-00001の原因と解決方法 が参考になります。
FORALL SAVE EXCEPTIONSとの使い分け
PL/SQLの一括処理では FORALL SAVE EXCEPTIONS も失敗行を拾う方法です。ただし、集合SQLで処理できるなら、1本のDMLに LOG ERRORS を付ける方がシンプルで速いことが多いです。
FORALL i IN 1 .. l_rows.COUNT
INSERT INTO sales(id, cust_id, amount, sale_dt)
VALUES (
l_rows(i).id,
l_rows(i).cust_id,
l_rows(i).amount,
l_rows(i).sale_dt
)
LOG ERRORS INTO err$_sales ('FORALL_20260527')
REJECT LIMIT UNLIMITED;
FORALL SAVE EXCEPTIONS の詳細は FORALL SAVE EXCEPTIONSで失敗行を個別ログ化する方法、大量処理全体は BULK COLLECT / FORALL完全ガイド も確認してください。
エラー内容を読む
実行後は ERR$_ 表を集計します。まずタグ単位で件数を見て、エラー番号・メッセージ別に分類し、再処理できるものと隔離すべきものに分けます。
SELECT ora_err_tag$,
ora_err_number$,
SUBSTR(ora_err_mesg$, 1, 200) AS err_msg,
COUNT(*) AS err_count
FROM err$_sales
GROUP BY ora_err_tag$,
ora_err_number$,
SUBSTR(ora_err_mesg$, 1, 200)
ORDER BY err_count DESC;
SELECT ora_err_number$,
ora_err_mesg$,
ora_err_rowid$,
ora_err_optyp$,
ora_err_tag$,
id,
cust_id,
amount,
sale_dt
FROM err$_sales
WHERE ora_err_tag$ = 'LOAD_20260527'
ORDER BY ora_err_number$, id;
エラーログ表の対象列は文字列として保存されることが多いため、再投入前に型変換や検証を改めて行います。エラー表をそのまま本表へ戻すのではなく、修正用ステージングへ移す設計が安全です。
拾えるエラーと拾えないエラー
LOG ERRORS は万能ではありません。行単位の制約違反や変換エラーには強い一方、SQL文全体が成立しないエラー、権限不足、領域不足、入力側重複などは文全体が失敗することがあります。
skip_unsupported を検討します。再処理フローを設計する
DBMS_ERRLOGは「ログを取って終わり」ではありません。本番運用では、ERR$_表から失敗行を分類し、修正可能なものを再投入し、修正できないものを隔離し、実行サマリを残します。
-- 1. エラーを隔離キューへ保存
INSERT INTO sales_error_queue(
load_tag,
err_number,
err_message,
sales_id,
cust_id,
amount_text,
reason,
created_at
)
SELECT ora_err_tag$,
ora_err_number$,
SUBSTR(ora_err_mesg$, 1, 1000),
id,
cust_id,
amount,
'needs review',
SYSTIMESTAMP
FROM err$_sales
WHERE ora_err_tag$ = 'LOAD_20260527';
-- 2. 修正済みデータを再投入用ステージングへ
INSERT INTO sales_stg_retry(id, cust_id, amount, sale_dt)
SELECT sales_id,
cust_id,
TO_NUMBER(amount_text),
SYSDATE
FROM sales_error_queue
WHERE load_tag = 'LOAD_20260527'
AND reason = 'fixed';
-- 3. 対象タグのERR$_表を保持期限に応じて削除
DELETE FROM err$_sales
WHERE ora_err_tag$ = 'LOAD_20260527';
JSON取り込みでDBMS_ERRLOGを使う場合は、受信JSONをステージングし、JSON_TABLE で展開してからDMLへつなげます。この流れは JSON_TABLEでJSONを取り込む方法 と相性が良いです。
ERRTAGの設計
ORA_ERR_TAG$ は再処理の生命線です。タグが曖昧だと、どのジョブのどの実行で発生したエラーか分からなくなります。ロードID、日付、対象テーブル、処理ステップ、再実行回数を含めると追いやすくなります。
-- 例: ジョブ名_対象日_ステップ_実行ID LOG ERRORS INTO err$_sales ( 'SALES_LOAD_20260527_STEP1_RUN00042' ) REJECT LIMIT UNLIMITED
ログ全体の追跡性を高めるには、ERR$_表だけでなく、ジョブ実行ログや監査ログにも同じ実行IDを残します。監査設計は ロギングとトレーサビリティを両立する監査設計 が参考になります。
保持期限・索引・パーティション
ERR$_ 表は放置すると増え続けます。タグ、エラー番号、作成日時で検索するなら索引を追加し、保持期限を決めます。大量ロードでエラー件数が多い場合は、エラーログ表の表領域やパーティションも検討します。
ALTER TABLE err$_sales ADD ( logged_at TIMESTAMP DEFAULT SYSTIMESTAMP ); CREATE INDEX err_sales_ix1 ON err$_sales(ora_err_tag$, ora_err_number$); CREATE INDEX err_sales_ix2 ON err$_sales(logged_at); -- 90日を超えたログを削除 DELETE FROM err$_sales WHERE logged_at < SYSTIMESTAMP - INTERVAL '90' DAY;
CREATE_ERROR_LOG で自動作成したあとに、運用列や索引を追加して構いません。ただし、再作成やDDL変更時に運用列を消さないよう、作成スクリプトとして管理します。
ジョブ運用に組み込む
データ取り込みはジョブとして動くことが多いため、成功件数、失敗件数、ERR$_件数、再処理件数をジョブ履歴へ残します。DBMS_ERRLOGでDMLが成功しても、失敗行が一定件数を超えたら業務的には異常とみなす判断が必要です。
INSERT INTO load_job_summary(
load_tag,
target_table,
success_count,
error_count,
status,
created_at
)
SELECT 'LOAD_20260527',
'SALES',
SQL%ROWCOUNT,
(SELECT COUNT(*) FROM err$_sales WHERE ora_err_tag$ = 'LOAD_20260527'),
CASE
WHEN (SELECT COUNT(*) FROM err$_sales WHERE ora_err_tag$ = 'LOAD_20260527') = 0
THEN 'SUCCESS'
ELSE 'WARNING'
END,
SYSTIMESTAMP
FROM dual;
ジョブ管理は DBMS_SCHEDULER完全ガイド、ジョブ履歴の集中管理は ジョブ実行履歴の集中管理と異常検知 と組み合わせると運用しやすくなります。
本番前チェックリスト
skip_unsupported を検討したか。ORA_ERR_TAG$ にジョブIDや実行IDを入れているか。まとめ
DBMS_ERRLOG と LOG ERRORS を使うと、DML中の失敗行を ERR$_ 表へ退避し、成功行の処理を継続できます。データ移行、外部連携、バルク更新、MERGEでは、失敗行の隔離と再処理を標準化できる便利な仕組みです。
実務では、エラーログ表の列構造、skip_unsupported、拾える/拾えないエラー、REJECT LIMIT、タグ設計、再処理、保持期限、ジョブ監視まで含めて設計します。DBMS_ERRLOGは「エラーを無視する機能」ではなく、「失敗行を管理可能な形で残す機能」として使うのが大事です。

