DB/Oracle/PLSQL/例外処理

Top/DB/Oracle/PLSQL/例外処理

目次

例外処理。えくせぷしょん。ぬるぽ!


基本的な例外処理

たとえば明示カーソルを使用しない SELECT ... INTO 文で、返りが複数行だった場合や、0 件だった場合に例外(事前に定義済みの例外で、それぞれ too_many_rows、no_data_found)が発生する。このようなときに、PL/SQL は EXCEPTION セクションに処理を移す。

  • 例外処理
    declare
        ...
    begin
        -- 例外がここで発生したとする
        select ... into ...;
    
    -- 例外処理部の記述
    exception
        -- 例外ハンドラ。エラー名と、対応する処理内容を記述
        -- SELECT 文で複数行が戻されてエラーになったときの処理
        when too_many_rows then
            ...;
        -- SELECT 文で行が戻されないでエラーになったときの処理
        when no_data_found then
            ...;
        -- 上記以外のすべての例外に対応するハンドラ
        when others then
            ...;
    end;
    /
    

注意事項

  • エラーが発生したとき、例外処理部で処理できれば、その PL/SQL ブロック自体は正常終了扱いになる。逆に想定外のエラーが発生した場合は、呼び出し元(たとえば SQL*Plus)にエラーを戻す。呼び出し元にエラーを戻すのはプログラムとしてもはやバグでしかないので、極力 OTHERS 例外ハンドラを使ったほうがよい。たぶん
  • 例外処理部でエラーを処理したあとに実行部に制御を戻すことはできない。無理やり実装するなら、エラーの発生しうる箇所をネストして記述すればできる
    begin
        ...
        -- 例外が発生しうる処理
        begin
            -- ここで例外が発生したとする
            select ... into ...;
        exception
            -- 子ブロック内の例外は子ブロックの例外処理部にわたる
            when ... then ...;
        end;
        -- 子ブロックの例外処理部でエラー処理が終了すれば
        -- 子ブロック自体は正常終了扱いになるので親に処理が戻る
        ...
    exception
        -- もし子ブロックの例外処理中に例外が発生したらここに制御が移動
        ...
    end;
    
  • 接続先のデータベースによって発生するエラーは PL/SQL の例外処理部の処理の対象外。たとえば存在しない表に対する SELECT 文の発行時は、PL/SQL からではなく Oracle Database からエラーが帰る。このような例外は OTHERS ハンドラでもキャッチできない

例外の種類

事前に定義済みの例外のほか、未定義の例外、ユーザ定義例外も利用可能。

事前定義例外

定義済み例外とは、PL/SQL によって既に用意されている例外のこと。

事前定義例外の例

定義済み例外には以下のようなものがある。詳細は Oracle の 『Oracle Database PL/SQL 言語リファレンス』を参照。

  • 事前定義例外の例
    例外名意味
    NO_DATA_FOUND1 行であるべき問い合わせの結果が 0 件だったとき
    TOO_MANY_ROWS1 行であるべき問い合わせの結果が複数行だったとき
    INVALID_CURSOR無効なカーソル操作を行ったとき
    ZERO_DIVIDEゼロ除算を行ったとき
    DUP_VAL_ON_INDEX挿入する値が重複していたとき

使い方

上の基本的な例外処理 がまさにそれ。そっちを見よう。

非事前定義例外

Oracle のエラー番号はあるけど、定義済みのように使いやすい別名が付けられていない例外。例外処理部で処理するには、エラー番号に対応した名前を自分でつける必要がある。

使い方

手順は以下の通り。

  1. 例外名の宣言
  2. 例外名とエラー番号の関連付け
  3. 例外処理部での利用
  • 使い方
    declare
        -- 例外名の宣言。EXCEPTION 型
        e_hoge exception;
    
        -- 例外名と(例として)エラー番号 -01400 の関連付け
        pragma exception_init(e_hoge, -01400);
    begin
        ...
        -- 例外の発生する処理
        insert into ... ;
        ...
    exception
        -- 宣言したエラー名でキャッチできる
        when e_hoge then
            ... ;
    end;
    /
    

ユーザ定義例外

ユーザが定義する例外。ルールの制御につかう。たとえばこの値は 100 以上ではいけないんだ! ていうときとか。

単に投げて受けて終わりの簡単なユーザ定義例外も作れるし、Oracle で使われていないエラー番号を付加して Oracle が発生させたエラーであるかのようにふるまわせることもできる。

使い方

単純な例はこう。

  • 単純なユーザ定義例外の利用
    declare
        -- 例外名の宣言
        e_hoge exception;
    begin
        ...
        -- エラーを投げるか否かの判定
        if sql%notfound then
            -- エラーを投げる
            raise e_hoge;
        end if;
        ...
    exception
        when e_hoge then
            ... ;
    end;
    / 
    

Oracle のエラーっぽくふるまわせることも可能。エラー番号を付けて、データベースのエラー処理機能を使って投げられる。ほかの Oracle の例外と同じ方法でユーザ定義例外も扱えるので、設計上例外処理そのものが楽になる。

使えるエラー番号は、-20000 から -20999 のいずれか。

  • ストアドプロシージャを用いたユーザ定義例外の利用
    declare
        -- 自分自身で例外処理するのであれば宣言をして番号と関連付ける
        e_hoge exception;
        pragma exception_init(e_hoge, -20999);
    begin
        ...
        if sql%notfound then
            -- ストアドプロシージャをコールして例外を発生
            -- 任意のエラー番号とエラーメッセージを渡す
            raise_application_error(-20999, 'なんかエラーだって!');
        end if;
    exception
        -- 自分自身で処理するならキャッチも必要
        when e_hoge then
            ... ;
    end;
    /
    

例外検出関数

例外処理部で使える便利な関数。

SQLCODE

例外のエラー番号を返す関数。

  • SQLCODE の返り値の例
    説明
    0例外が発生していない
    1ユーザ定義例外
    +100NO_DATA_FOUND の例外
    -NOracle の返すエラー番号

SQLERRM

例外のエラーメッセージを返す関数。そのまま。

使い方

主に例外処理部で利用する。SQL 文の中に直接これらの関数を埋めることは許可されないので、たとえば自前のエラーログテーブルに代入したい場合などは、一度別の変数に格納してから使う必要がある。

  • 使い方
    declare
        -- 例外処理部で使う変数の宣言
        v_errcode := number;
        v_errmsg  := varchar2(100);
    begin
        ...
    exception
        when others then
            -- 例外発生時に番号とメッセージを出力する
            dbms_output.put_line('ErrCode: ' || sqlcode) ;
            dbms_output.put_line('ErrMsg : ' || sqlerrm) ;
    
            -- エラーログテーブルに値を入れる
            -- SQL 文に直接は入れられないので一度変数に代入
            v_errcode := sqlcode;
            c_errmsg  := substr(sqlerrm, 1, 50);
            -- テーブルにぶちこむ
            insert into err_log values (v_errcode, v_errmsg);
    end;
    /
    

上の例では、エラーメッセージが長すぎるときに備えて、代入先の変数のバイト数の半分である 50 で エラーメッセージを切り出している。VARCHAR2 で指定するのはバイト数で、SUBSTR で指定するのは文字数なので、日本語では 100 バイトの変数には 50 文字しか入れられない(1 文字 = 2 バイト。文字コードによっては 3 バイトなので 33 文字)。

ただし日本語環境だからといってすべてのエラーメッセージが日本語であるというわけでもなく、一部は英語のまま。上の例のような切り出しをすると、英語のメッセージも 50 文字(50 バイト)で切られてしまう。

VARCHAR2 は可変長文字列なので宣言時に大きな値を指定してもメモリを食うわけではない。これを利用して十分に大きな値をとっておくことで対応するか、あるいはセッションのロケールを英語にするなどしても対応できないこともない。

まあでもエラー処理だし、英語が半分でちょん切られたとしても、べつにいいんじゃないかなあとは思うけど。


Last-modified: 2011-11-09 (水) 11:50:31