この章では、PL/SQLのコンパイル時の警告およびPL/SQLランタイム・エラーの処理方法について説明します。後者は例外と呼ばれます。
注意:
警告およびエラー・メッセージの言語はNLS_LANGUAGE
パラメータに依存します。このパラメータの詳細は、『Oracle Databaseグローバリゼーション・サポート・ガイド』を参照してください。
ここでのトピック
ヒント:
PL/SQLコードの作成時または実行時に問題が発生した場合は、Oracle Databaseのトレース・ファイルを確認してください。USER_DUMP_DEST
初期化パラメータによってトレース・ファイルの現在の位置が指定されています。このパラメータの値は、SHOW
PARAMETER
USER_DUMP_DEST
を発行して確認できます。トレース・ファイルの詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。
PL/SQLコンパイラは、ストアドPL/SQLユニットのコンパイル中に、エラーを生成してコンパイルの妨げになるほど重大ではない条件(非推奨のPL/SQL機能を使用している場合など)に対して、警告を生成します。
コンパイル中に生成される警告(およびエラー)を確認するには、静的データ・ディクショナリ・ビュー*_ERRORS
を問い合せるか、SQL*Plus環境でコマンドのSHOW
ERRORS
を使用します。
PL/SQL警告のメッセージ・コードは、PLW-nnnnnという形式です。
表11-1 コンパイル時の警告カテゴリ
カテゴリ | 説明 | 例 |
---|---|---|
|
条件によって、予期しないアクションまたは誤った結果が発生する可能性があります。 |
パラメータのエイリアシング問題 |
|
条件によって、パフォーマンス問題が発生する可能性があります。 |
|
|
条件は、パフォーマンスまたは正確性には影響しませんが、コードのメンテナンス性の向上のために変更する必要がある場合があります。 |
実行できないコード |
コンパイル・パラメータPLSQL_WARNINGS
を設定すると、次の操作を実行できます。
すべての警告、1つ以上の警告カテゴリまたは特定の警告の有効化および無効化
特定の警告のエラーとしての処理(その条件が修正されるまで、PL/SQLユニットはコンパイルできません)
PLSQL_WARNINGS
の値は、次のものに対して設定できます。
Oracle Databaseインスタンス
ALTER
SYSTEM
文を使用します。この文の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
セッション
ALTER
SESSION
文を使用します。この文の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
ストアドPL/SQLユニット
「ALTER文」のALTER
文をcompiler_parameters_clause
とともに使用します。
前述のいずれかのALTER
文で、PLSQL_WARNINGS
の値を次の構文で設定します。
PLSQL_WARNINGS = 'value_clause' [, 'value_clause' ] ...
value_clause
の構文は、『Oracle Databaseリファレンス』を参照してください。
PLSQL_WARNINGS
の現在の値を表示するには、静的データ・ディクショナリ・ビューALL_PLSQL_OBJECT_SETTINGS
を問い合せます。
関連項目:
静的データ・ディクショナリ・ビューALL_PLSQL_OBJECT_SETTINGS
の詳細は、『Oracle Databaseリファレンス』を参照してください
すべてのPL/SQL警告のメッセージ・コードについては、Oracle Databaseエラー・メッセージ・リファレンスを参照してください
静的データ・ディクショナリ・ビュー*_ERRORS
の詳細は、『Oracle Databaseリファレンス』を参照してください
PL/SQLユニットおよびコンパイル・パラメータの詳細は、「PL/SQLユニットおよびコンパイル・パラメータ」を参照してください。
例11-1 PLSQL_WARNINGSコンパイル・パラメータの値の設定
この例に、PLSQL_WARNINGS
の値を設定するALTER
文をいくつか示します。
セッションで、すべての警告を有効にします(開発時に特にお薦めします)。
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL';
セッションで、PERFORMANCE
警告を有効にします。
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:PERFORMANCE';
プロシージャloc_var
で、PERFORMANCE
警告を有効にし、設定を再利用します。
ALTER PROCEDURE loc_var COMPILE PLSQL_WARNINGS='ENABLE:PERFORMANCE' REUSE SETTINGS;
セッションで、SEVERE
警告を有効にし、PERFORMANCE
警告を無効にして、PLW-06002警告をエラーとして処理します。
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:SEVERE', 'DISABLE:PERFORMANCE', 'ERROR:06002';
セッションで、すべての警告を無効にします。
ALTER SESSION SET PLSQL_WARNINGS='DISABLE:ALL';
PL/SQLユニットをコンパイルする開発環境(SQL*Plusなど)でPL/SQLユニットを作成している場合、DBMS_WARNING
パッケージ内のサブプログラムを起動することで、PLSQL_WARNINGS
の値を表示および設定できます。
例11-2では、ALTER
SESSION
文を使用して、このセッションのすべての警告メッセージを無効化した後、使用不可コードを含むプロシージャをコンパイルしています。このプロシージャは警告なしでコンパイルされます。次に、DBMS_WARNING.set_warning_setting_string
を起動してこのセッションのすべての警告を有効化し、DBMS_WARNING.get_warning_setting_string
を起動してPLSQL_WARNINGS
の値を表示しています。最後に、プロシージャを再コンパイルすると、コンパイラは使用不可コードに関する警告を生成します。
注意:
使用不可コードは、間違いの可能性またはデバッグ・フラグで意図的に隠されている可能性があります。
複数のネストしたSQL*Plusスクリプトで構成され、サブプログラムごとに異なるPLSQL_WARNINGS
を設定する必要がある複雑なアプリケーションをコンパイルする場合は、DBMS_WARNING
サブプログラムが便利です。DBMS_WARNING
サブプログラムを使用すると、現行のPLSQL_WARNINGS
設定を保存し、設定を変更して特定のサブプログラム・セットをコンパイルしてから、設定を元の値にリストアすることができます。
関連項目:
DBMS_WARNING
パッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください
例11-2 DBMS_WARNINGサブプログラムを使用したPLSQL_WARNINGSの表示と設定
このセッションで、すべての警告メッセージを無効にします。
ALTER SESSION SET PLSQL_WARNINGS='DISABLE:ALL';
警告を無効にすると、このプロシージャは警告なしでコンパイルされます。
CREATE OR REPLACE PROCEDURE unreachable_code AUTHID DEFINER 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; /
このセッションで、すべての警告メッセージを有効にします。
CALL DBMS_WARNING.set_warning_setting_string ('ENABLE:ALL', 'SESSION');
警告の設定を確認します。
SELECT DBMS_WARNING.get_warning_setting_string() FROM DUAL;
結果:
DBMS_WARNING.GET_WARNING_SETTING_STRING() ----------------------------------------- ENABLE:ALL 1 row selected.
プロシージャを再コンパイルします。
ALTER PROCEDURE unreachable_code COMPILE;
結果:
SP2-0805: Procedure altered with compilation warnings
エラーを表示します。
SHOW ERRORS
結果:
Errors for PROCEDURE UNREACHABLE_CODE: LINE/COL ERROR -------- ----------------------------------------------------------------- 7/5 PLW-06002: Unreachable code
例外(PL/SQLランタイム・エラー)は、設計の失敗、コーディングの間違い、ハードウェアの障害などの多くの原因で発生する可能性があります。発生する可能性があるすべての例外を予想することはできませんが、例外が発生してもプログラムで処理を継続できるように例外ハンドラを記述できます。
すべてのPL/SQLブロックに例外処理部を配置でき、そこに1つ以上の例外ハンドラを配置できます。たとえば、例外処理部に次の構文を使用することもできます。
EXCEPTION WHEN ex_name_1 THEN statements_1 -- Exception handler WHEN ex_name_2 OR ex_name_3 THEN statements_2 -- Exception handler WHEN OTHERS THEN statements_3 -- Exception handler END;
前述の構文例で、ex_name_
n
は例外の名前、statements_
n
は1つ以上の文です。(構文およびセマンティクスの詳細は、「例外ハンドラ」を参照してください。)
ブロックの実行部で例外が呼び出されると、実行部は中止され、制御は例外処理部に移ります。ex_name_1
が呼び出された場合は、statements_1
が実行されます。ex_name_2
またはex_name_3
のいずれかが呼び出された場合は、statements_2
が実行されます。それ以外の例外が呼び出された場合は、statements_3
が実行されます。
例外ハンドラが実行されると、制御は外側のブロックの次の文に移ります。外側にブロックがない場合は、次のとおりです。
例外ハンドラがサブプログラム内にある場合、制御は起動元の起動後の文に戻ります。
例外ハンドラが無名ブロック内にある場合、制御はホスト環境(SQL*Plusなど)に移ります。
例外ハンドラを持たないブロックで例外が呼び出された場合は、例外が伝播します。つまり、その例外のハンドラを持つブロックに至るか外側のブロックがなくなるまで、1つずつ外側のブロックに進んで例外が再生されます(詳細は「例外の伝播」を参照してください)。その例外に対応するハンドラがない場合、PL/SQLは起動元またはホスト環境に未処理例外エラーを戻します。その結果は戻す場所によって異なります(詳細は「未処理例外」を参照してください)。
ここでのトピック
例外のカテゴリは次のとおりです。
内部的定義
内部的に定義された例外は、ランタイム・システムによって暗黙的(自動的)に呼び出されます。内部的に定義された例外の例には、ORA-00060(リソース待機の間にデッドロックが検出されました。)やORA-27102(メモリーが不足しています。)などがあります。
内部的に定義された例外には必ずエラー・コードがありますが、PL/SQLで指定されているかユーザーが指定しないかぎり、名前はありません。
詳細は、「内部的に定義された例外」を参照してください。
事前定義
事前定義の例外とは、PL/SQLにより名前が指定されている内部的に定義された例外です。たとえば、ORA-06500(PL/SQL: 記憶域エラー)にはSTORAGE_ERROR
という名前が事前に定義されています。
詳細は、「事前定義の例外」を参照してください。
ユーザー定義
PL/SQLの無名ブロック、サブプログラムまたはパッケージの宣言部で、ユーザー独自の例外を宣言できます。たとえば、残高がマイナスになっている銀行口座にフラグを付けるために、insufficient_funds
という名前の例外を宣言できます。
ユーザー定義の例外は明示的に呼び出す必要があります。
詳細は、「ユーザー定義の例外」を参照してください。
表11-2に、例外のカテゴリの概要を示します。
表11-2 例外のカテゴリ
カテゴリ | 定義者 | エラー・コード | 名前 | 暗黙的な呼出し | 明示的な呼出し |
---|---|---|---|---|---|
内部的定義 |
ランタイム・システム |
必ずあり |
ユーザーが割り当てた場合のみ |
使用可能 |
オプション脚注 1 |
事前定義 |
ランタイム・システム |
必ずあり |
必ずあり |
使用可能 |
オプション脚注参照 1 |
ユーザー定義 |
ユーザー |
ユーザーが割り当てた場合のみ |
必ずあり |
使用不可 |
必ずあり |
脚注 1
詳細は、「内部的に定義された例外のRAISE文による呼出し」を参照してください。
名前付き例外の場合は、OTHERS
例外ハンドラを使用して処理するかわりに、専用の例外ハンドラを記述できます。OTHERS
例外ハンドラの場合は、処理しようとしている例外をファンクションを起動して決定する必要があるため、専用の例外ハンドラの方が効率的です。詳細は、「エラー・コードとエラー・メッセージの取得」を参照してください。
エラー処理に例外ハンドラを使用すると、プログラムの作成および理解が容易になり、未処理例外が発生する可能性が減少します。
例外ハンドラを使用しない場合は、発生する可能性のあるあらゆるエラーを、発生する可能性のあるあらゆる場所でチェックし、処理する必要があります。発生する可能性のあるエラーまたはエラーが発生する可能性のある場所というのは、ただちに検出できないエラーの場合(たとえば、不正なデータは、計算で使用するまで検出できない可能性があります)は特に見落としやすいものです。エラー処理コードはプログラムのいたるところに存在することになります。
例外ハンドラを使用する場合は、発生する可能性のあるあらゆるエラー、またはエラーが発生する可能性のあるあらゆる場所を把握する必要がありません。エラーが発生する可能性のある各ブロックに、例外処理部を含めるだけでかまいません。例外処理部には、特定のエラーおよび不明なエラーの両方の例外ハンドラを含めることができます。ブロック(サブブロックを含む)のどこかでエラーが発生すると、例外ハンドラにより処理されます。エラー処理コードは、ブロックの例外処理部に分離されます。
例11-3のプロシージャでは、1つの例外ハンドラを使用してNO_DATA_FOUND
という事前定義の例外を処理していますが、この例外は2つのSELECT
INTO
文のいずれでも発生する可能性があります。
複数の文で同じ例外ハンドラを使用していて、失敗した文がどれなのかを知る必要がある場合は、例11-4に示すとおり、ロケータ変数を使用できます。
エラー処理コードの精度はユーザーが決定します。すべての0(ゼロ)による除算エラー、不正な配列の索引などに対応する1つの例外ハンドラを持つことができます。独自の例外ハンドラを持つブロック内に単一の文を配置することによって、その文に含まれるエラーをチェックすることもできます。
例11-3 複数の例外に対応した1つの例外ハンドラ
CREATE OR REPLACE PROCEDURE select_item ( t_column VARCHAR2, t_name VARCHAR2 ) AUTHID DEFINER IS temp VARCHAR2(30); BEGIN temp := t_column; -- For error message if next SELECT fails -- Fails if table t_name does not have column t_column: SELECT COLUMN_NAME INTO temp FROM USER_TAB_COLS WHERE TABLE_NAME = UPPER(t_name) AND COLUMN_NAME = UPPER(t_column); temp := t_name; -- For error message if next SELECT fails -- Fails if there is no table named t_name: SELECT OBJECT_NAME INTO temp FROM USER_OBJECTS WHERE OBJECT_NAME = UPPER(t_name) AND OBJECT_TYPE = 'TABLE'; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('No Data found for SELECT on ' || temp); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE ('Unexpected error'); RAISE; END; /
プロシージャを起動します(DEPARTMENTS
表は存在しますが、この表にLAST_NAME
という列はありません)。
BEGIN select_item('departments', 'last_name'); END; /
結果:
No Data found for SELECT on departments
プロシージャを起動します(EMP
表は存在しません)。
BEGIN select_item('emp', 'last_name'); END; /
結果:
No Data found for SELECT on emp
例11-4 例外ハンドラを共有する文のロケータ変数
CREATE OR REPLACE PROCEDURE loc_var AUTHID DEFINER IS stmt_no POSITIVE; name_ VARCHAR2(100); BEGIN stmt_no := 1; SELECT table_name INTO name_ FROM user_tables WHERE table_name LIKE 'ABC%'; stmt_no := 2; 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();
結果:
Table name not found in query 1
可能なかぎり信頼性の高い安全なプログラムを作成する方法は次のとおりです。
エラー・チェック・コードと例外ハンドラの両方を使用します。
不正な入力データが原因でエラーが発生する可能性がある場所には常に、エラー・チェック・コードを使用します。不正な入力データの例には、不適切またはNULLである実パラメータ、行を戻さないまたは予想以上の行を戻す問合せがあります。不正な入力データの様々な組合せでコードをテストし、どのような潜在的なエラーが発生するかを調べます。
エラー・チェック・コードを使用して、例外の発生を回避できる場合もあります(例11-7を参照)。
エラーが発生する可能性がある場所には常に、例外ハンドラを追加します。
算術演算、文字列操作、データベース操作の間は、エラーが発生する可能性が特に高くなります。コードとは関係のない問題(ディスク記憶域やメモリーのハードウェアの障害など)が原因でエラーが発生することもありますが、コードには対処措置が必要です。
データベースが想定外の状態になった場合にもプログラムが動作するように設計します。
たとえば、問い合せた表で列の追加や削除が行われていたり、列の型が変更されている場合があります。こうした問題を回避するには、スカラー変数を%TYPE
修飾子で宣言し、問合せ結果を保持するレコード変数を%ROWTYPE
修飾子で宣言します。
可能な場合は必ず、OTHERS
例外ハンドラを使用するかわりに、名前付き例外用の例外ハンドラを記述します。
事前定義の例外の名前と原因を調べておいてください。名前のない内部的に定義された特定の例外がデータベース操作によって呼び出される可能性があるとわかっている場合は、その例外に名前を付け、それ専用の例外ハンドラを記述できるようにします。
例外ハンドラからデバッグ情報が出力されるようにします。
個別の表にデバッグ情報を格納する場合は、メイン・サブプログラムが実行した処理をロールバックする場合でもデバッグ情報をコミットできるように、自律型ルーチンを使用します。自立型ルーチンの詳細は、「AUTONOMOUS_TRANSACTIONプラグマ」を参照してください。
各例外ハンドラについて、トランザクションをコミットさせるのか、ロールバックさせるのか、継続させるのかを慎重に考慮します。
エラーの重大さに関係なく、データベースを一貫性のある状態に保ち、不正なデータの格納を避けるようにしてください。
すべてのPL/SQLプログラムの最上位にOTHERS
例外ハンドラを配置し、未処理例外が発生しないようにします。
OTHERS
例外ハンドラの最後の文には、RAISE
またはRAISE_APPLICATION_ERROR
プロシージャの起動のいずれかを使用してください。(この方法に従わない場合は、PL/SQL警告が有効になり、PLW-06009が表示されます。)RAISE
またはRAISE_APPLICATION_ERROR
の起動の詳細は、「例外の明示的な呼出し」を参照してください。
内部的に定義された例外(ORA-nエラー)の詳細はOracle Databaseエラー・メッセージ・リファレンスを参照してください。これらの例外はランタイム・システムによって暗黙的(自動的)に呼び出されます。
内部的に定義された例外には、PL/SQLで指定されている(「事前定義の例外」を参照)かユーザーが指定しないかぎり、名前はありません。
名前のない内部的に定義された特定の例外がデータベース操作によって呼び出される可能性があるとわかっている場合は、その例外に名前を付け、それ専用の例外ハンドラを記述できるようにします。そうしない場合は、OTHERS
例外ハンドラ以外では処理できません。
内部的に定義された例外に名前を指定するには、適切な無名ブロック、サブプログラムまたはパッケージの宣言部で次の処理を実行します。(適切なブロックを判定する方法は、「例外の伝播」を参照してください。)
名前を宣言します。
例外名の宣言には次の構文を使用します。
exception_name EXCEPTION;
セマンティクスの詳細は、「例外宣言」を参照してください。
内部的に定義された例外のエラー・コードと名前を関連付けます。
次に構文を示します。
PRAGMA EXCEPTION_INIT (exception_name, error_code)
セマンティクスの詳細は、「EXCEPTION_INITプラグマ」を参照してください。
注意:
内部的に定義された例外は、ユーザーが宣言した名前が付いていても内部的に定義された例外のままであり、ユーザー定義の例外ではありません。
例11-5では、内部的に定義された例外ORA-00060(リソース待機の間にデッドロックが検出されました。)にdeadlock_detected
という名前を指定し、この名前を例外ハンドラで使用しています。
例11-5 内部的に定義された例外への名前付け
DECLARE deadlock_detected EXCEPTION; PRAGMA EXCEPTION_INIT(deadlock_detected, -60); BEGIN ... EXCEPTION WHEN deadlock_detected THEN ... END; /
事前定義の例外は、事前定義の名前を持つ内部的に定義された例外で、PL/SQLによりパッケージSTANDARD
の中でグローバルに宣言されています。事前定義の例外はランタイム・システムによって暗黙的(自動的)に呼び出されます。事前定義の例外には名前が付いているため、それ専用の例外ハンドラを記述できます。
表11-3に、事前定義の例外の名前とエラー・コードを示します。
表11-3 PL/SQLの事前定義の例外
例外名 | エラー・コード |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
例11-6では、企業の株価収益率を計算しています。企業の収益が0(ゼロ)の場合に除算演算を実行すると、事前定義の例外ZERO_DIVIDE
が呼び出され、ブロックの実行部から例外処理部に制御が移ります。
例11-7では、例11-6で処理されている例外を、エラー・チェック・コードを使用して回避しています。
例11-8では、プロシージャは、パラメータdiscrim
の値に応じてEMPLOYEES
表またはDEPARTMENTS
表のカーソル変数をオープンします。無名ブロックは、プロシージャを起動してEMPLOYEES
表のカーソル変数をオープンしますが、DEPARTMENTS
表からフェッチするため、事前定義の例外ROWTYPE_MISMATCH
が発生します。
例11-6 ZERO_DIVIDEを処理する無名ブロック
DECLARE stock_price NUMBER := 9.73; net_earnings NUMBER := 0; pe_ratio NUMBER; BEGIN pe_ratio := stock_price / net_earnings; -- raises ZERO_DIVIDE exception DBMS_OUTPUT.PUT_LINE('Price/earnings ratio = ' || pe_ratio); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Company had zero earnings.'); pe_ratio := NULL; END; /
結果:
Company had zero earnings.
例11-7 ZERO_DIVIDEを回避する無名ブロック
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; /
例11-8 ROWTYPE_MISMATCHを処理する無名ブロック
CREATE OR REPLACE PACKAGE emp_dept_data AUTHID DEFINER AS TYPE cv_type IS REF CURSOR; PROCEDURE open_cv ( cv IN OUT cv_type, discrim IN POSITIVE ); END emp_dept_data; / CREATE OR REPLACE PACKAGE BODY emp_dept_data AS PROCEDURE open_cv ( cv IN OUT cv_type, discrim IN POSITIVE) IS BEGIN IF discrim = 1 THEN OPEN cv FOR SELECT * FROM EMPLOYEES ORDER BY employee_id; ELSIF discrim = 2 THEN OPEN cv FOR SELECT * FROM DEPARTMENTS ORDER BY department_id; END IF; END open_cv; END emp_dept_data; /
無名ブロックからプロシージャopen_cv
を起動します。
DECLARE emp_rec EMPLOYEES%ROWTYPE; dept_rec DEPARTMENTS%ROWTYPE; cv Emp_dept_data.CV_TYPE; BEGIN emp_dept_data.open_cv(cv, 1); -- Open cv for EMPLOYEES fetch. FETCH cv INTO dept_rec; -- Fetch from DEPARTMENTS. DBMS_OUTPUT.PUT(dept_rec.DEPARTMENT_ID); DBMS_OUTPUT.PUT_LINE(' ' || dept_rec.LOCATION_ID); EXCEPTION WHEN ROWTYPE_MISMATCH THEN BEGIN DBMS_OUTPUT.PUT_LINE ('Row type mismatch, fetching EMPLOYEES data ...'); FETCH cv INTO emp_rec; DBMS_OUTPUT.PUT(emp_rec.DEPARTMENT_ID); DBMS_OUTPUT.PUT_LINE(' ' || emp_rec.LAST_NAME); END; END; /
結果:
Row type mismatch, fetching EMPLOYEES data ...
90 King
PL/SQLの無名ブロック、サブプログラムまたはパッケージの宣言部で、ユーザー独自の例外を宣言できます。
例外名の宣言には次の構文を使用します。
exception_name EXCEPTION;
セマンティクスの詳細は、「例外宣言」を参照してください。
ユーザー定義の例外は明示的に呼び出す必要があります。詳細は、「例外の明示的な呼出し」を参照してください。
事前定義の例外の再宣言、つまり、事前定義の例外の名前と同じ名前でユーザー定義の例外を宣言することはお薦めしません。(事前定義の例外の名前のリストは、表11-3を参照してください。)
事前定義の例外を再宣言すると、ローカルな宣言がパッケージSTANDARD
のグローバルな宣言をオーバーライドします。パッケージ名STANDARD
で例外名を修飾しないかぎり、グローバルに宣言された例外のために記述された例外ハンドラでは例外を処理できなくなります。
例11-9に、これを示します。
例11-9 再宣言された事前定義の識別子
DROP TABLE t; CREATE TABLE t (c NUMBER);
次のブロックでは、INSERT
文から事前定義の例外INVALID_NUMBER
が暗黙的に呼び出され、例外ハンドラにより処理されています。
DECLARE default_number NUMBER := 0; BEGIN INSERT INTO t VALUES(TO_NUMBER('100.00', '9G999')); EXCEPTION WHEN INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.'); INSERT INTO t VALUES(default_number); END; /
結果:
Substituting default value for invalid number.
次のブロックでは、事前定義の例外INVALID_NUMBER
を再宣言しています。INSERT
文から事前定義の例外INVALID_NUMBER
が暗黙的に呼び出されても、例外ハンドラでは処理されません。
DECLARE default_number NUMBER := 0; i NUMBER := 5; invalid_number EXCEPTION; -- redeclare predefined exception BEGIN INSERT INTO t VALUES(TO_NUMBER('100.00', '9G999')); EXCEPTION WHEN INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.'); INSERT INTO t VALUES(default_number); END; /
結果:
DECLARE
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at line 6
例外ハンドラの例外名を修飾すると、前述のブロックの例外ハンドラにより事前定義の例外INVALID_NUMBER
が処理されます。
DECLARE default_number NUMBER := 0; i NUMBER := 5; invalid_number EXCEPTION; -- redeclare predefined exception BEGIN INSERT INTO t VALUES(TO_NUMBER('100.00', '9G999')); EXCEPTION WHEN STANDARD.INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.'); INSERT INTO t VALUES(default_number); END; /
結果:
Substituting default value for invalid number.
例外を明示的に呼び出すには、RAISE
文またはRAISE_APPLICATION_ERROR
プロシージャのいずれかを使用します。
ここでのトピック
RAISE
文は例外を明示的に呼び出します。例外ハンドラの外側では、例外名を指定する必要があります。例外ハンドラの内側で、例外名を省略すると、RAISE
文は現行の例外を再度呼び出します。
ここでのトピック
例11-10のプロシージャでは、past_due
という名前の例外を宣言し、この例外をRAISE
文で明示的に呼び出し、例外ハンドラで処理しています。
例11-10 ユーザー定義の例外の宣言、呼出しおよび処理
CREATE PROCEDURE account_status ( due_date DATE, today DATE ) AUTHID DEFINER IS past_due EXCEPTION; -- declare exception BEGIN IF due_date < today THEN RAISE past_due; -- explicitly raise exception END IF; EXCEPTION WHEN past_due THEN -- handle exception DBMS_OUTPUT.PUT_LINE ('Account past due.'); END; / BEGIN account_status (TO_DATE('01-JUL-2010', 'DD-MON-YYYY'), TO_DATE('09-JUL-2010', 'DD-MON-YYYY')); END; /
結果:
Account past due.
内部的に定義された例外はランタイム・システムにより暗黙的に呼び出されますが、名前が付いている場合はRAISE
文を使用して明示的に呼び出すことができます。表11-3に、事前定義の名前を持つ内部的に定義された例外を示します。内部的に定義された例外にユーザーが宣言した名前を指定する方法は、「内部的に定義された例外」を参照してください。
内部的に定義された名前付き例外の例外ハンドラは、その例外の呼出しが暗黙的か明示的かに関係なく、例外を処理します。
例11-11のプロシージャでは、事前定義の例外INVALID_NUMBER
が明示的または暗黙的に呼び出され、必ずINVALID_NUMBER
例外ハンドラによって処理されます。
例11-11 事前定義の例外の明示的な呼出し
DROP TABLE t; CREATE TABLE t (c NUMBER); CREATE PROCEDURE p (n NUMBER) AUTHID DEFINER IS default_number NUMBER := 0; BEGIN IF n < 0 THEN RAISE INVALID_NUMBER; -- raise explicitly ELSE INSERT INTO t VALUES(TO_NUMBER('100.00', '9G999')); -- raise implicitly END IF; EXCEPTION WHEN INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.'); INSERT INTO t VALUES(default_number); END; / BEGIN p(-1); END; /
結果:
Substituting default value for invalid number. BEGIN p(1); END; /
結果:
Substituting default value for invalid number.
例外ハンドラでは、RAISE
文を使用して処理中の例外を「再呼出し」できます。例外を再呼出しすると、外側のブロックに例外を渡してさらに処理することができます。(呼び出された例外を外側のブロックで処理できない場合は例外が伝播します。詳細は「例外の伝播」を参照してください。)現行の例外を再呼出しする場合、例外名を指定する必要はありません。
例11-12の例外処理は、内側のブロックから開始して外側のブロックで終了します。外側のブロックで例外を宣言しているため、両方のブロックに例外名が存在し、各ブロックにこの例外専用の例外ハンドラがあります。内部ブロックが例外を呼び出すと、内部ブロックの例外ハンドラは最初の処理を実行した後にこの例外を再度呼び出し、さらに処理するために外側のブロックに渡しています。
例11-12 例外の再呼出し
DECLARE salary_too_high EXCEPTION; current_salary NUMBER := 20000; max_salary NUMBER := 10000; erroneous_salary NUMBER; BEGIN BEGIN IF current_salary > max_salary THEN RAISE salary_too_high; -- raise exception END IF; EXCEPTION WHEN salary_too_high THEN -- start handling exception erroneous_salary := current_salary; DBMS_OUTPUT.PUT_LINE('Salary ' || erroneous_salary ||' is out of range.'); DBMS_OUTPUT.PUT_LINE ('Maximum salary is ' || max_salary || '.'); RAISE; -- reraise current exception (exception name is optional) END; EXCEPTION WHEN salary_too_high THEN -- finish handling exception current_salary := max_salary; DBMS_OUTPUT.PUT_LINE ( 'Revising salary from ' || erroneous_salary || ' to ' || current_salary || '.' ); END; /
結果:
Salary 20000 is out of range. Maximum salary is 10000. Revising salary from 20000 to 10000.
(DBMS_STANDARD
パッケージで定義されている)RAISE_APPLICATION_ERROR
プロシージャは、ストアド・サブプログラムまたはメソッドからのみ起動できます。このプロシージャを起動する通常の目的は、ユーザー定義の例外を呼び出してそのエラー・コードとエラー・メッセージを起動元に戻すことです。
RAISE_APPLICATION_ERROR
を起動するには、次の構文を使用します。
RAISE_APPLICATION_ERROR (error_code, message[, {TRUE | FALSE}]);
EXCEPTION_INIT
プラグマを使用してerror_code
をユーザー定義の例外に代入しておく必要があります。次に構文を示します。
PRAGMA EXCEPTION_INIT (exception_name, error_code)
error_code
は-20000から-20999の範囲の整数で、message
は長さが2048バイト以内の文字列です。
セマンティクスの詳細は、「EXCEPTION_INITプラグマ」を参照してください。
message
は長さが2048バイト以内の文字列です。
TRUE
を指定すると、PL/SQLによりerror_code
がエラー・スタックの最上位に配置されます。それ以外の場合は、PL/SQLによりエラー・スタックがerror_code
に置き換えられます。
例11-13の無名ブロックでは、past_due
という名前の例外を宣言し、この例外にエラー・コード-20000を代入し、ストアド・プロシージャを起動しています。このストアド・プロシージャは、エラー・コード-20000とメッセージを使用してRAISE_APPLICATION_ERROR
プロシージャを起動し、制御が無名ブロックに戻ったところで例外を処理します。例外に関連付けられているメッセージを取得するために、無名ブロックの例外ハンドラがSQLERRM
ファンクション(詳細は、「エラー・コードとエラー・メッセージの取得」を参照)を起動しています。
例11-13 RAISE_APPLICATION_ERRORによるユーザー定義の例外の呼出し
CREATE PROCEDURE account_status ( due_date DATE, today DATE ) AUTHID DEFINER IS BEGIN IF due_date < today THEN -- explicitly raise exception RAISE_APPLICATION_ERROR(-20000, 'Account past due.'); END IF; END; / DECLARE past_due EXCEPTION; -- declare exception PRAGMA EXCEPTION_INIT (past_due, -20000); -- assign error code to exception BEGIN account_status (TO_DATE('01-JUL-2010', 'DD-MON-YYYY'), TO_DATE('09-JUL-2010', 'DD-MON-YYYY')); -- invoke procedure EXCEPTION WHEN past_due THEN -- handle exception DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLERRM(-20000))); END; /
結果:
ORA-20000: Account past due.
例外ハンドラを持たないブロックで例外が呼び出された場合は、例外が伝播します。つまり、その例外のハンドラを持つブロックに至るか外側のブロックがなくなるまで、1つずつ外側のブロックに進んで例外が再生されます。その例外に対応するハンドラがない場合、PL/SQLは起動元またはホスト環境に未処理例外エラーを戻します。その結果は戻す場所によって異なります(詳細は「未処理例外」を参照してください)。
図11-1では、1つのブロックが別のブロックの内側にネストされています。内側のブロックが例外Aを呼び出します。内側のブロックにはAの例外ハンドラがあるため、Aは伝播しません。例外ハンドラが実行されると、外側のブロックの次の文に制御が移ります。
図11-2では、内側のブロックが例外Bを呼び出しています。内側のブロックには例外Bの例外ハンドラがないため、例外Bはその例外ハンドラを持つ外側のブロックに伝播します。例外ハンドラが実行されると、ホスト環境に制御が移ります。
図11-3では、内側のブロックが例外Cを呼び出しています。内側のブロックには例外Cの例外ハンドラがないため、例外Cは外側のブロックに伝播します。外側のブロックにはCの例外ハンドラがないため、PL/SQLはホスト環境に未処理例外エラーを戻します。
ユーザー定義の例外は有効範囲を超えて(つまり宣言されたブロックを超えたところまで)伝播することがありますが、有効範囲を超えたところには例外名が存在しません。そのため、有効範囲を超えたユーザー定義の例外は、OTHERS
例外ハンドラ以外では処理できません。
例11-14の内側のブロックでは、past_due
という名前の例外を宣言していますが、この例外の例外ハンドラはありません。内側のブロックでpast_due
が呼び出されると、外側のブロックに例外が伝播しますが、そこにはpast_due
という名前が存在しません。外側のブロックはOTHERS
例外ハンドラを使用して例外を処理します。
ユーザー定義の例外が外側のブロックで処理されない場合は、例11-15に示すとおり、エラーが発生します。
注意:
例外はリモート・サブプログラム起動には伝播しません。そのため、PL/SQLブロックは、リモート・サブプログラムによって呼び出された例外を処理できません。
ここでのトピック
例11-14 有効範囲を超えて伝播した例外が処理される場合
CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS BEGIN DECLARE past_due EXCEPTION; PRAGMA EXCEPTION_INIT (past_due, -4910); due_date DATE := trunc(SYSDATE) - 1; todays_date DATE := trunc(SYSDATE); BEGIN IF due_date < todays_date THEN RAISE past_due; END IF; END; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END; /
例11-15 有効範囲を超えて伝播した例外が処理されない場合
BEGIN
DECLARE
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;
END;
/
結果:
BEGIN
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at line 9
宣言の中で呼び出された例外はただちに外側のブロック(外側のブロックがない場合は起動元またはホスト環境)に伝播します。したがって、例外ハンドラは宣言と同じブロックの中ではなく、外側または起動元のブロックに存在する必要があります。
例11-16では、VALUE_ERROR
が呼び出される宣言と同じブロックの中にVALUE_ERROR
例外ハンドラが存在しています。例外はただちにホスト環境に伝播するため、この例外は例外ハンドラで処理されません。
例11-17は例11-16と似ていますが、内側のブロックの中の宣言で呼び出されるVALUE_ERROR
例外が外側のブロックで処理される点が異なります。
例11-16 宣言の中で呼び出された例外が処理されない場合
DECLARE credit_limit CONSTANT NUMBER(3) := 5000; -- Maximum value is 999 BEGIN NULL; EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Exception raised in declaration.'); END; /
結果:
DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error: number precision too large ORA-06512: at line 2
例11-17 宣言の中で呼び出された例外が外側のブロックで処理される場合
BEGIN DECLARE credit_limit CONSTANT NUMBER(3) := 5000; BEGIN NULL; END; EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Exception raised in declaration.'); END; /
結果:
Exception raised in declaration.
例外ハンドラの中で呼び出された例外はただちに外側のブロック(外側のブロックがない場合は起動元またはホスト環境)に伝播します。したがって、例外ハンドラは、外側または起動元のブロックに存在する必要があります。
例11-18では、n
が0 (ゼロ)の場合、計算1/n
で事前定義の例外ZERO_DIVIDE
が呼び出され、同じブロックの中のZERO_DIVIDE
例外ハンドラに制御が移ります。例外ハンドラでZERO_DIVIDE
が呼び出されると、例外はただちに起動元に伝播します。起動元で例外が処理されないため、PL/SQLはホスト環境に未処理例外エラーを戻します。
例11-19は例11-18に似ていますが、プロシージャが起動元に未処理例外エラーを戻すと起動元でエラーが処理される点が異なります。
例11-20は例11-18と似ていますが、内側のブロックの中の例外ハンドラで呼び出される例外が外側のブロックで処理される点が異なります。
例11-21では、ユーザー定義の例外i_is_one
と事前定義の例外ZERO_DIVIDE
に対応する例外ハンドラがプロシージャの例外処理部にあります。i_is_one
例外ハンドラでZERO_DIVIDE
が呼び出されると、例外はただちに起動元に伝播します(そのため、ZERO_DIVIDE
例外ハンドラでは例外が処理されません)。起動元で例外が処理されないため、PL/SQLはホスト環境に未処理例外エラーを戻します。
例11-22は例11-21と似ていますが、i_is_one
例外ハンドラで呼び出されるZERO_DIVIDE
例外が外側のブロックで処理される点が異なります。
例11-18 例外ハンドラの中で呼び出された例外が処理されない場合
CREATE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS BEGIN DBMS_OUTPUT.PUT_LINE(1/n); -- handled EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error:'); DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined'); -- not handled END; / BEGIN -- invoking block print_reciprocal(0); END;
結果:
Error: BEGIN * ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at "HR.PRINT_RECIPROCAL", line 7 ORA-01476: divisor is equal to zero ORA-06512: at line 2
例11-19 例外ハンドラの中で呼び出された例外が起動元で処理される場合
CREATE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS BEGIN DBMS_OUTPUT.PUT_LINE(1/n); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error:'); DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined'); END; / BEGIN -- invoking block print_reciprocal(0); EXCEPTION WHEN ZERO_DIVIDE THEN -- handles exception raised in exception handler DBMS_OUTPUT.PUT_LINE('1/0 is undefined.'); END; /
結果:
Error: 1/0 is undefined.
例11-20 例外ハンドラの中で呼び出された例外が外側のブロックで処理される場合
CREATE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS BEGIN BEGIN DBMS_OUTPUT.PUT_LINE(1/n); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error in inner block:'); DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined.'); END; EXCEPTION WHEN ZERO_DIVIDE THEN -- handles exception raised in exception handler DBMS_OUTPUT.PUT('Error in outer block: '); DBMS_OUTPUT.PUT_LINE('1/0 is undefined.'); END; / BEGIN print_reciprocal(0); END; /
結果:
Error in inner block: Error in outer block: 1/0 is undefined.
例11-21 例外ハンドラの中で呼び出された例外が処理されない場合
CREATE PROCEDURE descending_reciprocals (n INTEGER) AUTHID DEFINER IS i INTEGER; i_is_one EXCEPTION; BEGIN i := n; LOOP IF i = 1 THEN RAISE i_is_one; ELSE DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || i || ' is ' || 1/i); END IF; i := i - 1; END LOOP; EXCEPTION WHEN i_is_one THEN DBMS_OUTPUT.PUT_LINE('1 is its own reciprocal.'); DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || TO_CHAR(i-1) || ' is ' || TO_CHAR(1/(i-1))); WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error:'); DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined'); END; / BEGIN descending_reciprocals(3); END; /
結果:
Reciprocal of 3 is .3333333333333333333333333333333333333333 Reciprocal of 2 is .5 1 is its own reciprocal. BEGIN * ERROR at line 1: ORA-01476: divisor is equal to zero ORA-06512: at "HR.DESCENDING_RECIPROCALS", line 19 ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at line 2
例11-22 例外ハンドラの中で呼び出された例外が外側のブロックで処理される場合
CREATE PROCEDURE descending_reciprocals (n INTEGER) AUTHID DEFINER IS i INTEGER; i_is_one EXCEPTION; BEGIN BEGIN i := n; LOOP IF i = 1 THEN RAISE i_is_one; ELSE DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || i || ' is ' || 1/i); END IF; i := i - 1; END LOOP; EXCEPTION WHEN i_is_one THEN DBMS_OUTPUT.PUT_LINE('1 is its own reciprocal.'); DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || TO_CHAR(i-1) || ' is ' || TO_CHAR(1/(i-1))); WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Error:'); DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined'); END; EXCEPTION WHEN ZERO_DIVIDE THEN -- handles exception raised in exception handler DBMS_OUTPUT.PUT_LINE('Error:'); DBMS_OUTPUT.PUT_LINE('1/0 is undefined'); END; / BEGIN descending_reciprocals(3); END; /
結果:
Reciprocal of 3 is .3333333333333333333333333333333333333333 Reciprocal of 2 is .5 1 is its own reciprocal. Error: 1/0 is undefined
発生した例外に対応するハンドラがない場合、PL/SQLは起動元またはホスト環境に未処理例外エラーを戻します。その結果は戻す場所によって異なります。
ストアド・サブプログラムが未処理例外で終了した場合には、PL/SQLはそのサブプログラムによって加えられたデータベースへの変更をロールバックしません。
FORALL
文は、VALUES
句およびWHERE
句の異なる値を使用して、1つのDML文を複数回実行します。1つの値セットで未処理例外が呼び出されると、PL/SQLはこのFORALL
文でそれまでに加えられたデータベースへのすべての変更をロールバックします。詳細は、「FORALL例外の即座の処理」および「FORALL文が完了した後のFORALL例外の処理」を参照してください。
ヒント:
すべてのPL/SQLプログラムの最上位にOTHERS
例外ハンドラを配置し、未処理例外が発生しないようにします。
例外ハンドラの中では、処理中の例外について次のことを実行できます。
PL/SQLファンクションSQLCODE
を使用してエラー・コードを取得できます。このファンクションの詳細は「SQLCODEファンクション」を参照してください。
次のいずれかを使用してエラー・メッセージを取得できます。
PL/SQLファンクションSQLERRM
(詳細は「SQLERRMファンクション」を参照)
このファンクションは最大512バイトを戻します。これは、Oracle Databaseエラー・メッセージ(エラー・コード、ネストされたメッセージおよび表や列の名前などのメッセージの挿入部分を含む)の最大長です。
パッケージ・ファンクションDBMS_UTILITY
.FORMAT_ERROR_STACK
(詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください)
このファンクションは最大2000バイトの完全なエラー・スタックを戻します。
FORALL
文をSAVE
EXCEPTIONS
句とともに使用する場合以外は、例12-13に示すとおり、DBMS_UTILITY
.FORMAT_ERROR_STACK
を使用することをお薦めします。
SQL文では、SQLCODE
またはSQLERRM
を起動できません。これらの値をSQL文で使用するには、例11-23に示すとおり、これらの値をまずローカル変数に代入します。
関連項目:
サブプログラムがouterスコープの例外ハンドラから呼び出された場合でも、例外が発生した場所のコール・スタックを表示するDBMS_UTILITY
.FORMAT_ERROR_BACKTRACE
関数の詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。
UTL_CALL_STACK
パッケージの詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。このパッケージのサブプログラムにより、現在実行中のサブプログラムのサブプログラム名などの情報が提供されます
例11-23 SQLCODEおよびSQLERRMの値の表示
DROP TABLE errors; CREATE TABLE errors ( code NUMBER, message VARCHAR2(64) ); CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS 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); /* Invoke another procedure, declared with PRAGMA AUTONOMOUS_TRANSACTION, to insert information about errors. */ INSERT INTO errors (code, message) VALUES (v_code, v_errm); RAISE; END; /
例外ハンドラが実行されると、外側のブロック(外側のブロックがない場合は起動元またはホスト環境)の次の文に制御が移ります。例外ハンドラは、そのハンドラが存在するブロックに制御を取り戻すことができません。
たとえば、例11-24では、SELECT
INTO
文でZERO_DIVIDE
が呼び出され、例外ハンドラがこれを処理した後に、SELECT
INTO
文に続くINSERT
文から続けて実行することができません。
SELECT
INTO
文に続くINSERT
文から実行を再開する必要がある場合は、例11-25
に示すとおり、独自のZERO_DIVIDE
例外ハンドラを持つ内側のブロックにSELECT
INTO文を配置します。
関連項目:
例12-13では、例外が発生してもバルクSQL操作が継続されます。
例11-24 例外ハンドラの実行と実行の終了
DROP TABLE employees_temp; CREATE TABLE employees_temp AS SELECT employee_id, salary, commission_pct FROM employees; DECLARE sal_calc NUMBER(8,2); BEGIN INSERT INTO employees_temp (employee_id, salary, commission_pct) 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); DBMS_OUTPUT.PUT_LINE('Row inserted.'); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Division by zero.'); END; /
結果:
Division by zero.
例11-25 例外ハンドラの実行と実行の継続
DECLARE sal_calc NUMBER(8,2); BEGIN INSERT INTO employees_temp (employee_id, salary, commission_pct) VALUES (301, 2500, 0); BEGIN SELECT (salary / commission_pct) INTO sal_calc FROM employees_temp WHERE employee_id = 301; EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Substituting 2500 for undefined number.'); sal_calc := 2500; END; INSERT INTO employees_temp VALUES (302, sal_calc/100, .1); DBMS_OUTPUT.PUT_LINE('Enclosing block: Row inserted.'); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Enclosing block: Division by zero.'); END; /
結果:
Substituting 2500 for undefined number. Enclosing block: Row inserted.
例外を呼び出したトランザクションを例外処理後に再試行するには、次の方法を使用します。
例11-26 例外処理後のトランザクションの再試行
DROP TABLE results; CREATE TABLE results ( res_name VARCHAR(20), res_answer VARCHAR2(3) ); CREATE UNIQUE INDEX res_name_ix ON results (res_name); INSERT INTO results (res_name, res_answer) VALUES ('SMYTHE', 'YES'); INSERT INTO results (res_name, res_answer) VALUES ('JONES', 'NO'); DECLARE name VARCHAR2(20) := 'SMYTHE'; answer VARCHAR2(3) := 'NO'; suffix NUMBER := 1; BEGIN FOR i IN 1..5 LOOP -- Try transaction at most 5 times. DBMS_OUTPUT.PUT('Try #' || i); BEGIN -- sub-block begins SAVEPOINT start_transaction; -- transaction begins DELETE FROM results WHERE res_answer = 'NO'; INSERT INTO results (res_name, res_answer) VALUES (name, answer); -- Nonunique name raises DUP_VAL_ON_INDEX. -- If transaction succeeded: COMMIT; DBMS_OUTPUT.PUT_LINE(' succeeded.'); EXIT; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN DBMS_OUTPUT.PUT_LINE(' failed; trying again.'); 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; /
結果:
Try #1 failed; trying again. Try #2 succeeded.
例11-26では、res_name
の値が一意でない場合に事前定義の例外DUP_VAL_ON_INDEX
がINSERT
文により呼び出されるトランザクションを、前述の方法を使用して再試行しています。
分散問合せは、トリガーまたはストアド・サブプログラムを使用して作成できます。この分散問合せは、ローカルのOracle Databaseインスタンスによって、対応する数のリモート問合せに分解されてリモート・ノードに送られます。リモート・ノードはその問合せを実行し、ローカル・ノードにその結果を送ります。その後、ローカル・ノードは必要な後処理を行い、ユーザーまたはアプリケーションに結果を戻します。
たとえば、制約違反のために分散問合せ文の一部でエラーが発生すると、Oracle DatabaseはORA-02055を戻します。後続する文またはサブプログラムの起動は、ロールバック、またはセーブポイントまでのロールバックが入力されるまで、ORA-02067を戻します。
分散更新の一部でエラーが発生したことを示すエラー・メッセージがチェックされるように、アプリケーションを設計してください。エラーを検出した場合、アプリケーションが処理を継続する前に、トランザクション全体をロールバック(またはセーブポイントまでロールバック)してください。