この章では、PL/SQLプログラムで使用できる、柔軟なエラー・トラップとエラー処理について説明します。
PL/SQLにおけるエラー処理と例外の詳細は、『Oracle Database PL/SQL言語リファレンス』のPL/SQLエラーの処理に関する説明を参照してください。
TimesTen固有の考慮事項の詳細は、章の最後を参照してください。
この章の内容は次のとおりです。
この項では、PL/SQLプログラミングにおける例外の概要を説明します。内容は次のとおりです。
例外とは、プログラムの実行中に(TimesTenによって暗黙的に、またはプログラムによって明示的に)呼び出されるPL/SQLエラーのことです。 例外を処理するには、ハンドラでトラップするか、またはコール元の環境に伝播します。
たとえばSELECT文で2つ以上の行が戻されると、TimesTenでは実行時にエラー(例外)が戻されます。 次の例に示すように、TimesTenエラー8507が表示された後、関連付けられているORAエラー・メッセージが表示されます (本来Oracle Database用に定義されているORAメッセージは、TimesTenでも同様に実装されます)。
Command> DECLARE
       >  v_lname VARCHAR2 (15);
       > BEGIN
       >  SELECT last_name INTO v_lname
       >  FROM employees
       >  WHERE first_name = 'John';
       >   DBMS_OUTPUT.PUT_LINE ('Last name is :' || v_lname);
       >  END;
       >  /
 8507: ORA-01422: exact fetch returns more than requested number of rows
 8507: ORA-06512: at line 4
The command failed.
プログラムが正常に完了するように、このような例外をPL/SQLブロックで処理できます。次に例を示します。
Command> DECLARE
       >  v_lname VARCHAR2 (15);
       > BEGIN
       >  SELECT last_name INTO v_lname
       >  FROM employees
       >  WHERE first_name = 'John';
       >   DBMS_OUTPUT.PUT_LINE ('Last name is :' || v_lname);
       > EXCEPTION
       >   WHEN TOO_MANY_ROWS THEN
       >   DBMS_OUTPUT.PUT_LINE (' Your SELECT statement retrieved multiple
       >   rows. Consider using a cursor.');
       > END;
       > /
 Your SELECT statement retrieved multiple rows. Consider using a cursor.
PL/SQL procedure successfully completed.
例外には、次の3つのタイプがあります。
事前定義例外: PL/SQLで定義されているエラー条件です。
未定義例外: 標準のTimesTenエラーなどがあります。
ユーザー定義例外: アプリケーション固有の例外です。
TimesTenでは、この3つのタイプの例外をOracleの場合と同じ方法で使用します。
| 例外 | 説明 | 処理方法 | 
|---|---|---|
| 事前定義のTimesTenエラー | PL/SQLコードで最もよく発生する約20個のエラーの1つ | これらの例外は、宣言する必要がありません。 TimesTenで事前に定義されています。 エラーはTimesTenによって暗黙的に呼び出されます。 | 
| 未定義のTimesTenエラー | その他の標準のTimesTenエラー | アプリケーションの宣言部で宣言する必要があります。 エラーはTimesTenによって暗黙的に呼び出され、例外ハンドラを使用して捕捉できます。 | 
| ユーザー定義のエラー | アプリケーションによって定義され、呼び出されるエラー | 宣言部で宣言する必要があります。 開発者が明示的に例外を呼び出します。 | 
この項では、事前定義のTimesTenエラーまたはユーザー定義のエラーをトラップする方法について説明します。
事前定義のTimesTenエラーをトラップするには、例外処理ルーチンで事前定義の名前を参照します。 PL/SQLでは、STANDARDパッケージで事前定義例外が宣言されています。
表4-1に、TimesTenでサポートされている事前定義例外、関連付けられているORAエラー番号とSQLCODE値、および例外の説明を示します。
「サポートされていない事前定義エラー」も参照してください。
表4-1 事前定義例外
| 例外名 | Oracleエラー番号 | SQLCODE | 説明 | 
|---|---|---|---|
| ACCESS_INTO_NULL | ORA-06530 | -6530 | 初期化されていないオブジェクトの属性に値を割り当てようとしました。 | 
| CASE_NOT_FOUND | ORA-06592 | -6592 | CASE文のWHEN句で何も選択されておらず、ELSE句もありません。 | 
| COLLECTION_IS_NULL | ORA-06531 | -6531 | EXISTS以外のコレクション・メソッドを初期化されていないネストした表やVARRAYに適用しようとしたか、または初期化されていないネストした表やVARRAYの要素に値を割り当てようとしました。 | 
| CURSOR_ALREADY_OPENED | ORA-06511 | -6511 | すでにオープンされているカーソルをオープンしようとしました。 | 
| DUP_VAL_ON_INDEX | ORA-00001 | -1 | 一意索引によって制約されている列に重複値を挿入しようとしました。 | 
| INVALID_CURSOR | ORA-01001 | -1001 | 無効なカーソル操作です。 | 
| INVALID_NUMBER | ORA-01722 | -1722 | 文字列から数値への変換に失敗しました。 | 
| NO_DATA_FOUND | ORA-01403 | +100 | 単一行のSELECTによって行が戻されなかったか、またはプログラムによって、ネストした表内の削除された要素や連想配列(索引付き表)内の初期化されていない要素が参照されました。 | 
| PROGRAM_ERROR | ORA-06501 | -6501 | PL/SQLに内部的な問題が発生しました。 | 
| ROWTYPE_MISMATCH | ORA-06504 | -6504 | 代入文のホスト・カーソル変数とPL/SQLカーソル変数の戻り型に互換性がありません。 | 
| STORAGE_ERROR | ORA-06500 | -6500 | PL/SQLのメモリーが不足しているか、メモリーが破損しています。 | 
| SUBSCRIPT_BEYOND_COUNT | ORA-06533 | -6533 | コレクション内の要素の数より大きい索引番号を使用して、ネストした表またはVARRAYを参照しました。 | 
| SUBSCRIPT_OUTSIDE_LIMIT | ORA-06532 | -6532 | 有効範囲外の索引番号(たとえば-1)を使用して、ネストした表またはVARRAYを参照しました。 | 
| SYS_INVALID_ROWID | ORA-01410 | -1410 | 文字列が値ROWIDを表していないため、ユニバーサルROWIDへの文字列の変換に失敗しました。 | 
| TOO_MANY_ROWS | ORA-01422 | -1422 | 単一行のSELECTによって、2つ以上の行が戻されました。 | 
| VALUE_ERROR | ORA-06502 | -6502 | 算術、変換、切捨てまたはサイズ制限のエラーが発生しました。 | 
| ZERO_DIVIDE | ORA-01476 | -1476 | 数値を0(ゼロ)で割ろうとしました。 | 
例4-1 事前定義例外ZERO_DIVIDEの使用
この例のPL/SQLプログラムでは、数値を0で割ろうとします。事前定義例外ZERO_DIVIDEを使用して、例外処理ルーチン内でエラーをトラップします。
Command> DECLARE v_invalid PLS_INTEGER;
       > BEGIN
       >   v_invalid := 100/0;
       > EXCEPTION
       > WHEN ZERO_DIVIDE THEN
       >   DBMS_OUTPUT.PUT_LINE ('Attempt to divide by 0');
       > END;
       >  /
Attempt to divide by 0
PL/SQL procedure successfully completed.
TimesTenでPL/SQLを使用して、ユーザー独自の例外を定義できます。 ユーザー定義例外は、PL/SQLのRAISE文またはRAISE_APPLICATION_ERRORプロシージャで明示的に呼び出すことができます。
RAISE文を使用すると、PL/SQLブロックまたはサブプログラムの通常の実行を停止し、制御を例外ハンドラに渡すことができます。 RAISE文では、事前定義例外または自分で名前を付けたユーザー定義例外を呼び出すことができます。
この例では、部門番号500は存在しないため、departments表内の行は更新されません。 RAISE文を使用して例外を明示的に呼び出し、SQLERRM組込みファンクションによって戻されるエラー・メッセージとSQLCODE組込みファンクションによって戻されるエラー・コードを表示します。 例外ハンドラ内で、RAISE文を単独で使用して同じ例外を再度呼び出し、コール元の環境に伝播します。
Command> DECLARE
       >   v_deptno NUMBER := 500;
       >   v_name VARCHAR2 (20) := 'Testing';
       >   e_invalid_dept EXCEPTION;
       >  BEGIN
       >   UPDATE departments
       >   SET department_name = v_name
       >   WHERE department_id = v_deptno;
       >  IF SQL%NOTFOUND THEN
       >     RAISE e_invalid_dept;
       >  END IF;
       >  ROLLBACK;
       >  EXCEPTION
       >    WHEN e_invalid_dept THEN
       >    DBMS_OUTPUT.PUT_LINE ('No such department');
       >    DBMS_OUTPUT.PUT_LINE (SQLERRM);
       >    DBMS_OUTPUT.PUT_LINE (SQLCODE);
       > END;
       > /
No such department
User-Defined Exception
1
PL/SQL procedure successfully completed.
The command succeeded.
| 注意:TimesTenとOracleでエラー条件が同じ場合、SQLCODEによって戻されるエラー・コードは同じですが、SQLERRMによって戻されるエラー・メッセージは同じとはかぎりません。このことについては、「TimesTenエラー・メッセージおよびSQLコード」でも説明しています。 | 
RAISE_APPLICATION_ERRORプロシージャは、PL/SQLプログラムの実行可能セクションまたは例外セクション、あるいはその両方で使用します。 TimesTenによってアプリケーションにエラーがレポートされるため、未処理例外を戻すのを回避できます。
-20,000から-20,999の範囲のエラー番号を使用します。メッセージには最大2,048バイトの文字列を指定します。
例4-3 RAISE_APPLICATION_ERRORプロシージャの使用
この例では、employees表からlast_name=Pattersonのデータを削除しようとします。 RAISE_APPLICATION_ERRORプロシージャで、エラー番号-20201を使用してエラーを呼び出します。
Command> DECLARE
       >   v_last_name employees.last_name%TYPE := 'Patterson';
       > BEGIN
       > DELETE FROM employees WHERE last_name = v_last_name;
       > IF SQL%NOTFOUND THEN
       >   RAISE_APPLICATION_ERROR (-20201, v_last_name || ' does not exist');
       > END IF;
       > END;
       > /
 8507: ORA-20201: Patterson does not exist
 8507: ORA-06512: at line 6
The command failed.
TimesTen PL/SQLとOracle PL/SQLで異なるエラー関連の動作に注意する必要があります。
トランザクションの途中でPL/SQLが実行され、PL/SQLの実行中に未処理例外が発生した場合、TimesTen PL/SQLの処理は、Oracle PL/SQLでの処理と異なります。 Oracleでは、無名ブロックの先頭にロールバックします。 TimesTenではロールバックしません。
次の例に示すように、PL/SQLブロックの実行の結果発生した例外を常にアプリケーションで処理し、自動コミットを無効にしてアプリケーションを実行する必要があります。
create table mytable (num int not null primary key);
set serveroutput on
insert into mytable values(1);
begin
 insert into mytable values(2);
 insert into mytable values(1);
exception
 when dup_val_on_index then
  dbms_output.put_line('oops:' || sqlerrm);
  rollback;
end;
/
select * from mytable;
commit;
値は一意である必要があるため、2つ目のINSERTは失敗します。そのため例外が発生し、ロールバックが実行されます。 このことをTimesTenで実行すると、次のような結果になります。
oops:TT0907: Unique constraint (MYTABLE) violated at Rowid <BMUFVUAAABQAAAADjq> PL/SQL procedure successfully completed. select * from mytable; 0 rows found.
結果はOracleでも同様で、SELECTによって行は表示されません。
ここで、例外が処理されないTimesTenの例を考えてみます。自動コミットを無効にし、この例を再実行します。
create table mytable (num int not null primary key); set serveroutput on insert into mytable values(1); begin insert into mytable values(2); insert into mytable values(1); end; / select * from mytable; commit;
TimesTenでは、SELECTは最初の2つのINSERTの実行を指示します。
907: Unique constraint (MYTABLE) violated at Rowid <BMUFVUAAABQAAAADjq> 8507: ORA-06512: at line 3 The command failed. select * from mytable; < 1 > < 2 > 2 rows found.
これをOracleで実行すると、PL/SQLブロックの先頭にロールバックされるため、SELECTは最初のINSERTのみの実行を指示します。
ORA-00001: unique constraint (SYSTEM.SYS_C004423) violated
ORA-06512: at line 3
       NUM
----------
         1
 | 注意: 
 | 
エラー条件が同じでも、TimesTenによって戻されるエラー・メッセージと、Oracleによって戻されるメッセージは同じとはかぎりません。ただし、SQLコードは同じです。 したがって、SQLERRMファンクションによって戻される情報は異なる場合がありますが、SQLCODEファンクションによって戻される情報は同じです。
詳細は、次の項または例を参照してください。
例4-2「RAISE文を使用したユーザー定義例外のトラップ」では、SQLERRMとSQLCODEを使用しています。
特定のTimesTenエラー・メッセージの詳細は、『Oracle TimesTen In-Memory Databaseエラー・メッセージおよびSNMPトラップ』の警告とエラーに関する説明を参照してください。
一般情報およびOracle固有の情報の詳細は、『Oracle Database PL/SQL言語リファレンス』のSQLERRMファンクションおよびSQLCODEファンクションに関する説明を参照してください。
Oracle Databaseには実行時警告の概念がないため、Oracle PL/SQLでは実行時警告の概念はサポートされていません。
TimesTen In-Memory Databaseには警告の概念がありますが、TimesTen PL/SQLの実装はOracle PL/SQLの実装に基づいているため、TimesTen PL/SQLでは警告はサポートされていません。
その結果、TimesTenではSQL文を実行してその結果発生する警告を表示できますが、同じ文をPL/SQLから実行した場合には、警告は表示されません。
「事前定義のTimesTenエラーのトラップ」には、TimesTenでサポートされている事前定義例外、関連付けられているORAエラー番号とSQLCODE値、および例外の説明が示されています。
表4-2に、TimesTenでサポートされていない事前定義例外を示します。
表4-2 TimesTenでサポートされていない事前定義例外
| 例外名 | Oracleエラー番号 | SQLCODE | 説明 | 
|---|---|---|---|
| LOGIN_DENIED | ORA-01017 | -1017 | ユーザー名とパスワードが無効です。 | 
| NOT_LOGGED_ON | ORA-01012 | -1012 | データベースに接続せずにデータベース・コールが発行されました。 | 
| SELF_IS_NULL | ORA-30625 | -30625 | MEMBERメソッドを起動しようとしましたが、オブジェクトのインスタンスが初期化されていませんでした。 | 
| TIMEOUT_ON_RESOURCE | ORA-00051 | -51 | データベースでリソースを待機している間に、タイムアウトが発生しました。 | 
TimesTen PL/SQLの実装では、PL/SQLプログラムのコンパイル時に、Oracle SQLパーサーが使用されます。 そのため、TimesTenでサポートされていないOracle構文またはOracle組込みファンクションをプログラムで使用しても、コンパイル中は問題が検出されません。 ただし、プログラムの実行中に実行時エラーが発生します。