ヘッダーをスキップ

Oracle Database PL/SQLユーザーズ・ガイドおよびリファレンス
10g リリース2(10.2)

B19257-01
目次
目次
索引
索引

戻る 次へ

10 PL/SQLエラーの処理

ランタイム・エラーは、設計の失敗、コーディングの間違い、ハードウェアの障害など、多くの原因で発生します。発生する可能性があるエラーをすべては予想できませんが、ユーザーのPL/SQLプログラムにとって重大なエラーに対しては、処理を準備しておくことはできます。

プログラミング言語では、通常、エラー・チェックを無効にしていないかぎり、スタック・オーバーフローや0(ゼロ)による除算のようなランタイム・エラーが発生すると、正常な処理が停止され、オペレーティング・システムに制御が戻ります。PL/SQLには「例外処理」というメカニズムがあり、エラーが発生しても処理を続けられるように、プログラムを保護しています。

この章の項目は、次のとおりです。

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

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

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

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

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

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

例10-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例外のメリット

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

例10-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
   WHEN NO_DATA_FOUND THEN  -- catches all 'no data found' errors
     DBMS_OUTPUT.PUT_LINE ('No Data found for SELECT on ' || temp_var);
END;
/

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

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

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

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

事前定義のPL/SQL例外のまとめ

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

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

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

例外  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 

不正なユーザー名またはパスワードでOracleにログオンしようとしたとき。 

NO_DATA_FOUND 

01403 

+100 

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

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

NOT_LOGGED_ON 

01012 

-1012 

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

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 

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

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文を使用して明示的に呼び出す必要があります。

PL/SQL例外の宣言

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

DECLARE
   past_due EXCEPTION;

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

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

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

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

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

block_label.exception_name

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

例10-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
  WHEN past_due THEN  -- does not handle raised exception
    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-メッセージ)を処理するには、OTHERSハンドラまたはEXCEPTION_INITプラグマを使用する必要があります。プラグマは、実行時ではなくコンパイル時に処理されるコンパイラ・ディレクティブです。

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

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

PRAGMA EXCEPTION_INIT(exception_name, -Oracle_error_number);

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

例10-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-エラー・メッセージを発行できます。これを利用すると、アプリケーションに対してエラーを報告し、処理されない例外が戻されるのを回避できます。

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エラーのようにトラップさせることができます。

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

例10-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. 
         Note that you do not need to qualify raise_application_error with 
         DBMS_STANDARD */
      raise_application_error(-20101, 'Expecting at least 1000 tables');
   ELSE
      NULL; -- Do the rest of the processing (for the non-error case).
   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エラー番号に関連付けたユーザー定義の例外の場合も同じです。ただし、それ以外のユーザー定義の例外は、RAISE文で明示的に呼び出す必要があります。

RAISE文を使用した例外の呼出し

PL/SQLブロックおよびサブプログラムから例外を呼び出すのは、エラーが原因で処理の完了が望ましくない場合または不可能な場合のみにする必要があります。指定した例外に対するRAISE文は、その例外の有効範囲の中ならば任意の場所に置くことができます。例10-6では、PL/SQLブロックでout_of_stockという名前のユーザー定義の例外を指定しています。

例10-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 we defined
   END IF;
EXCEPTION
   WHEN out_of_stock THEN
      -- handle the error
      DBMS_OUTPUT.PUT_LINE('Encountered out-of-stock error.');
END;
/

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

例10-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プロシージャ」を参照してください。

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

図10-1    伝播規則: 例1


画像の説明

図10-2    伝播規則: 例2


画像の説明

図10-3    伝播規則: 例3


画像の説明

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

例10-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: unhandled user-defined exception

PL/SQL例外の再呼出し

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

例外を再度呼び出すには、RAISE文で例外名を省略します。例外名の省略は、例外ハンドラの中でのみ許されます。

例10-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ループの内側で例外が呼び出されると、ハンドラに制御が移る前にカーソルは暗黙的にクローズされます。したがって、ハンドラでは明示カーソルの属性の値を参照できません。

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

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

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

DECLARE
   credit_limit CONSTANT NUMBER(3) := 5000;  -- raises an error
BEGIN
   NULL;
EXCEPTION
   WHEN OTHERS THEN
      -- Cannot catch the exception. This handler is never called.
      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 the exception
END;

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

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

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

エラー・コードとエラー・メッセージの取得: SQLCODEおよびSQLERRM

例外ハンドラでは、組込みファンクションSQLCODEおよびSQLERRMを使用して、発生したエラーを確認し、関連するエラー・メッセージを取得できます。内部例外の場合、SQLCODEはOracleエラーの番号を戻します。SQLCODEが戻す番号は負の値ですが、Oracleエラー「データが見つかりません。」の場合は例外で、+100が戻されます。SQLERRMは対応するエラー・メッセージを戻します。メッセージの先頭にはOracleエラー・コードが示されています。

ユーザー定義の例外の場合、SQLCODEは+1を戻し、SQLERRMはメッセージ「ユーザー定義の例外」を戻します。ただし、EXCEPTION_INITプラグマを使用して例外名をOracleエラー番号に関連付けている場合は例外です。この場合、SQLCODEはそのエラー番号を戻し、SQLERRMは対応するエラー・メッセージを戻します。Oracleエラー・メッセージの長さは、エラー・コードおよびネストされたメッセージ、表や列の名前といったメッセージの挿入部分を含めて512文字以内です。

例外が呼び出されなければ、SQLCODEは0(ゼロ)を戻し、SQLERRMはメッセージ「ORA-0000: 正常に完了しました。」を戻します。

SQLERRMにエラー番号を渡すことができます。このとき、SQLERRMはそのエラー番号に関連付けられたメッセージを戻します。SQLERRMに渡すエラー番号は負の値にしてください。

SQLERRMに正数を渡すと、必ず「ユーザー定義の例外」というメッセージが戻されます。+100を渡した場合は例外で、この場合SQLERRMは「データが見つかりません。」というメッセージを戻します。SQLERRMに0(ゼロ)を渡すと、常にメッセージ「正常に完了しました。」を戻します。

SQLCODEまたはSQLERRMは、SQL文では直接使用できません。例10-11に示すように、値をローカル変数に代入してから、その変数をSQL文の中で使用する必要があります。

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

CREATE TABLE errors (code NUMBER, message VARCHAR2(64), happened TIMESTAMP);
DECLARE
   name employees.last_name%TYPE;
   v_code NUMBER;
   v_errm VARCHAR2(64);
BEGIN
   SELECT last_name INTO name FROM employees WHERE employee_id = -1;
   EXCEPTION
      WHEN OTHERS THEN
         v_code := SQLCODE;
         v_errm := SUBSTR(SQLERRM, 1 , 64);
         DBMS_OUTPUT.PUT_LINE('Error code ' || v_code || ': ' || v_errm);
-- Normally we would call another procedure, declared with PRAGMA
-- AUTONOMOUS_TRANSACTION, to insert information about errors.
         INSERT INTO errors VALUES (v_code, v_errm, SYSTIMESTAMP);
END;
/

文字列ファンクションSUBSTRを使用しているため、SQLERRMの値をerr_msgに代入しても、(切捨ての結果として発生する)VALUE_ERROR例外は呼び出されません。どの内部例外が呼び出されるかを通知するファンクションSQLCODEおよびSQLERRMは、特にOTHERS例外ハンドラで使用すると便利です。

RESTRICT_REFERENCESプラグマを使用してストアド・ファンクションの純正度を示すときにファンクションがSQLCODEまたはSQLERRMをコールする場合は、WNPSおよびRNPS制約は指定できません。

未処理例外の捕捉

発生した例外に対応するハンドラが発見できない場合、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;
/

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

例10-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;
/

この例では、SELECT INTO文がZERO_DIVIDE例外を呼び出すと、ローカル・ハンドラが例外を捕捉してsal_calcを2500に設定します。ハンドラの実行が終わり、サブブロックが終了すると、実行はINSERT文から続けられます。例5-38「コレクションに関する例外」を参照してください。

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

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

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

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

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

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

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

例10-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つの例外ハンドラを使用すると、エラーの原因となった文がわからなくなる場合があります。どの文が失敗したかを知る必要がある場合は、ロケータ変数を使用します。

例10-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パッケージおよびUSER/DBA/ALL_PLSQL_OBJECT_SETTINGSビューを使用します。

PL/SQLの警告カテゴリ

PL/SQL警告メッセージはカテゴリに分類されているため、よく似た警告のグループをコンパイル時に抑制したり、表示することができます。カテゴリは次のとおりです。

Allキーワードはすべての警告メッセージを参照する簡単な方法です。

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

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

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

このパラメータはシステム・レベルまたはセッション・レベルで設定できます。ALTER PROCEDURE ...COMPILE文に含めることによって、1回のコンパイル用にも設定できます。開発中にすべての警告を有効にしたり、本番のための配布時にすべての警告を無効にできます。また、不要なコードやパフォーマンスなどの問題が予想される特定のサブプログラムの開発中に、一部の警告を有効にすることもできます。

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

-- To 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;
-- To turn off all warnings
ALTER SESSION SET PLSQL_WARNINGS='DISABLE:ALL';
-- Display 'severe' warnings, don't want 'performance' warnings, and 
-- want 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サブプログラムのコンパイル中に警告メッセージが発行される場合がありますが、無名ブロックは警告を発行しません。

PLSQL_WARNINGSパラメータの設定は、コンパイルされた各サブプログラムとともに格納されます。CREATE OR REPLACE文でサブプログラムを再コンパイルする場合、そのセッションの現在の設定が使用されます。ALTER ...COMPILE文でサブプログラムを再コンパイルする場合は、REUSE SETTINGS句を文に含めるかどうかによって、現在のセッション設定か、サブプロラムとともに格納した元の設定のいずれかが使用されます。詳細は、『Oracle Database SQLリファレンス』のALTER FUNCTIONALTER PACKAGEおよびALTER PROCEDUREに関する説明を参照してください。

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

DBMS_WARNINGパッケージの使用

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

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

例10-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 the procedure and a warning about unreachable code displays
ALTER PROCEDURE unreachable_code COMPILE;
SHOW ERRORS;

例10-16では、DBMS_WARNINGS.set_warning_setting_stringのコールなしで次のALTER PROCEDUREを使用できます。

ALTER PROCEDURE unreachable_code COMPILE
    PLSQL_WARNINGS = 'ENABLE:ALL' REUSE SETTINGS;

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


戻る 次へ
Oracle
Copyright © 2005 Oracle Corporation.

All Rights Reserved.
目次
目次
索引
索引