PL/SQL プログラミング入門(1) > PL/SQL 例外発生時にも戻り値を渡す方法

PL/SQL で例外発生させながら戻り値も渡す

ごく一般的なプロシージャとファンクションにおいては例外を発生させて呼び出し元へ例外を伝播させる場合には 戻り値を渡すことはできない。
ファンクションの場合には RETURN のステートメントが実行する前に例外終了するのであるから当然とも思えるが プロシージャの OUT パラメータの場合でも同じ結果となる。つまり、呼び出し元からみると変数の値が原状復帰されて代入処理が無効化されているようにみえる。(※)

(※) 代入処理が無効化されるだけで変数が NULL に初期化される事とも異なる。代入前の値が残ることになるため同じ変数を複数箇所で流用している場合には変数の初期化処理を怠っていると例外のときにのみ発生する発見されにくいバグの温床になる可能性がある。特にファンクションの場合には注意。

※ 動作確認していないが旧バージョンでは NOCOPY でも原状復帰されるという資料もあるため、書かれているとおりに動かない可能性があるかもしれない。(Oracle 8.1.5 のマニュアルでは特に問題なさそう)
また、NOCOPY は コンパイラヒント であり、IN, OUT, INOUT 宣言(ディレクティブ)とは位置づけが異なりコンパイラが指示に従うとは限らない。
参照: NOCOPY の制限事項

  • プロシージャの例外処理で値が代入されない事の確認
CREATE OR REPLACE PROCEDURE RIVUS.EXCEPTION_TEST(P_DUMMY OUT NUMBER)
IS
   eDummy EXCEPTION;
BEGIN
   P_DUMMY := 1;
   RAISE eDummy;
END;
/
----
SQL> DECLARE
   vNum NUMBER := 9999;
BEGIN
   EXCEPTION_TEST(vNum);
   DBMS_OUTPUT.PUT_LINE('処理の結果=' || vNum);
EXCEPTION
   WHEN OTHERS THEN
   DBMS_OUTPUT.PUT_LINE('例外処理の結果=' || vNum);
END;
/
例外処理の結果=9999    ← 以前の値が残ったままになる(初期化もされない)
 
PL/SQLプロシージャが正常に完了しました。

例外をスロー (RAISE) しながら戻り値を渡すには

では、例外発生と戻り値を両立するにはどうするかというとパラメータのオプションに NOCOPY を指定することで可能となる。

NOCOPY オプションの場合にはパラメータを複製することなく処理される。つまり実体に対して値を代入していることになり、 例外発生によるプロシージャの異常終了時にもパラメータ変数の代入の原状復帰(ロールバック?)も発生することがない。

CREATE OR REPLACE PROCEDURE RIVUS.EXCEPTION_TEST2(P_DUMMY OUT NOCOPY NUMBER)
-- NOCOPY オプションで戻り値の変数エリアに直接値を設定できる ↑↑↑
IS
   eDummy EXCEPTION;
BEGIN
   P_DUMMY := 1;
   RAISE eDummy;
END;
/
----
SQL> DECLARE
   vNum NUMBER := 9999;
BEGIN
   EXCEPTION_TEST2(vNum);
   DBMS_OUTPUT.PUT_LINE('処理の結果=' || vNum);
EXCEPTION
   WHEN OTHERS THEN
   DBMS_OUTPUT.PUT_LINE('例外処理の結果=' || vNum);
END;
/
例外処理の結果=1    ← 例外が発生しても値の代入は取り消されない。
 
PL/SQLプロシージャが正常に完了しました。
  • ファンクションの場合
CREATE OR REPLACE FUNCTION RIVUS.EXCEPTION_TEST3(P_DUMMY OUT NOCOPY NUMBER)
-- NOCOPY オプションで戻り値の変数エリアに直接値を設定できる ↑↑↑
RETURN NUMBER
IS
   eDummy EXCEPTION;
BEGIN
   P_DUMMY := 1;
   RAISE eDummy;
   RETURN 1;       -- ← ここは実行されることはない
END;
/
DECLARE
   vNum1 NUMBER := 9999;
   vNum2 NUMBER := 9999;
BEGIN
   vNum1 := EXCEPTION_TEST3(vNum2);
   DBMS_OUTPUT.PUT_LINE('処理の結果=' || vNum1);
   DBMS_OUTPUT.PUT_LINE('処理の結果=' || vNum2);
EXCEPTION
   WHEN OTHERS THEN
   DBMS_OUTPUT.PUT_LINE('例外処理の結果=' || vNum1);
   DBMS_OUTPUT.PUT_LINE('例外処理の結果=' || vNum2);
END;
/
例外処理の結果=9999
例外処理の結果=1

これ以外にもグローバルなパッケージ変数を用意するなどの実装方法もあるが、 共通モジュールとしてエラーハンドラを作成するつもりがないのであれば 柔軟性に欠けるグローバル変数の公開は控えたほうが良いだろう。

注意 この NOCOPY を使用する方法は NOCOPY が「ある」と「ない」だけの違いで相当な意味の違いになること。 そして例外処理という特殊な時のときに重要な役割をするという点を十分に理解してもらう必要がある。 プログラムには例外処理についてのコメントを残すなどをしておいたほうがよいだろう。 そうでもしないとメンテナンスするメンバには高速化のための NOCOPY か? と思われてしまい本当の用途が伝わらない可能性が高い。



PL/SQLの例外処理に関連する内容

日本オラクル
■ 日本オラクル 株式会社
■ オラクルマスター資格 (オラクルマスターとは
■ Oracle Web セミナー