Oracle PL/SQL の例外処理(Exception Handling)は、実行時エラーをコード内で捕捉して適切に対処するための仕組みです。例外処理を適切に実装することで、エラーが発生しても処理を安全に終了でき、意味のあるエラーメッセージを呼び出し元に返せます。
PL/SQL の例外は大きく3種類に分類されます。
| 種類 | 説明 | 主な例 |
|---|---|---|
| 定義済み例外 | Oracle が名前を付けた組み込み例外 | NO_DATA_FOUND、TOO_MANY_ROWS、ZERO_DIVIDE など |
| 未定義例外 | Oracle エラーだが名前がない。PRAGMA EXCEPTION_INIT で名前付けできる | ORA-00054(リソース・ビジー)など |
| ユーザー定義例外 | アプリ固有のビジネスルール違反を表すために自分で定義 | e_invalid_amount、e_stock_shortage など |
- EXCEPTION WHEN … THEN の基本構文と処理の流れ
- 定義済み例外の一覧と各エラーへの対処パターン
- PRAGMA EXCEPTION_INIT で ORA-XXXXX に名前を付ける方法
- ユーザー定義例外と RAISE の使い方
- RAISE_APPLICATION_ERROR でカスタムエラーを発生させる方法
- SQLCODE / SQLERRM / FORMAT_ERROR_BACKTRACE による診断
- 例外の伝播(ブロック間の伝播)と再発生(RAISE)
- 実務で使えるエラーログパターン
例外処理の基本構文
PL/SQL ブロックは BEGIN ... EXCEPTION ... END の構造を持ちます。EXCEPTION セクションに WHEN 例外名 THEN 処理 を書きます。
BEGIN
-- 通常処理
処理1;
処理2;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 0件の SELECT INTO で発生
DBMS_OUTPUT.PUT_LINE('データが見つかりません');
WHEN TOO_MANY_ROWS THEN
-- 複数行の SELECT INTO で発生
DBMS_OUTPUT.PUT_LINE('複数行が返されました');
WHEN OTHERS THEN
-- 上記いずれにも該当しない例外をすべて捕捉
DBMS_OUTPUT.PUT_LINE('予期しないエラー: ' || SQLERRM);
RAISE; -- 再発生させて呼び出し元にも伝える
END;
/
WHEN OTHERS THEN NULL; のようにエラーを無視すると、重大なバグが隠蔽されます。WHEN OTHERS を使う場合は必ず SQLCODE / SQLERRM をログに記録し、必要に応じて RAISE で再発生させることを推奨します。
定義済み例外の一覧
Oracle が名前を付けている主要な定義済み例外です。EXCEPTION セクションで直接使えます。
| 例外名 | ORAエラー | 発生する状況 |
|---|---|---|
NO_DATA_FOUND |
ORA-01403 | SELECT INTO で0件が返された |
TOO_MANY_ROWS |
ORA-01422 | SELECT INTO で2行以上が返された |
DUP_VAL_ON_INDEX |
ORA-00001 | UNIQUE 制約違反(重複INSERT) |
ZERO_DIVIDE |
ORA-01476 | 0 による除算 |
VALUE_ERROR |
ORA-06502 | 型変換失敗・桁あふれ・VARCHAR2 バッファ超過 |
INVALID_NUMBER |
ORA-01722 | SQL 層での数値変換失敗(PL/SQL は VALUE_ERROR) |
CURSOR_ALREADY_OPEN |
ORA-06511 | 既に OPEN されているカーソルを再 OPEN しようとした |
INVALID_CURSOR |
ORA-01001 | OPEN していないカーソルを FETCH/CLOSE しようとした |
ROWTYPE_MISMATCH |
ORA-06504 | REF CURSOR の行型が不一致 |
SELF_IS_NULL |
ORA-30625 | NULL のオブジェクト型インスタンスのメソッド呼び出し |
STORAGE_ERROR |
ORA-06500 | メモリ不足 |
TIMEOUT_ON_RESOURCE |
ORA-00051 | 待機中にタイムアウト発生 |
CREATE OR REPLACE PROCEDURE get_employee_salary (
p_emp_id IN employees.employee_id%TYPE,
p_salary OUT employees.salary%TYPE
) AS
BEGIN
SELECT salary
INTO p_salary
FROM employees
WHERE employee_id = p_emp_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 0件: 呼び出し元に意味のあるエラーを返す
RAISE_APPLICATION_ERROR(-20001,
'employee_id=' || p_emp_id || ' は存在しません');
WHEN TOO_MANY_ROWS THEN
-- データ整合性の問題(本来 PK なので起きないはずだが念のため)
RAISE_APPLICATION_ERROR(-20002,
'employee_id=' || p_emp_id || ' が重複しています');
WHEN OTHERS THEN
RAISE; -- 未知のエラーは再発生させる
END get_employee_salary;
/
PRAGMA EXCEPTION_INIT:未定義例外に名前を付ける
Oracle エラーには名前のないものも多くあります。PRAGMA EXCEPTION_INIT を使うと、ORA エラーコードを変数名に紐付けてWHEN 名前 THEN で捕捉できます。WHEN OTHERS で SQLCODE をチェックするより読みやすくなります。
DECLARE
-- ORA-00054: resource busy(NOWAIT でロック取得失敗)に名前を付ける
e_resource_busy EXCEPTION;
PRAGMA EXCEPTION_INIT(e_resource_busy, -54);
-- ORA-02292: 外部キー参照が残っているため削除できない
e_child_record_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(e_child_record_exists, -2292);
-- ORA-01031: 権限が不足しています
e_insufficient_priv EXCEPTION;
PRAGMA EXCEPTION_INIT(e_insufficient_priv, -1031);
BEGIN
-- NOWAIT: ロック取得できなければ即座に ORA-00054 が発生
UPDATE orders
SET status = 'PROCESSING'
WHERE order_id = 1001
FOR UPDATE NOWAIT;
EXCEPTION
WHEN e_resource_busy THEN
-- 名前付き例外で分かりやすく捕捉
DBMS_OUTPUT.PUT_LINE('注文 1001 は他のセッションがロック中です。後でリトライしてください。');
WHEN e_child_record_exists THEN
DBMS_OUTPUT.PUT_LINE('参照中のレコードがあるため削除できません');
WHEN OTHERS THEN
RAISE;
END;
/
ユーザー定義例外と RAISE
ビジネスルール違反など、ORA エラーとは関係のないアプリ固有の条件を例外として扱いたい場合は、EXCEPTION 型の変数を DECLARE セクションで定義し、RAISE で発生させます。
CREATE OR REPLACE PROCEDURE transfer_stock (
p_product_id IN NUMBER,
p_qty IN NUMBER
) AS
-- ビジネスルール違反を表すユーザー定義例外
e_invalid_qty EXCEPTION;
e_stock_shortage EXCEPTION;
v_stock NUMBER;
BEGIN
-- バリデーション: 数量は正の整数
IF p_qty <= 0 THEN
RAISE e_invalid_qty;
END IF;
-- 在庫確認
SELECT stock_qty INTO v_stock
FROM products WHERE product_id = p_product_id;
-- ビジネスルール: 在庫不足
IF v_stock < p_qty THEN
RAISE e_stock_shortage;
END IF;
-- 在庫を減らす
UPDATE products
SET stock_qty = stock_qty - p_qty
WHERE product_id = p_product_id;
COMMIT;
EXCEPTION
WHEN e_invalid_qty THEN
RAISE_APPLICATION_ERROR(-20010,
'数量は1以上を指定してください: ' || p_qty);
WHEN e_stock_shortage THEN
RAISE_APPLICATION_ERROR(-20011,
'在庫不足です。現在の在庫: ' || v_stock || ', 要求数: ' || p_qty);
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20012,
'商品が見つかりません: product_id=' || p_product_id);
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END transfer_stock;
/
RAISE_APPLICATION_ERROR:カスタムエラーを発生させる
RAISE_APPLICATION_ERROR(エラー番号, メッセージ) を使うと、ORA-XXXXX 形式のカスタムエラーを呼び出し元(アプリ、JDBC 等)に返せます。
- エラー番号の範囲: -20000 〜 -20999(ユーザー定義用に予約)
- メッセージ長: 最大 2048 バイト
- 第3引数
TRUE: 既存のエラースタックに追加(デフォルト FALSE は上書き)
-- 基本: エラー番号とメッセージを指定
RAISE_APPLICATION_ERROR(-20001, '入力値が無効です: ' || p_value);
-- 第3引数 TRUE: 既存のエラースタックを保持して追加
RAISE_APPLICATION_ERROR(-20099, 'ラッパーエラー', TRUE);
-- 実際のエラーコードの整理例(プロジェクト内でルール化推奨)
-- -20001: バリデーションエラー
-- -20002: データ重複エラー
-- -20003: 参照データなし
-- -20010〜-20019: 在庫系ビジネスルール違反
-- -20020〜-20029: 受注系ビジネスルール違反
-- ユーザー定義例外を RAISE_APPLICATION_ERROR で返す組み合わせパターン
DECLARE
e_invalid EXCEPTION;
PRAGMA EXCEPTION_INIT(e_invalid, -20001); -- エラー番号と紐付け
BEGIN
RAISE_APPLICATION_ERROR(-20001, 'カスタムエラーです');
EXCEPTION
WHEN e_invalid THEN
DBMS_OUTPUT.PUT_LINE('捕捉: ' || SQLERRM);
-- ORA-20001: カスタムエラーです
END;
/
SQLCODE / SQLERRM / FORMAT_ERROR_BACKTRACE:エラー情報の取得
| 関数・擬似列 | 返す値 | 主な用途 |
|---|---|---|
SQLCODE |
エラーコード(数値)。エラーなし=0、ORA-01403=-1403 | WHEN OTHERS 内でエラー種別を判定 |
SQLERRM |
エラーメッセージ文字列(最大 512 バイト) | ログへのエラー内容記録 |
DBMS_UTILITY.FORMAT_ERROR_STACK |
SQLERRM と同等(最大 2000 バイト) | 長いエラーメッセージの完全取得 |
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE |
例外が発生した行番号を含むスタックトレース(Oracle 10g 以降) | バグ特定・デバッグ |
CREATE OR REPLACE PROCEDURE log_and_reraise AS
BEGIN
-- 意図的にエラーを発生させる例
DECLARE
v_num NUMBER;
BEGIN
v_num := 1 / 0; -- ORA-01476: divisor is equal to zero
EXCEPTION
WHEN ZERO_DIVIDE THEN
-- エラー発生行番号を含むトレースを取得(Oracle 10g 以降)
DBMS_OUTPUT.PUT_LINE(
'BACKTRACE: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
);
-- 出力例:
-- BACKTRACE: ORA-06512: at "SCHEMA.LOG_AND_RERAISE", line 5
RAISE;
END;
END;
/
-- エラーログテーブルへの記録パターン
CREATE OR REPLACE PROCEDURE safe_process (p_id IN NUMBER) AS
BEGIN
処理;
EXCEPTION
WHEN OTHERS THEN
-- エラーログ記録(自律トランザクションで確実にコミット)
INSERT INTO error_log (
log_date, proc_name, err_code, err_msg, backtrace
) VALUES (
SYSDATE,
'SAFE_PROCESS',
SQLCODE,
SUBSTR(DBMS_UTILITY.FORMAT_ERROR_STACK, 1, 2000),
SUBSTR(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, 1, 2000)
);
COMMIT;
RAISE; -- 元の例外を再発生させる
END safe_process;
/
例外の伝播:ブロック間でどう伝わるか
PL/SQL のブロックは入れ子にできます。内側のブロックで発生した例外が処理されない場合、外側のブロックの EXCEPTION セクションに伝播します。
BEGIN
-- 外側ブロック
BEGIN
-- 内側ブロック
BEGIN
SELECT * FROM employees WHERE 1 = 0; -- 結果なし(ただしSELECT INTO でないので例外なし)
-- 実際のエラー例:
DECLARE v NUMBER; BEGIN v := 1/0; END; -- 内側で発生
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('内側: ZERO_DIVIDE を捕捉');
-- RAISE; を書かなければここで処理完了(外側には伝播しない)
END;
DBMS_OUTPUT.PUT_LINE('内側の後続処理は実行される');
EXCEPTION
WHEN OTHERS THEN
-- 内側で RAISE; した場合のみここに来る
DBMS_OUTPUT.PUT_LINE('外側: ' || SQLERRM);
END;
DBMS_OUTPUT.PUT_LINE('外側の後続処理');
END;
/
RAISE 例外名;— 指定した例外を発生させる(DECLARE セクション外でも使用可)RAISE;— EXCEPTION ブロック内で現在の例外を再発生させる(スタックトレース保持)- 例外が EXCEPTION セクションで捕捉されずに伝播すると、呼び出し元の EXCEPTION セクションへ伝わる
- 最終的に捕捉されなければ、呼び出し元アプリ(JDBC 等)にエラーが返る
自律トランザクション(PRAGMA AUTONOMOUS_TRANSACTION)でエラーログを確実に保存
通常の処理でエラーが発生して ROLLBACK すると、エラーログの INSERT も一緒に取り消されてしまいます。PRAGMA AUTONOMOUS_TRANSACTION を使ったロギングプロシージャを作ると、メイン処理がロールバックされてもログだけ確実にコミットできます。
-- エラーログテーブル(事前作成)
-- CREATE TABLE error_log (
-- log_id NUMBER GENERATED ALWAYS AS IDENTITY,
-- log_date DATE DEFAULT SYSDATE,
-- proc_name VARCHAR2(100),
-- err_code NUMBER,
-- err_msg VARCHAR2(2000),
-- backtrace VARCHAR2(4000)
-- );
CREATE OR REPLACE PROCEDURE write_error_log (
p_proc_name IN VARCHAR2,
p_err_code IN NUMBER,
p_err_msg IN VARCHAR2,
p_backtrace IN VARCHAR2
) AS
-- 自律トランザクション: メイン処理のロールバックに影響されない
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO error_log (proc_name, err_code, err_msg, backtrace)
VALUES (p_proc_name, p_err_code,
SUBSTR(p_err_msg, 1, 2000),
SUBSTR(p_backtrace, 1, 4000));
COMMIT; -- 自律トランザクションを独立してコミット
END write_error_log;
/
-- 呼び出し側での使用例
CREATE OR REPLACE PROCEDURE main_process AS
BEGIN
INSERT INTO orders (...) VALUES (...);
-- ... 何らかの処理 ...
RAISE_APPLICATION_ERROR(-20001, 'テストエラー'); -- エラー発生
EXCEPTION
WHEN OTHERS THEN
-- ログは自律トランザクションでコミットされる
write_error_log(
'MAIN_PROCESS',
SQLCODE,
DBMS_UTILITY.FORMAT_ERROR_STACK,
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
);
ROLLBACK; -- メイン処理はロールバック
RAISE;
END main_process;
/
まとめ:例外処理の実装チェックリスト
- 想定されるエラーは 個別の WHEN 句で明示的に捕捉する
WHEN OTHERS THEN NULLは禁止。必ずSQLCODE / SQLERRMを記録するかRAISEする- ORA エラーコードに名前を付けたい場合は PRAGMA EXCEPTION_INIT を使う
- ビジネスルール違反は ユーザー定義例外 + RAISE_APPLICATION_ERROR(-20000〜-20999) で返す
- デバッグには
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(Oracle 10g 以降)でエラー行を特定する - エラーログは PRAGMA AUTONOMOUS_TRANSACTION で独立してコミットし、ロールバックから保護する
- OPEN したカーソルは
%ISOPENを確認して EXCEPTION ブロックでも CLOSE する
SELECT INTO で発生する NO_DATA_FOUND・TOO_MANY_ROWS の詳細と対処パターンはORA-01403・ORA-01422 完全ガイドを、例外処理を組み込んだストアドプロシージャの作成方法はストアドプロシージャ・ファンクションの作成完全ガイドも参照してください。

