例外の伝播

例外ハンドラを持たないブロックで例外が呼び出された場合は、例外が伝播します。つまり、その例外のハンドラを持つブロックに至るか外側のブロックがなくなるまで、1つずつ外側のブロックに進んで例外が再生されます。その例外に対応するハンドラがない場合、PL/SQLは起動元またはホスト環境に未処理例外エラーを戻します。その結果は戻す場所によって異なります(詳細は「未処理例外」を参照してください)。

図12-1では、1つのブロックが別のブロックの内側にネストされています。内側のブロックが例外Aを呼び出します。内側のブロックにはAの例外ハンドラがあるため、Aは伝播しません。例外ハンドラが実行されると、外側のブロックの次の文に制御が移ります。

図12-1 伝播しない例外

図12-1の説明が続きます
「図12-1 伝播しない例外」の説明

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

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

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

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

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

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

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

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

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

ノート:

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

ここでのトピック

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

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

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

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

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

例12-17例12-16と似ていますが、内側のブロックの中の宣言で呼び出されるVALUE_ERROR例外が外側のブロックで処理される点が異なります。

例12-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: value or conversion error: number precision too large
ORA-06512: at line 2

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

例外ハンドラの中で呼び出された例外の伝播

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

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

例12-19例12-18に似ていますが、プロシージャが起動元に未処理例外エラーを戻すと起動元でエラーが処理される点が異なります。

例12-20例12-18と似ていますが、内側のブロックの中の例外ハンドラで呼び出される例外が外側のブロックで処理される点が異なります。

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

例12-22例12-21と似ていますが、i_is_one例外ハンドラで呼び出されるZERO_DIVIDE例外が外側のブロックで処理される点が異なります。

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

CREATE OR REPLACE 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

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

CREATE OR REPLACE 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.

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

CREATE OR REPLACE 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.

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

CREATE OR REPLACE 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

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

CREATE OR REPLACE 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