この章では、PL/SQLプログラムで使用できる、柔軟なエラー・トラップとエラー処理について説明します。
PL/SQLにおけるエラー処理と例外の詳細は、『Oracle Database PL/SQL言語リファレンス』のPL/SQLエラーの処理に関する説明を参照してください。
TimesTen固有の考慮事項の詳細は、章の最後を参照してください。
この章の内容は次のとおりです。
この項では、PL/SQLプログラミングにおける例外の概要を説明します。内容は次のとおりです。
例外とは、プログラムの実行中に(TimesTenによって暗黙的に、またはプログラムによって明示的に)呼び出されるPL/SQLエラーのことです。例外を処理するには、ハンドラでトラップするか、またはコール元の環境に伝播します。
たとえば、SELECT文が複数の行を戻す場合、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 Databaseと同様に使用されます。
| 例外 | 説明 | 処理方法 |
|---|---|---|
| 事前定義のTimesTenエラー | PL/SQLコードで最もよく発生する約20個のエラーの1つ | これらの例外は、宣言する必要がありません。TimesTenで事前に定義されています。エラーはTimesTenによって暗黙的に呼び出されます。 |
| 未定義のTimesTenエラー | その他の標準のTimesTenエラー | これらは、アプリケーションの宣言部で宣言する必要があります。エラーはTimesTenによって暗黙的に呼び出され、例外ハンドラを使用して捕捉できます。 |
| ユーザー定義のエラー | アプリケーションによって定義され、呼び出されるエラー | これらは、宣言部で宣言する必要があります。開発者が明示的に例外を呼び出します。 |
この項では、事前定義のTimesTenエラーまたはユーザー定義のエラーをトラップする方法について説明します。
事前定義のTimesTenエラーをトラップするには、例外処理ルーチンで事前定義の名前を参照します。PL/SQLでは、STANDARDパッケージで事前定義例外が宣言されています。
表4-1に、TimesTenでサポートされている事前定義例外、関連付けられているORAエラー番号とSQLCODE値、および例外の説明を示します。
「サポートされていない事前定義エラー」も参照してください。
表4-1 事前定義例外
| 例外名 | Oracle Databaseエラー番号 | SQLCODE | 説明 |
|---|---|---|---|
|
|
|
-6530 |
初期化されていないオブジェクトの属性に値を割り当てようとしました。 |
|
|
|
-6592 |
|
|
|
|
-6531 |
EXISTS以外のコレクション・メソッドを初期化されていないネストした表やVARRAYに適用しようとしたか、または初期化されていないネストした表やVARRAYの要素に値を割り当てようとしました。 |
|
|
|
-6511 |
すでにオープンされているカーソルをオープンしようとしました。 |
|
|
|
-1 |
プログラムは一意索引によって制約されている列に重複値を挿入しようとしました。 |
|
|
|
-1001 |
不正なカーソル操作があります。 |
|
|
|
-1722 |
文字列から数値への変換に失敗しました。 |
|
|
|
+100 |
単一行の |
|
|
|
-6501 |
PL/SQLに内部的な問題が発生しました。 |
|
|
|
-6504 |
代入文のホスト・カーソル変数とPL/SQLカーソル変数の戻り型に互換性がありません。 |
|
|
|
-6500 |
PL/SQLのメモリーが不足しているか、メモリーが破損しています。 |
|
|
|
-6533 |
コレクション内の要素の数より大きい索引番号を使用して、ネストした表またはVARRAYを参照しました。 |
|
|
|
-6532 |
有効範囲外の索引番号(たとえば-1)を使用して、ネストした表またはVARRAYを参照しました。 |
|
|
|
-1410 |
文字列が |
|
|
|
-1422 |
単一行 |
|
|
|
-6502 |
算術、変換、切捨てまたはサイズ制限のエラーが発生しました。 |
|
|
|
-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 Databaseでエラー条件が同じ場合、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.
ttIsqlでshow errorsコマンドを使用すると、無名ブロックの実行時、あるいはパッケージ、プロシージャまたはファンクションのコンパイル時に発生するエラーの詳細を表示できます。この例を例4-4に示します。
例4-4 ttIsqlのエラー表示コマンド
もう一度、例2-17を考えてみます。そこで示されていたのと同じパッケージ仕様(プロシージャおよびファンクションhire_employee、remove_employeeおよびnum_above_salaryを宣言)を想定します。ただし、そこで示されていた本体の定義のかわりに、次のようにhire_employeeおよびnum_above_salary(remove_employeeは除外)の定義を検討します。
CREATE OR REPLACE PACKAGE BODY emp_actions AS
-- Code for procedure hire_employee:
PROCEDURE hire_employee (employee_id NUMBER,
last_name VARCHAR2,
first_name VARCHAR2,
email VARCHAR2,
phone_number VARCHAR2,
hire_date DATE,
job_id VARCHAR2,
salary NUMBER,
commission_pct NUMBER,
manager_id NUMBER,
department_id NUMBER) IS
BEGIN
INSERT INTO employees VALUES (employee_id,
last_name,
first_name,
email,
phone_number,
hire_date,
job_id,
salary,
commission_pct,
manager_id,
department_id);
END hire_employee;
-- Code for function num_above_salary:
FUNCTION num_above_salary (emp_id NUMBER) RETURN NUMBER IS
emp_sal NUMBER(8,2);
num_count NUMBER;
BEGIN
SELECT salary INTO emp_sal FROM employees
WHERE employee_id = emp_id;
SELECT COUNT(*) INTO num_count FROM employees
WHERE salary > emp_sal;
RETURN num_count;
END num_above_salary;
END emp_actions;
/
元々のパッケージ仕様の後でこの本体の定義を試みると、次のようになります。
Warning: Package body created with compilation errors.
さらに情報を取得するには、ttIsqlを実行して、コマンドshow errorsを使用します。この例では、show errorsの結果は次のようになります。
Command> show errors; Errors for PACKAGE BODY EMP_ACTIONS: LINE/COL ERROR -------- ----------------------------------------------------------------- 13/13 PLS-00323: subprogram or cursor 'REMOVE_EMPLOYEE' is declared in a package specification and must be defined in the package body
TimesTen PL/SQLとOracle Database PL/SQLで異なるエラー関連の動作に注意する必要があります。
トランザクションの途中でPL/SQLが実行され、PL/SQLの実行中に未処理例外が発生した場合、TimesTen PL/SQLの処理は、Oracle Database PL/SQLでの処理と異なります。Oracle Databaseは無名ブロックの先頭までロールバックします。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> select * from mytable; 0 rows found.
結果はOracle Databaseの場合と同様で、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つの挿入の実行を指示します。
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 Databaseで実行すると、PL/SQLブロックの先頭にロールバックされるため、SELECTの結果は最初の挿入のみの実行を示します。
ORA-00001: unique constraint (SYSTEM.SYS_C004423) violated
ORA-06512: at line 3
NUM
----------
1
|
注意:
|
エラー条件が同じ場合、TimesTenでは、TimesTenが戻すエラー・メッセージとOracle Databaseが戻すメッセージが同じになることは保証されませんが、SQLコードは同じになります。そのため、SQLERRMファンクションによって戻される情報は異なる場合がありますが、SQLCODEファンクションによって戻される情報は同じになります。
詳細は、次の項または例を参照してください。
例4-2では、SQLERRMおよびSQLCODEを使用します。
特定のTimesTenエラー・メッセージの詳細は、『Oracle TimesTen In-Memory Databaseエラー・メッセージおよびSNMPトラップ』の警告とエラーに関する説明を参照してください。
一般的な情報は、『Oracle Database PL/SQL言語リファレンス』のSQLERRMファンクションおよびSQLCODEファンクションに関する説明を参照してください。
Oracle Databaseには実行時警告の概念がないため、Oracle Database PL/SQLでは警告はサポートされていません。
TimesTenには警告の概念がありますが、TimesTen PL/SQLの実装はOracle Database PL/SQLの実装に基づいているため、TimesTen PL/SQLでは警告はサポートされていません。
その結果、TimesTenではSQL文を実行してその結果発生する警告を表示できますが、同じ文をPL/SQLから実行した場合には、警告は表示されません。
「事前定義のTimesTenエラーのトラップ」には、TimesTenでサポートされている事前定義例外、関連付けられているORAエラー番号とSQLCODE値、および例外の説明が示されています。
表4-2に、TimesTenでサポートされていない事前定義例外を示します。
表4-2 TimesTenでサポートされていない事前定義例外
| 例外名 | Oracle Databaseエラー番号 | SQLCODE | 説明 |
|---|---|---|---|
|
|
|
-1017 |
ユーザー名およびパスワードが無効です。 |
|
|
|
-1012 |
プログラムがデータベースに接続せずにデータベース・コールを発行しました。 |
|
|
|
-30625 |
プログラムが |
|
|
|
-51 |
データベースでリソースを待機している間に、タイムアウトが発生しました。 |
TimesTen PL/SQLの実装では、PL/SQLプログラムのコンパイル時に、Oracle Database SQLパーサーが使用されます。(これは、「TimesTenにおけるPL/SQLとOracle DatabaseにおけるPL/SQL」で説明されています。)そのため、TimesTenでサポートされていないOracle Databaseの構文または組込みプロシージャをプログラムで使用しても、コンパイル中は問題が検出されません。ただし、プログラムの実行中に実行時エラーが発生します。
TimesTen SQLには、Oracle Database SQLにはない構造がいくつか含まれています。PL/SQL言語には、これらの構造は含まれていません。PL/SQLからTimesTen固有のSQLを実行するには、EXECUTE IMMEDIATE文を使用してSQL文を実行します。これによりコンパイル・エラーを回避します。
TimesTen固有のSQLおよび式のリストについては、『Oracle TimesTen Application-Tier Database Cacheユーザーズ・ガイド』のTimesTenおよびOracle Databases間の互換性に関する説明を参照してください。
EXECUTE IMMEDIATEの詳細は、「PL/SQLにおける動的SQL(EXECUTE IMMEDIATE文)」を参照してください。