【Oracle】PL/SQL 例外処理(Exception Handling)完全ガイド|定義済み例外・PRAGMA EXCEPTION_INIT・RAISE_APPLICATION_ERROR・FORMAT_ERROR_BACKTRACEまで実例で解説

【Oracle】PL/SQL 例外処理(Exception Handling)完全ガイド|定義済み例外・PRAGMA EXCEPTION_INIT・RAISE_APPLICATION_ERROR・FORMAT_ERROR_BACKTRACEまで実例で解説 Oracle

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 だけを書くのは危険
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 OTHERSSQLCODE をチェックするより読みやすくなります。

PRAGMA EXCEPTION_INIT の使い方
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 で発生させます。

ユーザー定義例外の定義と 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 の使い方
-- 基本: エラー番号とメッセージを指定
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 以降) バグ特定・デバッグ
FORMAT_ERROR_BACKTRACE でエラー発生行を特定する
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 セクションに伝播します。

例外の伝播と RAISE による再発生
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 の動作まとめ

  • 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_FOUNDTOO_MANY_ROWS の詳細と対処パターンはORA-01403・ORA-01422 完全ガイドを、例外処理を組み込んだストアドプロシージャの作成方法はストアドプロシージャ・ファンクションの作成完全ガイドも参照してください。