PL/SQLの勉強14 例外処理の書き方

今回は「エラー」についてやっていきます。

プログラムを書いていると、必ず「思い通りにはいかない」時がやってきます。
例えば「想定外のデータが入力された」とか、「テーブルにあると思ってたデータが無かった」とか、逆に「想定以上にデータ数が多い」とか。。。

全ての例外を想定するのは難しいですが、プログラムを書くものとして、ある程度は『想定して』『対応して』おきましょう。

例外処理の書き方(基本型)

まずは基本型からです。

四則計算の基本的な、やってはいけない「0割り」を使ってエラーを出してみようと思います。
こう書きます。

declare
    n_num  number;
begin
    -- 0割り:必ずエラーになる!
    n_num := 1/0;
	-- 数字を出力しようとする
	dbms_output.put_line(n_num);
exception
    when others then
      dbms_output.put_line('エラー');
end;

5行目で0割りとなって「exception」に飛びました。
ので、7行目に数字を出力しようとしても表示されていません。

なぜエラーとなったか?の情報を取得する

上記の例だと「0割りだと分かって」書いています。
でも実際は、どこにエラー(想定外のこと)があるんだろう?と探す必要があります。

なぜエラーになったのか?なぜexceptionに飛んだのか?を知るために、oracleは基本的な情報を提供しています。それを知る術を書いてみます。

declare
    n_num  number;
begin
    -- 0割り:必ずエラーになる!
    n_num := 1/0;
	-- 数字を出力しようとする
	dbms_output.put_line(n_num);
exception
    when others then
      dbms_output.put_line('エラー発生!');
      dbms_output.put_line('sqlcode : ' || sqlcode);
      dbms_output.put_line('sqlerrm : ' || sqlerrm);
end;

11行目・12行目を追加しました。この2つが、エラーの内容を知るのに必要な情報です。

sqlcode

エラーを番号で種類分けされています。
今回は「-1476番のエラー」ということになります。つまり0割りは1476番、ということです。
「oracle エラー 1476」で検索すれば、エラーの内容がなんとなく分かります。

sqlerrm

エラーの内容を、oracleが用意したメッセージで返してくれます。
今回は「divisor is equal to zero」と書いてありますので、0で割っているのが原因と分かります。出力されたエラーメッセージがよく分からないときは、エラーメッセージで検索するとヒントが得られるはずです。

例外処理の書き方(詳細)

エラーとなったとき、何も考えずに以下のように書いていました。

    when others then

「others」と書いています。「他のエラーがあったとき」という意味です。
つまり、PL/SQLには必要最低限のエラーハンドリングが用意されています。それ以外の時に「others」を使います。

0割りをハンドリングする

例えば以下のように書きます。

declare
    n_num  number;
begin
    -- 0割り:必ずエラーになる!
    n_num := 1/0;
	-- 数字を出力しようとする
	dbms_output.put_line(n_num);
exception
    when zero_divide then
      dbms_output.put_line('0割りしてます!');
    when others then
      dbms_output.put_line('エラー発生!');
      dbms_output.put_line('sqlcode : ' || sqlcode);
      dbms_output.put_line('sqlerrm : ' || sqlerrm);
end;

9行目〜10行目を追加しました。
「zero_divide」つまり「0割りのときは…」という意味で使える例外処理です。
「zero_divide」の時に用意したメッセージが出力されています。

0割りでない例外を見る(othersに行く場合)

例えば0割りでなく、四則計算に文字があった場合…以下のようになります。

declare
    n_num  number;
begin
    -- 数式に文字列:必ずエラーになる!
    n_num := 1/'a';
    -- 数字を出力しようとする
    dbms_output.put_line(n_num);
exception
    when zero_divide then
      dbms_output.put_line('0割りしてます!');
    when others then
      dbms_output.put_line('エラー発生!');
      dbms_output.put_line('sqlcode : ' || sqlcode);
      dbms_output.put_line('sqlerrm : ' || sqlerrm);
end;

5行目で「n_num := 1/’a’;」としているので、違うエラーとなります。その結果が「others」を通ってエラーの内容を出力しています。

想定していなかった例外が起きた時に「others」を使うようにします。逆に、事前に想定されるエラーは「others」を使わないようにするべきです。

今回の例だと、以下のように書くことが出来ます。

0割りでない例外を見る(想定される例外をハンドリング)

declare
    n_num  number;
begin
    -- 数式に文字列:必ずエラーになる!
    n_num := 1/'a';
    -- 数字を出力しようとする
    dbms_output.put_line(n_num);
exception
    when zero_divide then
      dbms_output.put_line('0割りしてます!');
    when value_error then
      dbms_output.put_line('文字列で計算してます!');
    when others then
      dbms_output.put_line('エラー発生!');
      dbms_output.put_line('sqlcode : ' || sqlcode);
      dbms_output.put_line('sqlerrm : ' || sqlerrm);
end;

PL/SQLで「value_error」という例外が用意されていますので、それを使います。

上記の例では「0割り」と「文字列で計算」の2つを想定して例外処理を作り、他は全くの想定外として「others」でハンドリングしています。

よく使う例外処理

私が個人的に、よく使う例外処理があります。
上では0割りについてやっていきましたが、正直あまり使いません(全く使わない、といったわけではなく)。
私がよく使うのは「テーブルから1件のデータを取得したい時」です。

例えばテーブル「hoge_table」に以下のデータが入っていたとします。

IDNAME
1一郎
2二郎
2次郎
3三郎

こんな感じな例外をよく書きます。

too_many_rows

declare
    v_name HOGE_TABLE.NAME%type;
begin
    select NAME
    into v_name
    from HOGE_TABLE
    where ID = 2
    ;
	dbms_output.put_line('NAME : '  || v_name);
exception
  when no_data_found then
    dbms_output.put_line('データが1件もありません!');
  when too_many_rows then
    dbms_output.put_line('データが複数件あります!');
  when others then
    dbms_output.put_line('エラー');
end;

7行目で「ID = 2」と条件を指定しています。
ID = 2なのは「二郎」「次郎」の2データあるので、エラーとなります。
結果は以下のようになります。

no_data_found

declare
    v_name HOGE_TABLE.NAME%type;
begin
    select NAME
    into v_name
    from HOGE_TABLE
    where ID = 4
    ;
	dbms_output.put_line('NAME : '  || v_name);
exception
  when no_data_found then
    dbms_output.put_line('データが1件もありません!');
  when too_many_rows then
    dbms_output.put_line('データが複数件あります!');
  when others then
    dbms_output.put_line('エラー');
end;

7行目で「ID = 4」と条件を指定しています。
ID = 4の場合はデータが無いので、エラーとなります。
結果は以下のようになります。

その他の例外名

PL/SQLにはその他にも色々な例外が用意されています。
2023年6月時点では、以下の例外名が存在します。

ACCESS_INTO_NULL
CASE_NOT_FOUND
COLLECTION_IS_NULL
CURSOR_ALREADY_OPENED
DUP_VAL_ON_INDEX
INVALID_CURSOR
INVALID_NUMBER
NO_DATA_FOUND
PROGRAM_ERROR
ROWTYPE_MISMATCH
STORAGE_ERROR
SUBSCRIPT_BEYOND_COUNT
SUBSCRIPT_OUTSIDE_LIMIT
SYS_INVALID_ROWID
TOO_MANY_ROWS
VALUE_ERROR
ZERO_DIVIDE

詳しくはoracleが用意しているリファレンスを見てください。

私もこの全てを覚えているわけではありません。プログラムを書く度に、使えそうな例外名が無いか?調べて確認する程度です。

以上ですw

想定していないデータがあったので、処理が落ちました。
というのは、ちょくちょくある話です。なのですが、だからと言って「処理が落ちても仕方ない」とはなりません。

  • なぜ処理が落ちたのか?
  • どこに原因があるのか?

を探るには、それなりの情報をログなどに出力しておく必要があります。そのために、今回やった例外処理をうまく活用してほしい、と思います。

PL/SQL

Posted by kiri