ヘッダーをスキップ
Oracle Database PL/SQL言語リファレンス
11g リリース1(11.1)
E05670-03
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

11 PL/SQLエラーの処理

PL/SQLランタイム・エラーは、設計の失敗、コーディングの間違い、ハードウェアの障害など、多くの原因で発生する可能性があります。 発生する可能性があるすべてのエラーを予想することはできませんが、エラーが発生してもプログラムで処理を続行できるように例外ハンドラをコーディングできます。

ここでのトピック:

PL/SQLのランタイム・エラー処理の概要

PL/SQLでは、エラー条件を例外と呼びます。 例外には、(実行時システムによって)内部的に定義された例外と、ユーザーが定義した例外があります。 内部的に定義された例外には、ORA-22056(値stringが0(ゼロ)で除算されています。)やORA-27102(メモリーが不足しています。)などがあります。 内部的に定義された例外には、ZERO_DIVIDESTORAGE_ERRORといった事前定義の名前を持つものもあります。 それ以外の内部例外にも名前を付けることができます。

PL/SQLブロック、サブプログラムまたはパッケージの宣言部で、ユーザー独自の例外を定義できます。 たとえば、残高がマイナスになっている銀行口座にフラグを付けるために、insufficient_fundsという名前の例外を定義できます。 ユーザー定義の例外には、名前を付ける必要があります。

エラーが発生すると例外が呼び出されます。 つまり、通常の実行は中止され、PL/SQLブロックまたはサブプログラムの例外処理部に制御が移ります。 内部例外は実行時システムによって暗黙的(自動的)に呼び出されます。 ユーザー定義の例外は、RAISE文、またはDBMS_STANDARD.RAISE_APPLICATION_ERRORプロシージャの起動によって明示的に呼び出す必要があります。

呼び出された例外を処理するには、例外ハンドラと呼ばれる独立したルーチンを作成します。 例外ハンドラが実行されると、現在のブロックの実行を中止し、外側のブロックの次の文から再開します。 外側にブロックがない場合は、制御はホスト環境に戻ります。 BULK COLLECTを使用する場合のエラーの管理の詳細は、「FORALL例外の処理(%BULK_EXCEPTIONS属性)」を参照してください。

例11-1では、企業の株価収益率を計算しています。 企業の収益が0(ゼロ)の場合に除算演算を実行すると、事前定義の例外ZERO_DIVIDEが呼び出され、ブロックの実行が中断し、制御が例外ハンドラに移ります。 ブロックで特に名前を指定していないすべての例外は、オプションのOTHERSハンドラで処理します。

例11-1 ランタイム・エラー処理

DECLARE
   stock_price NUMBER := 9.73;
   net_earnings NUMBER := 0;
   pe_ratio NUMBER;
BEGIN
-- Calculation might cause division-by-zero error.
   pe_ratio := stock_price / net_earnings;
   DBMS_OUTPUT.PUT_LINE('Price/earnings ratio = ' || pe_ratio);
EXCEPTION  -- exception handlers begin
-- Only one of the WHEN blocks is executed.
   WHEN ZERO_DIVIDE THEN  -- handles 'division by zero' error
      DBMS_OUTPUT.PUT_LINE('Company must have had zero earnings.');
      pe_ratio := NULL;
   WHEN OTHERS THEN  -- handles all other errors
      DBMS_OUTPUT.PUT_LINE('Some other kind of error occurred.');
      pe_ratio := NULL;
END;  -- exception handlers and block end here
/

上の例は例外処理を示しています。 次の例に示すように、詳細なエラー・チェックを行って分母が0(ゼロ)の場合は答えをNULLに置き換えるようにすると、例外を完全に回避できます。

DECLARE
   stock_price NUMBER := 9.73;
   net_earnings NUMBER := 0;
   pe_ratio NUMBER;
BEGIN
   pe_ratio :=
      CASE net_earnings
         WHEN 0 THEN NULL
         ELSE stock_price / net_earnings
      end;
END;
/

PL/SQLエラーと例外の回避および処理のガイドライン

データベース・プログラムには信頼性が重要であるため、エラー・チェックと例外処理の両方を使用して、プログラムがすべての可能性を処理できるようにします。

PL/SQL例外のメリット

エラー処理に例外を使用すると、次のようなメリットがあります。 例外を使用すると、例11-2に示すように、1つの例外ハンドラで多くの文の潜在的なエラーを確実に処理できます。

例11-2 1つの例外ハンドラを使用した複数エラーの処理

DECLARE
   emp_column       VARCHAR2(30) := 'last_name';
   table_name       VARCHAR2(30) := 'emp';
   temp_var         VARCHAR2(30);
BEGIN
  temp_var := emp_column;
  SELECT COLUMN_NAME INTO temp_var FROM USER_TAB_COLS
    WHERE TABLE_NAME = 'EMPLOYEES'
    AND COLUMN_NAME = UPPER(emp_column);
-- processing here
  temp_var := table_name;
  SELECT OBJECT_NAME INTO temp_var FROM USER_OBJECTS
    WHERE OBJECT_NAME = UPPER(table_name)
    AND OBJECT_TYPE = 'TABLE';
-- processing here
EXCEPTION
  -- Catches all 'no data found' errors
   WHEN NO_DATA_FOUND THEN
     DBMS_OUTPUT.PUT_LINE
       ('No Data found for SELECT on ' || temp_var);
END;
/

エラーが発生する可能性のある場所で逐一エラーをチェックするのではなく、例外ハンドラをPL/SQLブロックに追加します。 これによって、そのブロック(サブブロックを含む)で例外が呼び出されたときにその例外が処理されるようになります。

エラーの発生がすぐにはわからなかったり、不正なデータで計算を実行した場合に実行後までエラーを検出できないこともあります。 この場合も、1つの例外ハンドラで、すべての0(ゼロ)による除算エラー、不正な配列の添字などをトラップできます。

特定の場所でエラーをチェックする必要がある場合は、独立した例外ハンドラを持つ独立したBEGIN-ENDブロックで単一の文または文のグループを囲みます。 一般的なチェックを行うことも、厳密にチェックを行うこともできます。

エラー処理ルーチンを分離することで、プログラムの残りの部分がわかりやすく、理解しやすくなります。

事前定義のPL/SQL例外

PL/SQLプログラムがデータベースの規則に違反するか、またはそのシステムの制限を超えると、自動的に内部例外が呼び出されます。 PL/SQLでは、いくつかの一般的なORA-nエラーが例外として事前定義されています。 たとえば、SELECT INTO文が行を戻さなかった場合は、事前定義の例外NO_DATA_FOUNDがPL/SQLによって呼び出されます。

EXCEPTION_INITプラグマを使用して、例外名を予想される他のOracle Databaseエラー・コードに関連付けることができます。 想定外のOracle Databaseエラーを処理するには、OTHERSハンドラを使用します。 このハンドラ内では、SQLCODEファンクションとSQLERRMファンクションを起動して、Oracle Databaseエラー・コードとメッセージ・テキストを戻すことができます。 エラー・コードがわかれば、EXCEPTION_INITプラグマでエラー・コードを使用して、そのエラー専用のハンドラを作成できます。

PL/SQLでは、STANDARDパッケージの中で、事前定義の例外がグローバルに宣言されています。 ユーザーが宣言する必要はありません。 表11-1に示す名前を使用すると、事前定義の例外を処理するハンドラを作成できます。

表11-1 事前定義のPL/SQL例外

例外名 ORAエラー SQLCODE 呼び出される場合

ACCESS_INTO_NULL

06530

-6530

プログラムが未初期化オブジェクトの属性に値を代入しようとしたとき。

CASE_NOT_FOUND

06592

-6592

CASE文のWHEN句で何も選択されておらず、ELSE句もない場合。

COLLECTION_IS_NULL

06531

-6531

プログラムがEXISTS以外のコレクション・メソッドを未初期化のネストした表またはVARRAYに適用しようとしたか、または未初期化のネストした表またはVARRAYの要素に値を代入しようとしたとき。

CURSOR_ALREADY_OPEN

06511

-6511

すでにオープンされているカーソルをオープンしようとしたとき。 カーソルをオープンするには、一度クローズする必要があります。 カーソルFORループは、参照するカーソルを自動的にオープンします。このため、ループの内側ではカーソルをオープンできません。

DUP_VAL_ON_INDEX

00001

-1

UNIQUE索引によって制約されている列に、重複した値を格納しようとしたとき。

INVALID_CURSOR

01001

-1001

オープンされていないカーソルをクローズするなど、不正なカーソル操作を実行しようとしたとき。

INVALID_NUMBER

01722

-1722

SQL文の中で、文字列が正しい数値を表していなかったために、文字列から数値への変換が失敗したとき。 (プロシージャ文では、VALUE_ERRORが呼び出されます。) この例外は、バルクFETCH文のLIMIT句の式が正数に評価されない場合にも呼び出されます。

LOGIN_DENIED

01017

-1017

不正なユーザー名またはパスワードでデータベースにログオンしようとした場合。

NO_DATA_FOUND

01403

+100

SELECT INTO文が行を戻さなかったとき、ネストした表で削除された要素を参照したとき、または索引付き表で未初期化の要素を参照したとき。

この例外は、いくつかのSQLファンクションで終了したことを通知するために内部的に使用されているため、問合せの一部として起動されるファンクション内部で呼び出された場合は、この例外が伝播されても信頼しないでください。

NOT_LOGGED_ON

01012

-1012

データベースに接続していないプログラムが、データベース・コールを発行した場合。

PROGRAM_ERROR

06501

-6501

PL/SQLに内部的な問題が発生した場合。

ROWTYPE_MISMATCH

06504

-6504

1つの代入の中に含まれるホスト・カーソル変数とPL/SQLカーソル変数の戻り型に互換性がない場合。 オープン・ホスト・カーソル変数をストアド・サブプログラムに渡すとき、実パラメータの戻り型と仮パラメータの戻り型には互換性が必要です。

SELF_IS_NULL

30625

-30625

プログラムがMEMBERメソッドの起動を試行したが、オブジェクト型のインスタンスが初期化されなかった場合。 つまり、組込みパラメータSELFがオブジェクトを指している場合です。このパラメータは、常にMEMBERメソッドに最初に渡されるパラメータです。

STORAGE_ERROR

06500

-6500

PL/SQLのメモリーが足りなくなった場合、またはメモリーが破損された場合。

SUBSCRIPT_BEYOND_COUNT

06533

-6533

コレクション中の要素数より大きい索引番号を使用してネストした表またはVARRAYの要素を参照した場合。

SUBSCRIPT_OUTSIDE_LIMIT

06532

-6532

有効範囲外(たとえば-1)の索引番号を使用してネストした表またはVARRAYの要素を参照した場合。

SYS_INVALID_ROWID

01410

-1410

文字列が正しいROWIDを表していなかったために、文字列からユニバーサルROWIDへの変換が失敗した場合。

TIMEOUT_ON_RESOURCE

00051

-51

データベースがリソースを求めて待機しているときにタイムアウトが発生した場合。

TOO_MANY_ROWS

01422

-1422

SELECT INTO文が複数の行を戻した場合。

VALUE_ERROR

06502

-6502

算術エラー、変換エラー、切捨てエラー、またはサイズ制約エラーが発生した場合。 たとえば、列値を選択し文字変数に代入するときに、その値が変数の宣言された長さよりも長い場合、PL/SQLはその代入を停止してVALUE_ERRORを呼び出します。 プロシージャ文では、文字列から数値への変換が失敗した場合にVALUE_ERRORが呼び出されます。 (SQL文では、INVALID_NUMBERが呼び出されます。)

ZERO_DIVIDE

01476

-1476

数値を0(ゼロ)で割ろうとしたとき。


独自のPL/SQL例外の定義

PL/SQLではユーザー独自の例外を定義できます。 事前定義の例外とは異なり、ユーザー定義の例外は宣言してから、RAISE文またはDBMS_STANDARD.RAISE_APPLICATION_ERRORプロシージャを使用して明示的に呼び出す必要があります。 後者を使用すると、ユーザー定義の例外にエラー・メッセージを関連付けることができます。

ここでのトピック:

PL/SQL例外の宣言

例外はPL/SQLブロック、サブプログラムまたはパッケージの宣言部でしか宣言できません。 例外は、例外の名前にキーワードEXCEPTIONを付けて宣言します。 次の例では、past_dueという名前の例外を宣言しています。

DECLARE
   past_due EXCEPTION;

例外の宣言と変数の宣言は似ています。 ただし、例外はデータ項目ではなく、エラー条件であることを覚えておいてください。 変数とは異なり、例外は代入文やSQL文では使用できません。 ただし、変数と例外の有効範囲規則は同じです。

PL/SQL例外の有効範囲規則

同じブロックでは1つの例外を2回宣言できません。 ただし、2つの異なるブロックであれば、同じ例外を宣言できます。

ブロックの中で宣言された例外は、そのブロックに対してローカルで、そのブロックのすべてのサブブロックに対してグローバルであるとみなされます。 ブロックはローカルまたはグローバルな例外しか参照できないため、サブブロックで宣言された例外を外側のブロックから参照できません。

サブブロックでグローバルな例外を再宣言すると、ローカルの宣言が優先されます。 このため、サブブロックからはグローバルな例外を参照できません。ただし、グローバルな例外がラベル付きのブロックで宣言されている場合は、例外の名前をブロック・ラベルblock_label.exception_nameで修飾すると、グローバルな例外を参照できます。

例11-3に、有効範囲規則を示します。

例11-3 PL/SQL例外の有効範囲

DECLARE
   past_due EXCEPTION;
   acct_num NUMBER;
BEGIN
   DECLARE  ---------- sub-block begins
      past_due EXCEPTION;  -- this declaration prevails
      acct_num NUMBER;
     due_date DATE := SYSDATE - 1;
     todays_date DATE := SYSDATE;
   BEGIN
      IF due_date < todays_date THEN
         RAISE past_due;  -- this is not handled
      END IF;
   END;  ------------- sub-block ends
EXCEPTION
  -- Does not handle raised exception
  WHEN past_due THEN
    DBMS_OUTPUT.PUT_LINE
      ('Handling PAST_DUE exception.');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE
      ('Could not recognize PAST_DUE_EXCEPTION in this scope.');
END;
/

サブブロックのpast_dueの宣言が優先されるため、外側のブロックは呼び出された例外を処理しません。 この2つの例外は同じpast_dueという名前を持っていますが、同じ名前の2つのacct_num変数が別の変数であるのと同様に、別々の例外です。 したがって、RAISE文とWHEN句は別々の例外を参照しています。 呼び出された例外を外側のブロックで処理するには、サブブロックから宣言を削除するか、OTHERSハンドラを定義する必要があります。

PL/SQL例外と番号の関連付け(EXCEPTION_INITプラグマ)

事前定義の名前がないエラー状態(通常はORA-nメッセージ)を処理するには、OTHERSハンドラまたはEXCEPTION_INITプラグマを使用する必要があります。 プラグマは、実行時ではなくコンパイル時に処理されるコンパイラ・ディレクティブです。

PL/SQLでは、EXCEPTION_INITプラグマでコンパイラに指示して、例外名とOracle Databaseエラー番号を関連付けます。 この関連付けによって、内部例外を名前で参照し、専用のハンドラを作成できます。 エラー・スタックまたは一連のエラー・メッセージを確認する場合、一番上のエラーがトラップおよび処理できるエラーです。

EXCEPTION_INITプラグマは、PL/SQLブロック、サブプログラムまたはパッケージの宣言部で、次の構文を使用して指定します。

PRAGMA EXCEPTION_INIT(exception_name, -Oracle_error_number);

exception_nameは事前に宣言されている例外の名前で、番号はORA-nエラーに対応する負の値です。 例11-4に示すとおり、プラグマは、同じ宣言部内の例外宣言より後に表示されます。

例11-4 PRAGMA EXCEPTION_INITの使用

DECLARE
   deadlock_detected EXCEPTION;
   PRAGMA EXCEPTION_INIT(deadlock_detected, -60);
BEGIN
   NULL; -- Some operation that causes an ORA-00060 error
EXCEPTION
   WHEN deadlock_detected THEN
      NULL; -- handle the error
END;
/

独自のエラー・メッセージの定義(RAISE_APPLICATION_ERRORプロシージャ)

RAISE_APPLICATION_ERRORプロシージャを使用すると、ストアド・サブプログラムからユーザー定義のORA-nエラー・メッセージを発行できます。 これを利用すると、アプリケーションに対してエラーを報告し、処理されない例外が戻されるのを回避できます。

RAISE_APPLICATION_ERRORを起動するには、次の構文を使用します。

raise_application_error(
      error_number, message[, {TRUE | FALSE}]);

error_numberは-20000から-20999の範囲内の負の整数で、messageは長さが2048バイト以内の文字列です。オプションの3番目のパラメータがTRUEの場合、エラーは、以前のエラーのスタックに配置されます。 そのパラメータがFALSE(デフォルト)の場合、エラーは以前のエラーをすべて置換します。 RAISE_APPLICATION_ERRORはパッケージDBMS_STANDARDの一部で、パッケージSTANDARDと同様に、参照する際に名前を修飾する必要はありません。

アプリケーションは、実行中のストアド・サブプログラム(またはメソッド)からのみraise_application_errorを起動できます。 raise_application_errorが起動されると、サブプログラムは終了し、ユーザー定義のエラー番号とメッセージがアプリケーションに戻されます。 エラー番号とメッセージは、Oracle Databaseエラーのようにトラップさせることができます。

例11-5では、選択したエラー条件が発生した場合に(この例では、現行スキーマの所有する表の数が1000より少ない場合に)RAISE_APPLICATION_ERRORを起動しています。

例11-5 RAISE_APPLICATION_ERRORを使用したアプリケーション・エラーの呼出し

DECLARE
   num_tables NUMBER;
BEGIN
   SELECT COUNT(*) INTO num_tables FROM USER_TABLES;
   IF num_tables < 1000 THEN
      /* Issue your own error code (ORA-20101)
         with your own error message. You need not
          qualify RAISE_APPLICATION_ERROR with
          DBMS_STANDARD */
      RAISE_APPLICATION_ERROR
        (-20101, 'Expecting at least 1000 tables');
   ELSE
      -- Do rest of processing (for nonerror case)
      NULL;
   END IF;
END;
/

起動元のアプリケーションは、PL/SQL例外を受け取り、エラー・レポート・ファンクションSQLCODEおよびSQLERRMを使用してOTHERSハンドラで処理できます。 また、EXCEPTION_INITプラグマを使用すると、次のPro*C例が示すようにRAISE_APPLICATION_ERRORが戻す特定のエラー番号をアプリケーション独自の例外にマップできます。

EXEC SQL EXECUTE
  /* Execute embedded PL/SQL block using host
     variables v_emp_id and v_amount, which were
     assigned values in the host environment. */
DECLARE
  null_salary EXCEPTION;
  /* Map error number returned by RAISE_APPLICATION_ERROR
     to user-defined exception. */
  PRAGMA EXCEPTION_INIT(null_salary, -20101);
  BEGIN
    raise_salary(:v_emp_id, :v_amount);
  EXCEPTION
    WHEN null_salary THEN
      INSERT INTO emp_audit VALUES (:v_emp_id, ...);
  END;
END-EXEC;

この方法を使用すると、起動元のアプリケーションは、エラーが発生している状態を特定の例外ハンドラで処理できます。

事前定義の例外の再宣言

PL/SQLは、事前定義の例外をパッケージSTANDARDでグローバルに宣言しているため、ユーザーが宣言する必要はありません。 事前定義の例外を再宣言すると、ローカルな宣言がグローバルな宣言をオーバーライドするため、エラーが発生しやすくなります。 たとえば、invalid_numberという名前の例外を宣言し、PL/SQLによって事前定義の例外INVALID_NUMBERが内部的に呼び出された場合、INVALID_NUMBER用に作成されたハンドラは内部例外を捕捉できません。 この場合は、ドット表記法を使用して、次のように事前定義の例外を指定する必要があります。

EXCEPTION
  WHEN invalid_number OR STANDARD.INVALID_NUMBER THEN
    -- handle the error
END;

PL/SQL例外の呼出し

内部例外は実行時システムによって暗黙的に呼び出されます。これは、EXCEPTION_INITを使用してOracle Databaseエラー番号に関連付けたユーザー定義の例外の場合も同じです。 その他のユーザー定義の例外は、RAISE文、またはDBMS_STANDARD.RAISE_APPLICATION_ERRORプロシージャの起動によって明示的に呼び出す必要があります。

エラーが原因で処理の完了が望ましくない場合または不可能な場合にのみ、PL/SQLブロックまたはサブプログラムで例外を呼び出します。 指定した例外は、その例外の有効範囲内であれば任意の場所で明示的に呼び出すことができます。 例11-6では、PL/SQLブロックをout_of_stockという名前のユーザー定義の例外に変更しています。

例11-6 ユーザー定義の例外を呼び出すためのRAISEの使用

DECLARE
   out_of_stock   EXCEPTION;
   number_on_hand NUMBER := 0;
BEGIN
   IF number_on_hand < 1 THEN
      RAISE out_of_stock; -- raise an exception that you defined
   END IF;
EXCEPTION
   WHEN out_of_stock THEN
      -- handle the error
      DBMS_OUTPUT.PUT_LINE('Encountered out-of-stock error.');
END;
/

事前定義の例外を明示的に呼び出すこともできます。 これを利用すると、事前定義の例外のために書かれた例外ハンドラで、それ以外のエラーを処理させることができます。例11-7に例を示します。

例11-7 事前定義の例外を呼び出すためのRAISEの使用

DECLARE
   acct_type INTEGER := 7;
BEGIN
   IF acct_type NOT IN (1, 2, 3) THEN
      RAISE INVALID_NUMBER;  -- raise predefined exception
   END IF;
EXCEPTION
   WHEN INVALID_NUMBER THEN
      DBMS_OUTPUT.PUT_LINE
        ('HANDLING INVALID INPUT BY ROLLING BACK.');
      ROLLBACK;
END;
/

PL/SQL例外の伝播

例外が呼び出されたときに、PL/SQLがその例外のハンドラをカレント・ブロックまたはサブプログラムで発見できない場合、例外は伝播します。 つまり、例外は外側のブロックで再生され、ハンドラが見つかるまで、または検索するブロックがなくなるまで、1つずつ外側のブロックに進んでいきます。 ハンドラが見つからなかった場合、PL/SQLはホスト環境に「未処理例外」エラーを戻します。

例外はデータベース・リンクを介して行われるリモート・サブプログラム・コールには伝播しません。 そのため、PL/SQLブロックは、リモート・サブプログラムによって呼び出された例外を処理できません。 回避策の詳細は、「独自のエラー・メッセージの定義(RAISE_APPLICATION_ERRORプロシージャ)」を参照してください。

図11-1図11-2および図11-3に、基本的な伝播規則を示します。

図11-1 伝播規則: 例1

伝播規則: 例1
「図11-1 伝播規則: 例1」の説明

図11-2 伝播規則: 例2

伝播規則: 例2
「図11-2 伝播規則: 例2」の説明

図11-3 伝播規則: 例3

伝播規則: 例3
「図11-3 伝播規則: 例3」の説明

例11-8に示すように、例外は有効範囲を超えて、つまり宣言されたブロックを超えたところまで伝播することがあります。

例11-8 例外の有効範囲

BEGIN
   DECLARE  ---------- sub-block begins
     past_due EXCEPTION;
     due_date DATE := trunc(SYSDATE) - 1;
     todays_date DATE := trunc(SYSDATE);
   BEGIN
     IF due_date < todays_date THEN
        RAISE past_due;
     END IF;
   END;  ------------- sub-block ends
EXCEPTION
   WHEN OTHERS THEN
      ROLLBACK;
END;
/

例外past_dueが宣言されたブロックに例外ハンドラが存在しないため、例外は外側のブロックに伝播します。 ただし、宣言された有効範囲がすでに存在しないため、外側のブロックは名前PAST_DUEを参照できません。 一度例外名が失われると、この例外を捕捉できるのはOTHERSハンドラにかぎられます。 ユーザー定義の例外のハンドラがない場合、起動元のアプリケーションはORA-06510を受け取ります。

PL/SQL例外の再呼出し

例外の再呼出しとは、ローカルに処理した例外を、外側のブロックに渡すことです。 たとえば、現在のブロックでトランザクションをロールバックし、エラーを外側のブロックの中でログする場合があります。

例外を再度呼び出すには、RAISE文で例外名を省略します。例外名の省略は、例11-9に示すように、例外ハンドラ内でのみ許されます。

例11-9 PL/SQL例外の再呼出し

DECLARE
  salary_too_high  EXCEPTION;
  current_salary NUMBER := 20000;
  max_salary NUMBER := 10000;
  erroneous_salary NUMBER;
BEGIN
  BEGIN  ---------- sub-block begins
    IF current_salary > max_salary THEN
      RAISE salary_too_high;  -- raise the exception
    END IF;
  EXCEPTION
    WHEN salary_too_high THEN
      -- first step in handling the error
      DBMS_OUTPUT.PUT_LINE('Salary ' || erroneous_salary ||
      ' is out of range.');
      DBMS_OUTPUT.PUT_LINE
        ('Maximum salary is ' || max_salary || '.');
      RAISE;  -- reraise the current exception
  END;  ------------ sub-block ends
EXCEPTION
  WHEN salary_too_high THEN
    -- handle the error more thoroughly
    erroneous_salary := current_salary;
    current_salary := max_salary;
    DBMS_OUTPUT.PUT_LINE('Revising salary from ' || erroneous_salary ||
       ' to ' || current_salary || '.');
END;
/

呼び出されたPL/SQL例外の処理

例外が呼び出されると、PL/SQLブロックまたはサブプログラムの通常の実行は中止され、制御が例外処理部に移ります。例外処理部の書式を次に示します。

EXCEPTION
  WHEN exception1 THEN -- handler for exception1
    sequence_of_statements1
  WHEN exception2 THEN -- another handler for exception2
    sequence_of_statements2
  ...
  WHEN OTHERS THEN -- optional handler for all other errors
    sequence_of_statements3
END;

呼び出された例外を処理するには、例外ハンドラを作成します。 個々のハンドラは、例外を指定するWHEN句に、その例外が呼び出されたときに実行される一連の文を続けたものです。 これらの文を最後に、ブロックまたはサブプログラムの実行は終わります。制御は例外が呼び出された箇所に戻りません。 つまり、処理を中止した位置からは再開できません。

オプションのOTHERS例外ハンドラは、必ずブロックまたはサブプログラムの最後のハンドラにする必要があります。OTHERS例外ハンドラは、名前を付けなかったすべての例外のハンドラとして使用されます。 このため、ブロックまたはサブプログラムが持てるOTHERSハンドラは1つのみです。 OTHERSハンドラを使用すると、すべての例外が処理されます。

2つ以上の例外で、同じ一連の文を実行する場合は、WHEN句の中でキーワードORで区切って例外名を並べてください。次に例を示します。

EXCEPTION
  WHEN over_limit OR under_limit OR VALUE_ERROR THEN
    -- handle the error

リスト中の例外のいずれかが呼び出されると、それに関連する一連の文が実行されます。 キーワードOTHERSは例外名のリストの中では使用できず、単独で使用する必要があります。 例外ハンドラの数に制限はなく、また、個々のハンドラは例外のリストを一連の文に関連付けることができます。 ただし、例外名はPL/SQLブロックまたはサブプログラムの例外処理部で一度しか使用できません。

PL/SQL変数の通常の有効範囲規則が適用されるため、例外ハンドラの中ではローカル変数とグローバル変数が参照できます。 ただし、カーソルFORループの内側で例外が呼び出されると、ハンドラに制御が移る前にカーソルは暗黙的にクローズされます。 したがって、ハンドラでは明示カーソルの属性の値を参照できません。

ここでのトピック:

宣言の中で呼び出された例外

宣言の中でも、初期化の式が間違っていると例外が呼び出される場合があります。 たとえば、例11-10の宣言では、定数credit_limitが999より大きい数値を格納できないため、例外が呼び出されます。

例11-10 宣言の中での例外の呼出し

DECLARE
  -- Raises an error:
  credit_limit CONSTANT NUMBER(3) := 5000;
BEGIN
  NULL;
EXCEPTION
  WHEN OTHERS THEN
    -- Cannot catch exception. This handler is never invoked.
    DBMS_OUTPUT.PUT_LINE
      ('Can''t handle an exception in a declaration.');
END;
/

宣言の中で呼び出された例外は、ただちに外側のブロックに伝播するため、現在のブロックの中のハンドラは呼び出された例外を捕捉できません。

例外ハンドラの中で呼び出された例外の処理

例外ハンドラの中で例外が発生した場合、そのハンドラで例外を捕捉することはできません。 このため、ハンドラの内側で呼び出された例外はただちに外側のブロックに伝播し、そこで再び呼び出されて、その新しい例外のハンドラが検索されます。 それ以降の例外の伝播は通常どおりに行われます。 次に例を示します。

EXCEPTION
  WHEN INVALID_NUMBER THEN
    INSERT INTO ... -- might raise DUP_VAL_ON_INDEX
  WHEN DUP_VAL_ON_INDEX THEN -- cannot catch exception
END;

例外ハンドラへの分岐と例外ハンドラからの分岐

GOTO文では、例外ハンドラから外側のブロックに分岐できます。

ただし、GOTO文では例外ハンドラへは分岐できません。また、例外ハンドラからカレント・ブロックに分岐することもできません。

エラー・コードとエラー・メッセージの取得

例外ハンドラでは、組込みファンクションSQLCODEを使用してエラー・コードを取得できます。 関連するエラー・メッセージを取得するには、パッケージ・ファンクションDBMS_UTILTY.FORMAT_ERROR_STACKまたは組込みファンクションSQLERRMのいずれかを使用できます。

SQLERRMは最大512バイトを戻します。これは、Oracle Databaseエラー・メッセージ(エラー・コード、ネストされたメッセージおよび表や列の名前などのメッセージの挿入部分を含む)の最大長です。 DBMS_UTILTY.FORMAT_ERROR_STACKは最大2000バイトの完全なエラー・スタックを戻します。 このため、FORALL文をSAVE EXCEPTIONS句とともに使用する場合以外は、SQLERRMよりDBMS_UTILTY.FORMAT_ERROR_STACKを使用することをお薦めします。 例12-9に示すように、SAVE EXCEPTIONSに対してはSQLERRMを使用してください。


参照:

  • このファンクションの構文およびセマンティクスは、「SQLCODEファンクション」を参照してください。

  • このファンクションの構文およびセマンティクスは、「SQLERRMファンクション」を参照してください。

  • FORALL文をSAVE EXCEPTIONS句とともに使用する方法の詳細は、「FORALL例外の処理(%BULK_EXCEPTIONS属性)」を参照してください。

  • DBMS_UTILTY.FORMAT_ERROR_STACKの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。


SQL文では、SQLCODEまたはSQLERRMを起動できません。 これらの値をSQL文で使用するには、例11-11に示すように、これらの値をまずローカル変数に代入します。

例11-11 SQLCODEおよびSQLERRMの表示

SQL> CREATE TABLE errors (
  2    code      NUMBER,
  3    message   VARCHAR2(64),
  4    happened  TIMESTAMP);

Table created.

SQL>
SQL> DECLARE
  2    name    EMPLOYEES.LAST_NAME%TYPE;
  3    v_code  NUMBER;
  4    v_errm  VARCHAR2(64);
  5  BEGIN
  6    SELECT last_name INTO name
  7      FROM EMPLOYEES
  8        WHERE EMPLOYEE_ID = -1;
  9    EXCEPTION
 10      WHEN OTHERS THEN
 11        v_code := SQLCODE;
 12        v_errm := SUBSTR(SQLERRM, 1, 64);
 13        DBMS_OUTPUT.PUT_LINE
 14          ('Error code ' || v_code || ': ' || v_errm);
 15
 16        /* Invoke another procedure,
 17           declared with PRAGMA AUTONOMOUS_TRANSACTION,
 18           to insert information about errors. */
 19
 20           INSERT INTO errors
 21             VALUES (v_code, v_errm, SYSTIMESTAMP);
 22  END;
 23  /
Error code 100: ORA-01403: no data found

PL/SQL procedure successfully completed.

SQL>

未処理例外の捕捉

発生した例外に対応するハンドラが発見できない場合、PL/SQLはホスト環境に「例外は処理されませんでした。」というエラーを戻します。その結果はホスト環境によって異なります。 たとえば、Oracleプリコンパイラ環境では、失敗したSQL文またはPL/SQLブロックがデータベースに加えた変更は、すべてロールバックされます。

未処理例外はサブプログラムにも影響を与えます。 サブプログラムの実行が正常終了すると、PL/SQLはOUTパラメータに値を代入します。 ただし、未処理例外が発生して実行が終了すると、PL/SQLはOUTパラメータに値を代入しません(NOCOPYパラメータではない場合)。 また、ストアド・サブプログラムで未処理例外が発生して実行が失敗した場合、PL/SQLはそのサブプログラムが実行したデータベース処理をロールバックしません。

すべてのPL/SQLプログラムの最も上のレベルにOTHERSハンドラを置くと、未処理例外の発生を避けることができます。

PL/SQLエラーの処理のガイドライン

ここでのトピック:

例外が呼び出された後に実行を続ける方法

例外ハンドラを使用すると、ブロックを終了する前に致命的なエラーからリカバリできます。 ただしハンドラの実行が終了すると、ブロックの実行も終了します。 例外ハンドラから現在のブロックに戻ることはできません。 次の例で、SELECT INTO文がZERO_DIVIDEを呼び出した場合、INSERT文の実行は再開できません。

CREATE TABLE employees_temp AS
  SELECT employee_id, salary,
    commission_pct FROM employees;

DECLARE
  sal_calc NUMBER(8,2);
BEGIN
  INSERT INTO employees_temp VALUES (301, 2500, 0);
  SELECT salary / commission_pct INTO sal_calc
    FROM employees_temp
    WHERE employee_id = 301;
  INSERT INTO employees_temp VALUES (302, sal_calc/100, .1);
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    NULL;
END;
/

ある文の例外を処理してから、次の文に進むことができます。 この場合、独立した例外ハンドラを持つ独立したサブブロックに文を入れます。 サブブロックでエラーが発生すると、ローカルなハンドラが例外を処理します。 例11-12に示すように、サブブロックが終了すると、外側のブロックは、そのサブブロックの終了位置から実行を継続します。

例11-12 例外の後に実行を続ける方法

DECLARE
  sal_calc NUMBER(8,2);
BEGIN
  INSERT INTO employees_temp VALUES (303, 2500, 0);
  BEGIN -- sub-block begins
    SELECT salary / commission_pct INTO sal_calc
      FROM employees_temp
      WHERE employee_id = 301;
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        sal_calc := 2500;
  END; -- sub-block ends
  INSERT INTO employees_temp VALUES (304, sal_calc/100, .1);
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    NULL;
END;
/

例11-12では、SELECT INTO文がZERO_DIVIDE例外を呼び出すと、ローカル・ハンドラが例外を捕捉してsal_calcを2500に設定します。ハンドラの実行が終わり、サブブロックが終了すると、実行はINSERT文から続行されます。

また、一部で失敗した可能性がある一連のDML操作を実行し、操作全体が完了した後で例外を処理する方法もあります(「FORALL例外の処理(%BULK_EXCEPTIONS属性)」を参照)。

トランザクションの再試行

例外が呼び出された場合、トランザクションを中止せずに、再試行する場合があります。 その方法は、次のとおりです。

  1. トランザクションをサブブロックに入れます。

  2. そのサブブロックをループの中に入れ、トランザクションが繰り返して実行されるようにします。

  3. トランザクションを開始する前にセーブポイントをマークします。 トランザクションの実行に成功すると、コミットしてループを終了します。 トランザクションの実行に失敗すると制御は例外ハンドラに移り、例外ハンドラはセーブポイントまでロールバックして変更をすべて取り消し、問題点を修正します。

例11-13では、INSERT文で一意の列に重複する値を挿入しようとしたため、例外が呼び出されます。 この例では、一意である必要のある値を変更し、次のループ反復に進んでいます。 INSERT文での挿入が正常に完了すると、ただちにループを終了します。 この方法では、FORループまたはWHILEループを使用して試行の回数を制限します。

例11-13 例外の後のトランザクションの再試行

CREATE TABLE results (res_name VARCHAR(20), res_answer VARCHAR2(3));
CREATE UNIQUE INDEX res_name_ix ON results (res_name);
INSERT INTO results VALUES ('SMYTHE', 'YES');
INSERT INTO results VALUES ('JONES', 'NO');

DECLARE
   name     VARCHAR2(20) := 'SMYTHE';
   answer   VARCHAR2(3) := 'NO';
   suffix   NUMBER := 1;
BEGIN
   FOR i IN 1..5 LOOP  -- try 5 times
      BEGIN  -- sub-block begins
         SAVEPOINT start_transaction;  -- mark a savepoint
         /* Remove rows from a table of survey results. */
         DELETE FROM results WHERE res_answer = 'NO';
         /* Add a survey respondent's name and answers. */
         INSERT INTO results VALUES (name, answer);
 -- raises DUP_VAL_ON_INDEX
 -- if two respondents have the same name
         COMMIT;
         EXIT;
      EXCEPTION
         WHEN DUP_VAL_ON_INDEX THEN
            ROLLBACK TO start_transaction;  -- undo changes
            suffix := suffix + 1;           -- try to fix problem
            name := name || TO_CHAR(suffix);
      END;  -- sub-block ends
   END LOOP;
END;
/

ロケータ変数を使用した例外の位置の識別

INSERT文、DELETE文またはUPDATE文のような一連の文に対して1つの例外ハンドラを使用すると、エラーの原因となった文がわからなくなる場合があります。 失敗した文を知る必要がある場合は、例11-14に示すようにロケータ変数を使用します。

例11-14 ロケータ変数を使用した例外の位置の識別

CREATE OR REPLACE PROCEDURE loc_var AS
   stmt_no NUMBER;
   name    VARCHAR2(100);
BEGIN
   stmt_no := 1;  -- designates 1st SELECT statement
   SELECT table_name INTO name
     FROM user_tables
     WHERE table_name LIKE 'ABC%';
   stmt_no := 2;  -- designates 2nd SELECT statement
   SELECT table_name INTO name
     FROM user_tables
     WHERE table_name LIKE 'XYZ%';
EXCEPTION
   WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE
        ('Table name not found in query ' || stmt_no);
END;
/
CALL loc_var();

PL/SQLのコンパイル時の警告の概要

プログラムの安全性を高め、実行時の問題を避けるには、特定の警告条件のチェックを有効にします。 これらの条件は、エラーが発生したり、サブプログラムがコンパイルできなくなるほど重大ではありません。 未定義の結果を生成したり、パフォーマンス問題を発生させる可能性のあるサブプログラム内の問題を指摘する場合もあります。

PL/SQL警告メッセージを操作するには、PLSQL_WARNINGSコンパイル・パラメータ、DBMS_WARNINGパッケージおよび静的データ・ディクショナリ・ビュー*_PLSQL_OBJECT_SETTINGSを使用します。

ここでのトピック:

PL/SQLの警告カテゴリ

PL/SQL警告メッセージは、表11-2に示すカテゴリに分類されます。 コンパイル時に、同様の警告のグループを抑止または表示することができます。 すべての警告メッセージを参照するには、キーワードAllを使用します。

表11-2 PL/SQLの警告カテゴリ

カテゴリ 説明

SEVERE

条件によって、予期しないアクションまたは誤った結果が発生する可能性があります。

パラメータのエイリアシング問題

PERFORMANCE

条件によって、パフォーマンス問題が発生する可能性があります。

INSERT文でNUMBER列にVARCHAR2値が渡した場合

INFORMATIONAL

条件は、パフォーマンスまたは正確性には影響しませんが、コードのメンテナンス性の向上のために変更する必要がある場合があります。

実行できないコード


特定のメッセージを警告ではなく、エラーとして扱うこともできます。 たとえば、警告メッセージPLW-05003がコードの重大な問題を表している場合、PLSQL_WARNINGS設定に'ERROR:05003'を含めると、条件トリガーは警告メッセージではなくエラー・メッセージ(PLS_05003)になります。 エラー・メッセージが発行されると、コンパイルは失敗します。

PL/SQL警告メッセージの制御

PL/SQLコンパイル時にデータベースで警告メッセージを発行させるには、コンパイル・パラメータPLSQL_WARNINGSを設定します。 警告の全カテゴリ(ALLSEVEREINFORMATIONALPERFORMANCE)および特定のメッセージ番号を有効または無効にできます。また、条件の修正が必要な特定の警告をデータベースでコンパイル・エラーとして扱うこともできます。 PL/SQLコンパイル・パラメータの詳細は、「PL/SQLユニットおよびコンパイル・パラメータ」を参照してください。

例11-15 PL/SQL警告の表示の制御

-- Focus on one aspect:
ALTER SESSION
  SET PLSQL_WARNINGS='ENABLE:PERFORMANCE';
-- Recompile with extra checking:
ALTER PROCEDURE loc_var
  COMPILE PLSQL_WARNINGS='ENABLE:PERFORMANCE'
  REUSE SETTINGS;
-- Turn off warnings:
ALTER SESSION
  SET PLSQL_WARNINGS='DISABLE:ALL';
-- Display 'severe' warnings but not 'performance' warnings,
-- display PLW-06002 warnings to produce errors that halt compilation:
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:SEVERE',
  'DISABLE:PERFORMANCE', 'ERROR:06002';
-- For debugging during development
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL';

PL/SQLサブプログラムのコンパイル中に警告メッセージが発行される場合がありますが、無名ブロックは警告を発行しません。

コンパイル中に生成される警告を確認するには、SQL*PlusのSHOW ERRORS文を使用するか、または静的データ・ディクショナリ・ビューUSER_ERRORSを問い合せます。 PL/SQL警告メッセージには、接頭辞PLWが付きます。

PL/SQLコンパイル・パラメータの一般情報は、「PL/SQLユニットおよびコンパイル・パラメータ」を参照してください。

DBMS_WARNINGパッケージの使用

PL/SQLサブプログラムをコンパイルする開発環境でPL/SQLサブプログラムを作成している場合、DBMS_WARNINGパッケージ内のサブプログラムを起動することで、PL/SQL警告メッセージを制御できます。 複数のネストしたSQL*Plusスクリプトからなる複雑なアプリケーションでは、コンパイル時にこのパッケージを使用することで、サブプログラムごとに異なる警告設定を適用できます。 このパッケージを一度コールしてPLSQL_WARNINGSパラメータの現在の状態を保存し、パラメータを変更して特定のサブプログラム・セットをコンパイルしてから、元のパラメータ値をリストアできます。

例11-16のプロシージャには、削除できる不要なコードがあります。 間違いの可能性や、デバッグ・フラグで意図的に隠された可能性もあります。したがって警告メッセージが必要な場合も必要ない場合もあります。

例11-16 DBMS_WARNINGパッケージを使用した警告の表示

-- When warnings disabled,
-- the following procedure compiles with no warnings
CREATE OR REPLACE PROCEDURE unreachable_code AS
  x CONSTANT BOOLEAN := TRUE;
BEGIN
  IF x THEN
    DBMS_OUTPUT.PUT_LINE('TRUE');
  ELSE
    DBMS_OUTPUT.PUT_LINE('FALSE');
  END IF;
END unreachable_code;
/
-- enable all warning messages for this session
CALL DBMS_WARNING.set_warning_setting_string
  ('ENABLE:ALL' ,'SESSION');
-- Check the current warning setting
SELECT DBMS_WARNING.get_warning_setting_string() FROM DUAL;

-- Recompile procedure
-- and warning about unreachable code displays
ALTER PROCEDURE unreachable_code COMPILE;
SHOW ERRORS;

詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』のDBMS_WARNINGパッケージおよび『Oracle Databaseエラー・メッセージ』のPLW-メッセージに関する項を参照してください。