例外処理の概要

例外(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という名前の例外を宣言できます。

    ユーザー定義の例外は明示的に呼び出す必要があります。

    詳細は、「ユーザー定義の例外」を参照してください。

表12-2に、例外のカテゴリの概要を示します。

表12-2 例外のカテゴリ

カテゴリ 定義者 エラー・コード 名前 暗黙的な呼出し 明示的な呼出し

内部的定義

ランタイム・システム

常に

ユーザーが割り当てた場合のみ

あり

オプション脚注1

事前定義

ランタイム・システム

常に

常に

あり

オプション脚注1

ユーザー定義

ユーザー

ユーザーが割り当てた場合のみ

常に

なし

常に

脚注1

詳細は、「内部的に定義された例外のRAISE文による呼出し」を参照してください。

名前付き例外の場合は、OTHERS例外ハンドラを使用して処理するかわりに、専用の例外ハンドラを記述できます。OTHERS例外ハンドラの場合は、処理しようとしている例外をファンクションを起動して決定する必要があるため、専用の例外ハンドラの方が効率的です。詳細は、「エラー・コードとエラー・メッセージの取得」を参照してください。

例外ハンドラのメリット

エラー処理に例外ハンドラを使用すると、プログラムの作成および理解が容易になり、未処理例外が発生する可能性が減少します。

例外ハンドラを使用しない場合は、発生する可能性のあるあらゆるエラーを、発生する可能性のあるあらゆる場所でチェックし、処理する必要があります。発生する可能性のあるエラーまたはエラーが発生する可能性のある場所というのは、ただちに検出できないエラーの場合(たとえば、不正なデータは、計算で使用するまで検出できない可能性があります)は特に見落としやすいものです。エラー処理コードはプログラムのいたるところに存在することになります。

例外ハンドラを使用する場合は、発生する可能性のあるあらゆるエラー、またはエラーが発生する可能性のあるあらゆる場所を把握する必要がありません。エラーが発生する可能性のある各ブロックに、例外処理部を含めるだけでかまいません。例外処理部には、特定のエラーおよび不明なエラーの両方の例外ハンドラを含めることができます。ブロック(サブブロックを含む)のどこかでエラーが発生すると、例外ハンドラにより処理されます。エラー処理コードは、ブロックの例外処理部に分離されます。

例12-3のプロシージャでは、1つの例外ハンドラを使用してNO_DATA_FOUNDという事前定義の例外を処理していますが、この例外は2つのSELECT INTO文のいずれでも発生する可能性があります。

複数の文で同じ例外ハンドラを使用していて、失敗した文がどれなのかを知る必要がある場合は、例12-4に示すとおり、ロケータ変数を使用できます。

エラー処理コードの精度はユーザーが決定します。すべての0(ゼロ)による除算エラー、不正な配列の索引などに対応する1つの例外ハンドラを持つことができます。独自の例外ハンドラを持つブロック内に単一の文を配置することによって、その文に含まれるエラーをチェックすることもできます。

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

例12-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である実パラメータ、行を戻さないまたは予想以上の行を戻す問合せがあります。不正な入力データの様々な組合せでコードをテストし、どのような潜在的なエラーが発生するかを調べます。

    エラー・チェック・コードを使用して、例外の発生を回避できる場合もあります(例12-7を参照)。

  • エラーが発生する可能性がある場所には常に、例外ハンドラを追加します。

    算術演算、文字列操作、データベース操作の間は、エラーが発生する可能性が特に高くなります。コードとは関係のない問題(ディスク記憶域やメモリーのハードウェアの障害など)が原因でエラーが発生することもありますが、コードには対処措置が必要です。

  • データベースが想定外の状態になった場合にもプログラムが動作するように設計します。

    たとえば、問い合せた表で列の追加や削除が行われていたり、列の型が変更されている場合があります。こうした問題を回避するには、スカラー変数を%TYPE修飾子で宣言し、問合せ結果を保持するレコード変数を%ROWTYPE修飾子で宣言します。

  • 可能な場合は必ず、OTHERS例外ハンドラを使用するかわりに、名前付き例外用の例外ハンドラを記述します。

    事前定義の例外の名前と原因を調べておいてください。名前のない内部的に定義された特定の例外がデータベース操作によって呼び出される可能性があるとわかっている場合は、その例外に名前を付け、それ専用の例外ハンドラを記述できるようにします。

  • 例外ハンドラからデバッグ情報が出力されるようにします。

    個別の表にデバッグ情報を格納する場合は、メイン・サブプログラムが実行した処理をロールバックする場合でもデバッグ情報をコミットできるように、自律型ルーチンを使用します。自立型ルーチンの詳細は、「AUTONOMOUS_TRANSACTIONプラグマ」を参照してください。

  • 各例外ハンドラについて、トランザクションをコミットさせるのか、ロールバックさせるのか、継続させるのかを慎重に考慮します。

    エラーの重大さに関係なく、データベースを一貫性のある状態に保ち、不正なデータの格納を避けるようにしてください。

  • すべてのPL/SQLプログラムの最上位にOTHERS例外ハンドラを配置し、未処理例外が発生しないようにします。

    OTHERS例外ハンドラの最後の文は、RAISEにするか、SUPPRESSES_WARNING_6009プラグマのマークを付けたサブルーチンの起動にします。(この方法に従わない場合は、PL/SQL警告が有効になり、PLW-06009が表示されます。)RAISEまたはRAISE_APPLICATION_ERRORの起動の詳細は、「例外の明示的な呼出し」を参照してください。