PL/SQLランタイム・エラーは、設計の失敗、コーディングの間違い、ハードウェアの障害など、多くの原因で発生する可能性があります。 発生する可能性があるすべてのエラーを予想することはできませんが、エラーが発生してもプログラムで処理を続行できるように例外ハンドラをコーディングできます。
ここでのトピック:
PL/SQLでは、エラー条件を例外と呼びます。 例外には、(実行時システムによって)内部的に定義された例外と、ユーザーが定義した例外があります。 内部的に定義された例外には、ORA-22056(値stringが0(ゼロ)で除算されています。)やORA-27102(メモリーが不足しています。)などがあります。 内部的に定義された例外には、ZERO_DIVIDEやSTORAGE_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;
/
データベース・プログラムには信頼性が重要であるため、エラー・チェックと例外処理の両方を使用して、プログラムがすべての可能性を処理できるようにします。
エラーが発生する可能性がある場合は常に、例外ハンドラを追加します。
算術演算、文字列操作、データベース操作の間は、エラーが発生する可能性が特に高くなります。 エラーはその他の場合にも発生する可能性があります。たとえば、ディスク記憶域やメモリーのハードウェア障害がコードに関係ない問題を引き起こした場合でも、コードには対処措置が必要です。
不正な入力データが原因でエラーが発生する可能性がある場合は常に、エラー・チェック・コードを追加します。
コードに間違ったパラメータやNULLパラメータが渡される場合や、問合せが行を戻さない場合、または予想以上の行を戻す場合が考えられます。
不正なデータの様々な組合せでコードをテストし、どのような潜在的なエラーが発生するかを調べます。
データベースが想定外の状態になった場合にもプログラムが安全に動作するようにします。
たとえば、問い合せた表で列の追加や削除が行われていたり、列の型が変更されている場合があります。 こうした問題を回避するには、各変数を%TYPE修飾子で宣言し、問合せ結果を保持するレコードを%ROWTYPE修飾子で宣言します。
例外ハンドラでWHEN OTHERSを使用するかわりに、可能なかぎり名前付き例外を処理します。
事前定義の例外の名前と原因を調べておいてください。 データベース操作によって特定のORA-nエラーが発生する場合は、そのエラーに名前を付けて、それを処理するハンドラを作成できるようにします。 (方法についてはこの章の後半で説明します。)
デバッグ情報を例外ハンドラに記述します。
個別の表にこのような情報を格納する場合もあります。 その場合は、AUTONOMOUS_TRANSACTIONプラグマで宣言したサブプログラムを起動します。これによって、メイン・サブプログラムが行っていた処理をロールバックする場合でもデバッグ情報をコミットできます。
各例外ハンドラがトランザクションをコミットするか、ロールバックするか、継続するのかを慎重に考慮します。
エラーがどれほど重大であっても、データベースを一貫性のある状態に保ち、不正なデータの格納を避けるようにしてください。
エラー処理に例外を使用すると、次のようなメリットがあります。 例外を使用すると、例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では、いくつかの一般的な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 | 呼び出される場合 |
|---|---|---|---|
|
|
|
プログラムが未初期化オブジェクトの属性に値を代入しようとしたとき。 |
|
|
|
|
|
|
|
|
|
プログラムが |
|
|
|
|
すでにオープンされているカーソルをオープンしようとしたとき。 カーソルをオープンするには、一度クローズする必要があります。 カーソル |
|
|
|
|
UNIQUE索引によって制約されている列に、重複した値を格納しようとしたとき。 |
|
|
|
|
オープンされていないカーソルをクローズするなど、不正なカーソル操作を実行しようとしたとき。 |
|
|
|
|
SQL文の中で、文字列が正しい数値を表していなかったために、文字列から数値への変換が失敗したとき。 (プロシージャ文では、 |
|
|
|
|
不正なユーザー名またはパスワードでデータベースにログオンしようとした場合。 |
|
|
|
|
この例外は、いくつかのSQLファンクションで終了したことを通知するために内部的に使用されているため、問合せの一部として起動されるファンクション内部で呼び出された場合は、この例外が伝播されても信頼しないでください。 |
|
|
|
|
データベースに接続していないプログラムが、データベース・コールを発行した場合。 |
|
|
|
|
PL/SQLに内部的な問題が発生した場合。 |
|
|
|
|
1つの代入の中に含まれるホスト・カーソル変数とPL/SQLカーソル変数の戻り型に互換性がない場合。 オープン・ホスト・カーソル変数をストアド・サブプログラムに渡すとき、実パラメータの戻り型と仮パラメータの戻り型には互換性が必要です。 |
|
|
|
|
プログラムが |
|
|
|
|
PL/SQLのメモリーが足りなくなった場合、またはメモリーが破損された場合。 |
|
|
|
|
コレクション中の要素数より大きい索引番号を使用してネストした表またはVARRAYの要素を参照した場合。 |
|
|
|
|
有効範囲外(たとえば-1)の索引番号を使用してネストした表またはVARRAYの要素を参照した場合。 |
|
|
|
|
文字列が正しいROWIDを表していなかったために、文字列からユニバーサルROWIDへの変換が失敗した場合。 |
|
|
|
|
データベースがリソースを求めて待機しているときにタイムアウトが発生した場合。 |
|
|
|
|
|
|
|
|
|
算術エラー、変換エラー、切捨てエラー、またはサイズ制約エラーが発生した場合。 たとえば、列値を選択し文字変数に代入するときに、その値が変数の宣言された長さよりも長い場合、PL/SQLはその代入を停止して |
|
|
|
|
数値を0(ゼロ)で割ろうとしたとき。 |
PL/SQLではユーザー独自の例外を定義できます。 事前定義の例外とは異なり、ユーザー定義の例外は宣言してから、RAISE文またはDBMS_STANDARD.RAISE_APPLICATION_ERRORプロシージャを使用して明示的に呼び出す必要があります。 後者を使用すると、ユーザー定義の例外にエラー・メッセージを関連付けることができます。
ここでのトピック:
例外はPL/SQLブロック、サブプログラムまたはパッケージの宣言部でしか宣言できません。 例外は、例外の名前にキーワードEXCEPTIONを付けて宣言します。 次の例では、past_dueという名前の例外を宣言しています。
DECLARE past_due EXCEPTION;
例外の宣言と変数の宣言は似ています。 ただし、例外はデータ項目ではなく、エラー条件であることを覚えておいてください。 変数とは異なり、例外は代入文や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ハンドラを定義する必要があります。
事前定義の名前がないエラー状態(通常は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に示すとおり、プラグマは、同じ宣言部内の例外宣言より後に表示されます。
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;
内部例外は実行時システムによって暗黙的に呼び出されます。これは、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に例を示します。
例外が呼び出されたときに、PL/SQLがその例外のハンドラをカレント・ブロックまたはサブプログラムで発見できない場合、例外は伝播します。 つまり、例外は外側のブロックで再生され、ハンドラが見つかるまで、または検索するブロックがなくなるまで、1つずつ外側のブロックに進んでいきます。 ハンドラが見つからなかった場合、PL/SQLはホスト環境に「未処理例外」エラーを戻します。
例外はデータベース・リンクを介して行われるリモート・サブプログラム・コールには伝播しません。 そのため、PL/SQLブロックは、リモート・サブプログラムによって呼び出された例外を処理できません。 回避策の詳細は、「独自のエラー・メッセージの定義(RAISE_APPLICATION_ERRORプロシージャ)」を参照してください。
図11-1、図11-2および図11-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を受け取ります。
例外の再呼出しとは、ローカルに処理した例外を、外側のブロックに渡すことです。 たとえば、現在のブロックでトランザクションをロールバックし、エラーを外側のブロックの中でログする場合があります。
例外を再度呼び出すには、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ブロックまたはサブプログラムの通常の実行は中止され、制御が例外処理部に移ります。例外処理部の書式を次に示します。
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を使用してください。
|
参照:
|
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ハンドラを置くと、未処理例外の発生を避けることができます。
例外ハンドラを使用すると、ブロックを終了する前に致命的なエラーからリカバリできます。 ただしハンドラの実行が終了すると、ブロックの実行も終了します。 例外ハンドラから現在のブロックに戻ることはできません。 次の例で、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属性)」を参照)。
例外が呼び出された場合、トランザクションを中止せずに、再試行する場合があります。 その方法は、次のとおりです。
トランザクションをサブブロックに入れます。
そのサブブロックをループの中に入れ、トランザクションが繰り返して実行されるようにします。
トランザクションを開始する前にセーブポイントをマークします。 トランザクションの実行に成功すると、コミットしてループを終了します。 トランザクションの実行に失敗すると制御は例外ハンドラに移り、例外ハンドラはセーブポイントまでロールバックして変更をすべて取り消し、問題点を修正します。
例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警告メッセージを操作するには、PLSQL_WARNINGSコンパイル・パラメータ、DBMS_WARNINGパッケージおよび静的データ・ディクショナリ・ビュー*_PLSQL_OBJECT_SETTINGSを使用します。
ここでのトピック:
PL/SQL警告メッセージは、表11-2に示すカテゴリに分類されます。 コンパイル時に、同様の警告のグループを抑止または表示することができます。 すべての警告メッセージを参照するには、キーワードAllを使用します。
表11-2 PL/SQLの警告カテゴリ
| カテゴリ | 説明 | 例 |
|---|---|---|
|
|
条件によって、予期しないアクションまたは誤った結果が発生する可能性があります。 |
パラメータのエイリアシング問題 |
|
|
条件によって、パフォーマンス問題が発生する可能性があります。 |
|
|
|
条件は、パフォーマンスまたは正確性には影響しませんが、コードのメンテナンス性の向上のために変更する必要がある場合があります。 |
実行できないコード |
特定のメッセージを警告ではなく、エラーとして扱うこともできます。 たとえば、警告メッセージPLW-05003がコードの重大な問題を表している場合、PLSQL_WARNINGS設定に'ERROR:05003'を含めると、条件トリガーは警告メッセージではなくエラー・メッセージ(PLS_05003)になります。 エラー・メッセージが発行されると、コンパイルは失敗します。
PL/SQLコンパイル時にデータベースで警告メッセージを発行させるには、コンパイル・パラメータPLSQL_WARNINGSを設定します。 警告の全カテゴリ(ALL、SEVERE、INFORMATIONAL、PERFORMANCE)および特定のメッセージ番号を有効または無効にできます。また、条件の修正が必要な特定の警告をデータベースでコンパイル・エラーとして扱うこともできます。 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ユニットおよびコンパイル・パラメータ」を参照してください。
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-メッセージに関する項を参照してください。