ヘッダーをスキップ
Oracle® Database PL/SQL言語リファレンス
11gリリース2 (11.2)
B56260-09
  目次へ移動
目次
索引へ移動
索引

前
 
次
 

11 PL/SQLのエラー処理

この章では、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(『Oracle Databaseリファレンス』を参照)を問い合せるか、SQL*Plus環境でコマンドのSHOW ERRORSを使用します。

PL/SQL警告のメッセージ・コードは、PLW-nnnnnという形式です。すべてのPL/SQL警告のメッセージ・コードについては、『Oracle Databaseエラー・メッセージ』を参照してください。

表11-1に、警告のカテゴリの概要を示します。

表11-1 コンパイル時の警告カテゴリ

カテゴリ 説明

SEVERE

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

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

PERFORMANCE

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

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

INFORMATIONAL

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

実行できないコード


コンパイル・パラメータ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とともに使用します。PL/SQLユニットおよびコンパイラ・パラメータの詳細は、「PL/SQLユニットおよびコンパイル・パラメータ」を参照してください。

前述のいずれかのALTER文で、PLSQL_WARNINGSの値を次の構文で設定します。

PLSQL_WARNINGS = 'value_clause' [, 'value_clause' ] ...

value_clauseの構文は、『Oracle Databaseリファレンス』を参照してください。

例11-1に、PLSQL_WARNINGSの値を設定するALTER文をいくつか示します。

例11-1 PLSQL_WARNINGSコンパイル・パラメータの値の設定

セッションで、すべての警告を有効にします(開発時に特にお薦めします)。

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';

PLSQL_WARNINGSの現在の値を表示するには、静的データ・ディクショナリ・ビューALL_PLSQL_OBJECT_SETTINGSを問い合せます。このビューの詳細は、『Oracle Databaseリファレンス』を参照してください。

DBMS_WARNINGパッケージ

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の値を表示しています。最後に、プロシージャを再コンパイルすると、コンパイラは使用不可コードに関する警告を生成します。


注意:

使用不可コードは、間違いの可能性またはデバッグ・フラグで意図的に隠されている可能性があります。

例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

複数のネストしたSQL*Plusスクリプトで構成され、サブプログラムごとに異なるPLSQL_WARNINGSを設定する必要がある複雑なアプリケーションをコンパイルする場合は、DBMS_WARNINGサブプログラムが便利です。DBMS_WARNINGサブプログラムを使用すると、現行のPLSQL_WARNINGS設定を保存し、設定を変更して特定のサブプログラム・セットをコンパイルしてから、設定を元の値にリストアすることができます。


関連項目:

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

例外処理の概要

例外(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-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に示すとおり、ロケータ変数を使用できます。

例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

エラー処理コードの精度はユーザーが決定します。すべての0(ゼロ)による除算エラー、不正な配列の索引などに対応する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例外ハンドラ以外では処理できません。

内部的に定義された例外に名前を指定するには、適切な無名ブロック、サブプログラムまたはパッケージの宣言部で次の処理を実行します。(適切なブロックを判定する方法は、「例外の伝播」を参照してください。)

  1. 名前を宣言します。

    例外名の宣言には次の構文を使用します。

    exception_name EXCEPTION;
    

    セマンティクスの詳細は、「例外宣言」を参照してください。

  2. 内部的に定義された例外のエラー・コードと名前を関連付けます。

    次に構文を示します。

    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の事前定義の例外

例外名 エラー・コード

ACCESS_INTO_NULL

-6530

CASE_NOT_FOUND

-6592

COLLECTION_IS_NULL

-6531

CURSOR_ALREADY_OPEN

-6511

DUP_VAL_ON_INDEX

-1

INVALID_CURSOR

-1001

INVALID_NUMBER

-1722

LOGIN_DENIED

-1017

NO_DATA_FOUND

+100

NO_DATA_NEEDED

-6548

NOT_LOGGED_ON

-1012

PROGRAM_ERROR

-6501

ROWTYPE_MISMATCH

-6504

SELF_IS_NULL

-30625

STORAGE_ERROR

-6500

SUBSCRIPT_BEYOND_COUNT

-6533

SUBSCRIPT_OUTSIDE_LIMIT

-6532

SYS_INVALID_ROWID

-1410

TIMEOUT_ON_RESOURCE

-51

TOO_MANY_ROWS

-1422

VALUE_ERROR

-6502

ZERO_DIVIDE

-1476


例11-6では、企業の株価収益率を計算しています。企業の収益が0(ゼロ)の場合に除算演算を実行すると、事前定義の例外ZERO_DIVIDEが呼び出され、ブロックの実行部から例外処理部に制御が移ります。

例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では、例11-6で処理されている例外を、エラー・チェック・コードを使用して回避しています。

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

ユーザー定義の例外

PL/SQLの無名ブロック、サブプログラムまたはパッケージの宣言部で、ユーザー独自の例外を宣言できます。

例外名の宣言には次の構文を使用します。

exception_name EXCEPTION;

セマンティクスの詳細は、「例外宣言」を参照してください。

ユーザー定義の例外は明示的に呼び出す必要があります。詳細は、「例外の明示的な呼出し」を参照してください。

再宣言された事前定義の例外

事前定義の例外の再宣言、つまり、事前定義の例外の名前と同じ名前でユーザー定義の例外を宣言することはお薦めしません。(事前定義の例外の名前のリストは、表11-3を参照してください。)

事前定義の例外を再宣言すると、ローカルな宣言がパッケージSTANDARDのグローバルな宣言をオーバーライドします。パッケージ名STANDARDで例外名を修飾しないかぎり、グローバルに宣言された例外のために記述された例外ハンドラでは例外を処理できなくなります。

例11-8にその様子を示します。

例11-8 再宣言された事前定義の識別子

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文は例外を明示的に呼び出します。例外ハンドラの外側では、例外名を指定する必要があります。例外ハンドラの内側で、例外名を省略すると、RAISE文は現行の例外を再度呼び出します。

ここでのトピック

ユーザー定義の例外のRAISE文による呼出し

例11-9のプロシージャでは、past_dueという名前の例外を宣言し、この例外をRAISE文で明示的に呼び出し、例外ハンドラで処理しています。

例11-9 ユーザー定義の例外の宣言、呼出しおよび処理

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 ('1-JUL-10', '9-JUL-10');
END;
/

結果:

Account past due.

内部的に定義された例外のRAISE文による呼出し

内部的に定義された例外はランタイム・システムにより暗黙的に呼び出されますが、名前が付いている場合はRAISE文を使用して明示的に呼び出すことができます。表11-3に、事前定義の名前を持つ内部的に定義された例外を示します。内部的に定義された例外にユーザーが宣言した名前を指定する方法は、「内部的に定義された例外」を参照してください。

内部的に定義された名前付き例外の例外ハンドラは、その例外の呼出しが暗黙的か明示的かに関係なく、例外を処理します。

例11-10のプロシージャでは、事前定義の例外INVALID_NUMBERが明示的または暗黙的に呼び出され、必ずINVALID_NUMBER例外ハンドラによって処理されます。

例11-10 事前定義の例外の明示的な呼出し

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文による再呼出し

例外ハンドラでは、処理中の例外をRAISE文で「再呼出し」できます。例外を再呼出しすると、外側のブロックに例外を渡してさらに処理することができます。(呼び出された例外を外側のブロックで処理できない場合は例外が伝播します。詳細は「例外の伝播」を参照してください。)現行の例外を再呼出しする場合、例外名を指定する必要はありません。

例11-11の例外処理は、内側のブロックから開始して外側のブロックで終了します。外側のブロックで例外を宣言しているため、両方のブロックに例外名が存在し、各ブロックにこの例外専用の例外ハンドラがあります。内部ブロックが例外を呼び出すと、内部ブロックの例外ハンドラは最初の処理を実行した後にこの例外を再度呼び出し、さらに処理するために外側のブロックに渡しています。

例11-11 例外の再呼出し

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.

RAISE_APPLICATION_ERRORプロシージャ

(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)

セマンティクスの詳細は、「EXCEPTION_INITプラグマ」を参照してください。

error_codeは-20000から-20999の範囲の整数で、messageは長さが2048バイト以内の文字列です。

TRUEを指定すると、PL/SQLによりerror_codeがエラー・スタックの最上位に配置されます。それ以外の場合は、PL/SQLによりエラー・スタックがerror_codeに置き換えられます。

例11-12の無名ブロックでは、past_dueという名前の例外を宣言し、この例外にエラー・コード-20000を代入し、ストアド・プロシージャを起動しています。このストアド・プロシージャは、エラー・コード-20000とメッセージを使用してRAISE_APPLICATION_ERRORプロシージャを起動し、制御が無名ブロックに戻ったところで例外を処理します。例外に関連付けられているメッセージを取得するために、無名ブロックの例外ハンドラがSQLERRMファンクション(詳細は、「エラー・コードとエラー・メッセージの取得」を参照)を起動しています。

例11-12 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 ('1-JUL-10', '9-JUL-10');   -- 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-1 伝播しない例外

図11-1の説明が続く
「図11-1 伝播しない例外」の説明

図11-2では、内側のブロックが例外Bを呼び出しています。内側のブロックには例外Bの例外ハンドラがないため、例外Bはその例外ハンドラを持つ外側のブロックに伝播します。例外ハンドラが実行されると、ホスト環境に制御が移ります。

図11-2 内側のブロックから外側のブロックに伝播する例外

図11-2の説明が続く
「図11-2 内側のブロックから外側のブロックに伝播する例外」の説明

図11-3では、内側のブロックが例外Cを呼び出しています。内側のブロックには例外Cの例外ハンドラがないため、例外Cは外側のブロックに伝播します。外側のブロックにはCの例外ハンドラがないため、PL/SQLはホスト環境に未処理例外エラーを戻します。

図11-3 ホスト環境に未処理例外エラーを戻すPL/SQL

図11-3の説明が続く
「図11-3 ホスト環境に未処理例外エラーを戻すPL/SQL」の説明

ユーザー定義の例外は有効範囲を超えて(つまり宣言されたブロックを超えたところまで)伝播することがありますが、有効範囲を超えたところには例外名が存在しません。そのため、有効範囲を超えたユーザー定義の例外は、OTHERS例外ハンドラ以外では処理できません。

例11-13の内側のブロックでは、past_dueという名前の例外を宣言していますが、この例外の例外ハンドラはありません。内側のブロックでpast_dueが呼び出されると、外側のブロックに例外が伝播しますが、そこにはpast_dueという名前が存在しません。外側のブロックはOTHERS例外ハンドラを使用して例外を処理します。

例11-13 有効範囲を超えて伝播した例外が処理される場合

CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
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;

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
END;
/

ユーザー定義の例外が外側のブロックで処理されない場合は、例11-14に示すとおり、エラーが発生します。

例11-14 有効範囲を超えて伝播した例外が処理されない場合

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

注意:

例外はリモート・サブプログラム起動には伝播しません。そのため、PL/SQLブロックは、リモート・サブプログラムによって呼び出された例外を処理できません。

ここでのトピック

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

宣言の中で呼び出された例外はただちに外側のブロック(外側のブロックがない場合は起動元またはホスト環境)に伝播します。したがって、例外ハンドラは宣言と同じブロックの中ではなく、外側または起動元のブロックに存在する必要があります。

例11-15では、VALUE_ERRORが呼び出される宣言と同じブロックの中にVALUE_ERROR例外ハンドラが存在しています。例外はただちにホスト環境に伝播するため、この例外は例外ハンドラで処理されません。

例11-15 宣言の中で呼び出された例外が処理されない場合

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-16例11-15と似ていますが、内側のブロックの中の宣言で呼び出されるVALUE_ERROR例外が外側のブロックで処理される点が異なります。

例11-16 宣言の中で呼び出された例外が外側のブロックで処理される場合

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-17では、nが0(ゼロ)の場合、計算1/nで事前定義の例外ZERO_DIVIDEが呼び出され、同じブロックの中のZERO_DIVIDE例外ハンドラに制御が移ります。例外ハンドラでZERO_DIVIDEが呼び出されると、例外はただちに起動元に伝播します。起動元で例外が処理されないため、PL/SQLはホスト環境に未処理例外エラーを戻します。

例11-17 例外ハンドラの中で呼び出された例外が処理されない場合

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-18例11-17に似ていますが、プロシージャが起動元に未処理例外エラーを戻すと起動元でエラーが処理される点が異なります。

例11-18 例外ハンドラの中で呼び出された例外が起動元で処理される場合

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-19例11-17と似ていますが、内側のブロックの中の例外ハンドラで呼び出される例外が外側のブロックで処理される点が異なります。

例11-19 例外ハンドラの中で呼び出された例外が外側のブロックで処理される場合

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-20では、ユーザー定義の例外i_is_oneと事前定義の例外ZERO_DIVIDEに対応する例外ハンドラがプロシージャの例外処理部にあります。i_is_one例外ハンドラでZERO_DIVIDEが呼び出されると、例外はただちに起動元に伝播します(そのため、ZERO_DIVIDE例外ハンドラでは例外が処理されません)。起動元で例外が処理されないため、PL/SQLはホスト環境に未処理例外エラーを戻します。

例11-20 例外ハンドラの中で呼び出された例外が処理されない場合

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-21例11-20と似ていますが、i_is_one例外ハンドラで呼び出されるZERO_DIVIDE例外が外側のブロックで処理される点が異なります。

例11-21 例外ハンドラの中で呼び出された例外が外側のブロックで処理される場合

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は起動元またはホスト環境に未処理例外エラーを戻します。その結果は戻す場所によって異なります。

サブプログラムが未処理例外で終了した場合、デフォルトで値によって渡されるOUTおよびIN OUT仮パラメータに対応する実パラメータには、サブプログラムを起動する前の値が保持されます(例8-15を参照)。

ストアド・サブプログラムが未処理例外で終了した場合には、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-22に示すとおり、これらの値をまずローカル変数に代入します。

例11-22 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;
/

結果:

Error code 100: ORA-01403: no data found

関連項目:

サブプログラムがouterスコープの例外ハンドラから呼び出された場合でも、例外が発生した場所のコール・スタックを表示するDBMS_UTILITY.FORMAT_ERROR_BACKTRACE関数の詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。

例外処理後に実行を続ける方法

例外ハンドラが実行されると、外側のブロック(外側のブロックがない場合は起動元またはホスト環境)の次の文に制御が移ります。例外ハンドラは、そのハンドラが存在するブロックに制御を取り戻すことができません。

たとえば、例11-23では、SELECT INTO文でZERO_DIVIDEが呼び出され、例外ハンドラがこれを処理した後に、SELECT INTO文に続くINSERT文から続けて実行することができません。

例11-23 例外ハンドラの実行と実行の終了

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.

SELECT INTO文に続くINSERT文から実行を再開する必要がある場合は、例11-24に示すとおり、独自のZERO_DIVIDE例外ハンドラを持つ内側のブロックにSELECT INTO文を配置します。

例11-24 例外ハンドラの実行と実行の継続

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.

関連項目:

例12-13では、例外が発生してもバルクSQL操作が継続されます。

例外処理後のトランザクションの再試行

例外を呼び出したトランザクションを例外処理後に再試行するには、次の方法を使用します。

  1. 例外処理部を持つサブブロックにトランザクションを入れます。

  2. サブブロックの中で、トランザクションを開始する前にセーブポイントをマークします。

  3. サブブロックの例外処理部の中で、セーブポイントまでロールバックする例外ハンドラを配置し、問題の修正を試行します。

  4. LOOP文の中にサブブロックを配置します。

  5. サブブロックの中で、トランザクションを終了させるCOMMIT文の後にEXIT文を配置します。

    トランザクションの実行に成功すると、COMMIT文とEXIT文が実行されます。

    トランザクションの実行に失敗すると、サブプログラムの例外処理部に制御が移り、例外ハンドラが実行されてからループが繰り返されます。

例11-25では、res_nameの値が一意でない場合に事前定義の例外DUP_VAL_ON_INDEXINSERT文により呼び出されるトランザクションを、前述の方法を使用して再試行しています。

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

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.