| Oracle Database PL/SQLユーザーズ・ガイドおよびリファレンス 10g リリース2(10.2) B19257-01 |
|
ランタイム・エラーは、設計の失敗、コーディングの間違い、ハードウェアの障害など、多くの原因で発生します。発生する可能性があるエラーをすべては予想できませんが、ユーザーのPL/SQLプログラムにとって重大なエラーに対しては、処理を準備しておくことはできます。
プログラミング言語では、通常、エラー・チェックを無効にしていないかぎり、スタック・オーバーフローや0(ゼロ)による除算のようなランタイム・エラーが発生すると、正常な処理が停止され、オペレーティング・システムに制御が戻ります。PL/SQLには「例外処理」というメカニズムがあり、エラーが発生しても処理を続けられるように、プログラムを保護しています。
この章の項目は、次のとおりです。
PL/SQLでは、エラー条件を例外と呼びます。例外には、(実行時システムによって)内部的に定義された例外と、ユーザーが定義した例外があります。一般的な内部例外の中には、「0(ゼロ)による除算」や「メモリー不足」などがあります。内部的に定義された例外には、ZERO_DIVIDEやSTORAGE_ERRORといった事前定義の名前を持つものもあります。それ以外の内部例外にも名前を付けることができます。
PL/SQLブロック、サブプログラムまたはパッケージの宣言部で、ユーザー独自の例外を定義できます。たとえば、残高がマイナスになっている銀行口座にフラグを付けるために、insufficient_fundsという名前の例外を定義できます。内部例外とは異なり、ユーザー定義の例外には名前を付ける必要があります。
エラーが発生すると例外が呼び出されます。つまり、通常の実行は中止され、PL/SQLブロックまたはサブプログラムの例外処理部に制御が移ります。内部例外は実行時システムによって暗黙的(自動的)に呼び出されます。ユーザー定義の例外はRAISE文によって明示的に呼び出す必要があります(RAISE文も事前定義の例外を呼び出します)。
呼び出された例外を処理するには、例外ハンドラと呼ばれる独立したルーチンを作成します。例外ハンドラが実行されると、現在のブロックの実行を中止し、外側のブロックの次の文から再開します。外側にブロックがない場合は、制御はホスト環境に戻ります。BULK COLLECTを使用する場合のエラーの管理の詳細は、「%BULK_EXCEPTIONS属性を持つFORALL例外の処理」を参照してください。
例10-1では、企業の株価収益率を計算しています。企業の収益が0(ゼロ)の場合に除算演算を実行すると、事前定義の例外ZERO_DIVIDEが呼び出され、ブロックの実行が中断し、制御が例外ハンドラに移ります。ブロックで特に名前を指定していないすべての例外は、オプションのOTHERSハンドラで処理します。
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; /
データベース・プログラムには信頼性が重要であるため、エラー・チェックと例外処理の両方を使用して、プログラムがすべての可能性を処理できるようにします。
%TYPE修飾子で宣言し、問合せ結果を保持するレコードを%ROWTYPE修飾子で宣言します。
AUTONOMOUS_TRANSACTIONプラグマで宣言したプロシージャをコールしてください。これによって、メイン・プロシージャが行っていた処理をロールバックする場合でもデバッグ情報をコミットできます。
エラー処理に例外を使用すると、次のようなメリットがあります。例外を使用すると、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プログラムがOracleの規則に違反するか、そのシステムの制限を超えると、自動的に内部例外が呼び出されます。PL/SQLでは、いくつかの一般的なOracleエラーが例外として事前定義されています。たとえば、SELECT INTO文が行を戻さなかった場合は、事前定義の例外NO_DATA_FOUNDがPL/SQLによって呼び出されます。
EXCEPTION_INITプラグマを使用して、例外名を予想される他のOracleエラー・コードに関連付けることができます。想定外のOracleエラーを処理するには、OTHERSハンドラを使用します。このハンドラ内では、SQLCODEファンクションとSQLERRMファンクションをコールして、Oracleエラー・コードとメッセージ・テキストを戻すことができます。エラー・コードがわかれば、EXCEPTION_INITプラグマでエラー・コードを使用して、そのエラー専用のハンドラを作成できます。
PL/SQLでは、STANDARDパッケージの中で、事前定義の例外がグローバルに宣言されています。ユーザーが宣言する必要はありません。次の表に示す名前を使用すれば、事前定義の例外を処理するハンドラを作成できます。
PL/SQLではユーザー独自の例外を定義できます。事前定義の例外とは異なり、ユーザー定義の例外は宣言する必要があり、RAISE文を使用して明示的に呼び出す必要があります。
例外はPL/SQLブロック、サブプログラムまたはパッケージの宣言部でしか宣言できません。例外は、例外の名前にキーワードEXCEPTIONを付けて宣言します。次の例では、past_dueという名前の例外を宣言しています。
DECLARE past_due EXCEPTION;
例外の宣言と変数の宣言は似ています。ただし、例外はデータ項目ではなく、エラー条件であることを覚えておいてください。変数とは異なり、例外は代入文やSQL文では使用できません。ただし、変数と例外の有効範囲規則は同じです。
同じブロックでは1つの例外を2回宣言できません。ただし、2つの異なるブロックであれば、同じ例外を宣言できます。
ブロックの中で宣言された例外は、そのブロックに対してローカルで、そのブロックのすべてのサブブロックに対してグローバルであるとみなされます。ブロックはローカルまたはグローバルな例外しか参照できないため、サブブロックで宣言された例外を外側のブロックから参照できません。
サブブロックでグローバルな例外を再宣言すると、ローカルの宣言が優先されます。このため、サブブロックからはグローバルな例外を参照できません。ただし、グローバルな例外がラベル付きのブロックで宣言されている場合は、例外の名前をブロック・ラベルで修飾すると、グローバルな例外を参照できます。
block_label.exception_name
例10-3に、有効範囲規則を示します。
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ハンドラを定義する必要があります。
事前定義の名前がないエラー状態(通常は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に示すとおり、プラグマは、同じ宣言部内の例外宣言より後に表示されます。
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プロシージャを使用すると、ストアド・サブプログラムからユーザー定義のORA-エラー・メッセージを発行できます。これを利用すると、アプリケーションに対してエラーを報告し、処理されない例外が戻されるのを回避できます。
RAISE_APPLICATION_ERRORをコールするには、次の構文を使用します。
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を呼び出しています。
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_INITを使用してOracleエラー番号に関連付けたユーザー定義の例外の場合も同じです。ただし、それ以外のユーザー定義の例外は、RAISE文で明示的に呼び出す必要があります。
PL/SQLブロックおよびサブプログラムから例外を呼び出すのは、エラーが原因で処理の完了が望ましくない場合または不可能な場合のみにする必要があります。指定した例外に対するRAISE文は、その例外の有効範囲の中ならば任意の場所に置くことができます。例10-6では、PL/SQLブロックでout_of_stockという名前のユーザー定義の例外を指定しています。
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に例を示します。
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がその例外のハンドラをカレント・ブロックまたはサブプログラムで発見できない場合、例外は伝播します。つまり、例外は外側のブロックで再生され、ハンドラが見つかるまで、または検索するブロックがなくなるまで、1つずつ外側のブロックに進んでいきます。ハンドラが見つからなかった場合、PL/SQLはホスト環境に「未処理例外」エラーを戻します。
例外はデータベース・リンクを通じて行われるリモート・プロシージャ・コールには伝播しません。そのため、PL/SQLブロックは、リモート・サブプログラムによって呼び出された例外を処理できません。回避策の詳細は、「独自のエラー・メッセージの定義: RAISE_APPLICATION_ERRORプロシージャ」を参照してください。
図10-1、図10-2および図10-3に、基本的な伝播規則を示します。
例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
例外の再呼出しとは、ローカルに処理した例外を、外側のブロックに渡すことです。たとえば、現在のブロックでトランザクションをロールバックし、エラーを外側のブロックの中でログする場合があります。
例外を再度呼び出すには、RAISE文で例外名を省略します。例外名の省略は、例外ハンドラの中でのみ許されます。
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で区切って例外名を並べてください。次に例を示します。
リスト中の例外のいずれかが呼び出されると、それに関連する一連の文が実行されます。キーワードOTHERSは例外名のリストの中では使用できず、単独で使用する必要があります。例外ハンドラの数に制限はなく、また、個々のハンドラは例外のリストを一連の文に関連付けることができます。ただし、例外名はPL/SQLブロックまたはサブプログラムの例外処理部で一度しか使用できません。
PL/SQL変数の通常の有効範囲規則が適用されるため、例外ハンドラの中ではローカル変数とグローバル変数が参照できます。ただし、カーソルFORループの内側で例外が呼び出されると、ハンドラに制御が移る前にカーソルは暗黙的にクローズされます。したがって、ハンドラでは明示カーソルの属性の値を参照できません。
宣言の中でも、初期化の式が間違っていると例外が呼び出される場合があります。たとえば、次の宣言では定数credit_limitが999よりも大きい数値を格納できないため、例外が呼び出されます。
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は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文の中で使用する必要があります。
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ハンドラを置くと、未処理例外の発生を避けることができます。
ここでは、柔軟性が高い方法について説明します。
例外ハンドラを使用すると、ブロックを終了する前に致命的なエラーからリカバリできます。ただしハンドラの実行が終了すると、ブロックの実行も終了します。例外ハンドラから現在のブロックに戻ることはできません。次の例で、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に示すように、サブブロックが終了すると、外側のブロックは、そのサブブロックの終了位置から実行を継続します。
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例外の処理」を参照してください。
例外が呼び出された場合、トランザクションを中止せずに、再試行する場合があります。その方法は、次のとおりです。
例10-13では、INSERT文で一意の列に重複する値を挿入しようとしたため、例外が呼び出されます。この例では、一意である必要のある値を変更し、次のループ反復に進んでいます。INSERT文での挿入が正常に完了すると、ただちにループを終了します。この方法で、FORループまたはWHILEループを使用して、試行の回数を制限することをお薦めします。
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つの例外ハンドラを使用すると、エラーの原因となった文がわからなくなる場合があります。どの文が失敗したかを知る必要がある場合は、ロケータ変数を使用します。
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パッケージおよびUSER/DBA/ALL_PLSQL_OBJECT_SETTINGSビューを使用します。
PL/SQL警告メッセージはカテゴリに分類されているため、よく似た警告のグループをコンパイル時に抑制したり、表示することができます。カテゴリは次のとおりです。
SEVERE: パラメータのエイリアシング問題など、予期しない動作や誤った結果が発生する可能性がある条件のメッセージ。
PERFORMANCE: INSERT文でNUMBER列にVARCHAR2値が渡される場合など、パフォーマンス問題が発生する可能性がある条件のメッセージ。
INFORMATIONAL: 実行できない到達不能コードなど、パフォーマンスや正確性に影響はないが、コードのメンテナンス性の向上のために変更する可能性がある条件のメッセージ。
Allキーワードはすべての警告メッセージを参照する簡単な方法です。
特定のメッセージを警告ではなく、エラーとして扱うこともできます。たとえば、警告メッセージPLW-05003がコードの重大な問題を表している場合、PLSQL_WARNINGS設定に'ERROR:05003'を含めると、条件トリガーは警告メッセージではなくエラー・メッセージ(PLS_05003)になります。エラー・メッセージが発行されると、コンパイルは失敗します。
PL/SQLコンパイル時にデータベースで警告メッセージを発行させるには、初期化パラメータPLSQL_WARNINGSを設定します。警告の全カテゴリ(ALL、SEVERE、INFORMATIONAL、PERFORMANCE)および特定のメッセージ番号を有効または無効にできます。また、条件の修正が必要な特定の警告をデータベースでコンパイル・エラーとして扱うこともできます。
このパラメータはシステム・レベルまたはセッション・レベルで設定できます。ALTER PROCEDURE ...COMPILE文に含めることによって、1回のコンパイル用にも設定できます。開発中にすべての警告を有効にしたり、本番のための配布時にすべての警告を無効にできます。また、不要なコードやパフォーマンスなどの問題が予想される特定のサブプログラムの開発中に、一部の警告を有効にすることもできます。
-- 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 FUNCTION、ALTER PACKAGEおよびALTER PROCEDUREに関する説明を参照してください。
コンパイル中に生成される警告を確認するには、SQL*PlusのSHOW ERRORSコマンドを使用するか、USER_ERRORSデータ・ディクショナリ・ビューを問い合せます。すべてのPL/SQL警告メッセージには、接頭辞PLWが付きます。
PL/SQLサブプログラムをコンパイルする開発環境を作成している場合、DBMS_WARNINGパッケージ内のサブプログラムをコールすることで、PL/SQL警告メッセージを制御できます。複数のネストしたSQL*Plusスクリプトからなる複雑なアプリケーションでは、コンパイル時にこのパッケージを使用することで、サブプログラムごとに異なる警告設定を適用できます。このパッケージを一度コールしてPLSQL_WARNINGSパラメータの現在の状態を保存し、パラメータを変更して特定のサブプログラム・セットをコンパイルしてから、元のパラメータ値をリストアできます。
たとえば、例10-16のプロシージャには、削除できる不要なコードがあります。間違いの可能性や、デバッグ・フラグで意図的に隠された可能性もあります。したがって警告メッセージが必要な場合も必要ない場合もあります。
-- 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を使用できます。
詳細は、『Oracle Database SQLリファレンス』のALTER PROCEDURE、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』のDBMS_WARNINGパッケージおよび『Oracle Databaseエラー・メッセージ』のPLW-メッセージに関する説明を参照してください。
|
![]() Copyright © 2005 Oracle Corporation. All Rights Reserved. |
|