【PL/SQL】DBMS_ERRLOGとLOG ERRORSの使い方|REJECT LIMIT・ERR$_表・再処理設計まで

【PL/SQL】エラーログを自動収集するDBMS_ERRLOGの使い方 PL/SQL

DBMS_ERRLOGLOG 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 INTOREJECT LIMIT
  • INSERT / UPDATE / MERGE / FORALL での使い分け
  • skip_unsupported、LOB列、拾えるエラー・拾えないエラー
  • 再処理、隔離、保持期限、索引、ジョブ運用
スポンサーリンク

DBMS_ERRLOGとは

DBMS_ERRLOG は、DMLエラーログ表を作成するためのパッケージです。作成したエラーログ表をDML文の LOG ERRORS 句で指定すると、行単位のエラーを記録しながら処理を継続できます。

目的失敗行だけを退避し、成功行の処理を進めます。
対象INSERTUPDATEDELETEMERGE などで利用します。
保存先ERR$_ で始まるエラーログ表へ保存します。
向き不向き行単位で修正・再投入できるデータ品質エラーに向きます。

エラーログ表を作成する

まず対象表に対するエラーログ表を作ります。デフォルトでは、対象表名の先頭25文字に ERR$_ を付けた名前になります。既存名と衝突する場合や運用命名を統一したい場合は err_log_table_name を指定します。

create-error-log.sql
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;
/

公式仕様では、LONGCLOBBLOBBFILEADT 型はエラーログ表の対象列としてサポートされません。skip_unsupported => TRUE を指定すると、非対応列をエラーログ表に追加せず処理できます。LOBを別途調査する必要がある場合は、主キーや外部IDをエラーログ側に残せる設計にします。

ERR$_表の列構造

ERR$_ 表には、Oracleが使う制御列と、対象表の列に対応するログ列が作られます。対象表の列は縦持ちではなく、同名に近い列として横持ちで作られ、多くは VARCHAR2(4000) として値が保存されます。

err-table-columns.sql
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-log-errors.sql
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-log-errors.sql
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 を付ける方がシンプルで速いことが多いです。

LOG ERRORSSQLだけで集合処理できるロード、MERGE、更新に向きます。
SAVE EXCEPTIONS行ごとにPL/SQL側の前処理や個別呼び出しが必要な場合に向きます。
両方の併用FORALL内のDMLにLOG ERRORSを付けることもできますが、運用は複雑になります。
再処理LOG ERRORSはERR$_表、SAVE EXCEPTIONSはSQL%BULK_EXCEPTIONSから設計します。
forall-log-errors.sql
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$_ 表を集計します。まずタグ単位で件数を見て、エラー番号・メッセージ別に分類し、再処理できるものと隔離すべきものに分けます。

inspect-error-log.sql
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文全体が成立しないエラー、権限不足、領域不足、入力側重複などは文全体が失敗することがあります。

拾いやすいNOT NULL、一意制約、外部キー、CHECK制約、値変換エラーなど。
拾えないことがある構文エラー、権限不足、列不存在、表不存在、領域不足、入力側重複によるMERGE失敗など。
トリガー例外トリガー内のアプリケーション例外は全体失敗になることがあります。
LOB/ADT列エラーログ表への対象列生成に非対応。skip_unsupported を検討します。

再処理フローを設計する

DBMS_ERRLOGは「ログを取って終わり」ではありません。本番運用では、ERR$_表から失敗行を分類し、修正可能なものを再投入し、修正できないものを隔離し、実行サマリを残します。

reprocess-flow.sql
-- 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、日付、対象テーブル、処理ステップ、再実行回数を含めると追いやすくなります。

errtag-design.sql
-- 例: ジョブ名_対象日_ステップ_実行ID
LOG ERRORS INTO err$_sales (
  'SALES_LOAD_20260527_STEP1_RUN00042'
)
REJECT LIMIT UNLIMITED

ログ全体の追跡性を高めるには、ERR$_表だけでなく、ジョブ実行ログや監査ログにも同じ実行IDを残します。監査設計は ロギングとトレーサビリティを両立する監査設計 が参考になります。

保持期限・索引・パーティション

ERR$_ 表は放置すると増え続けます。タグ、エラー番号、作成日時で検索するなら索引を追加し、保持期限を決めます。大量ロードでエラー件数が多い場合は、エラーログ表の表領域やパーティションも検討します。

err-table-maintenance.sql
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が成功しても、失敗行が一定件数を超えたら業務的には異常とみなす判断が必要です。

job-summary.sql
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完全ガイド、ジョブ履歴の集中管理は ジョブ実行履歴の集中管理と異常検知 と組み合わせると運用しやすくなります。

本番前チェックリスト

ERR$_表対象表ごとに作成し、非対応列は skip_unsupported を検討したか。
タグORA_ERR_TAG$ にジョブIDや実行IDを入れているか。
REJECT LIMIT許容失敗件数を業務要件で決めたか。
拾えないエラー権限不足、領域不足、構文エラー、入力重複への対策があるか。
再処理隔離、修正、再投入、削除の手順があるか。
保持期限ERR$_表の削除・アーカイブ・索引方針を決めたか。
監視エラー件数がしきい値を超えたらジョブを警告/失敗扱いにするか。
検証正常行、制約違反行、拾えないエラーのテストデータを用意したか。

まとめ

DBMS_ERRLOGLOG ERRORS を使うと、DML中の失敗行を ERR$_ 表へ退避し、成功行の処理を継続できます。データ移行、外部連携、バルク更新、MERGEでは、失敗行の隔離と再処理を標準化できる便利な仕組みです。

実務では、エラーログ表の列構造、skip_unsupported、拾える/拾えないエラー、REJECT LIMIT、タグ設計、再処理、保持期限、ジョブ監視まで含めて設計します。DBMS_ERRLOGは「エラーを無視する機能」ではなく、「失敗行を管理可能な形で残す機能」として使うのが大事です。