OracleがWHERE句の検索条件を満たす行を検出できなかったか、SELECT INTOまたはFETCHが行を戻さなかったため、SQLCODEに+1403が設定されています(MODE=ANSIのときは+100)。
MODE=ANSIのときは、どの行もINSERTできなければ+100がSQLCODEに戻されます。
最初の実行SQL文の前に次のWHENEVERディレクティブを指定する必要があります。
EXEC SQL WHENEVER NOT FOUND GOTO close_cursor;
EXEC SQL WHENEVER SQLWARNING CONTINUE;
EXEC SQL WHENEVER SQLERROR GOTO error_handler;
次の例では、WHENEVER...DOディレクティブを使用して特定のエラーを処理します。
EXEC SQL WHENEVER SQLERROR DO handle_insert_error("INSERT error");
EXEC SQL INSERT INTO emp (empno, ename, deptno)
VALUES (:emp_number, :emp_name, :dept_number);
EXEC SQL WHENEVER SQLERROR DO handle_delete_error("DELETE error");
EXEC SQL DELETE FROM dept WHERE deptno = :dept_number;
handle_insert_error(char *stmt)
{ switch(sqlca.sqlcode)
case -1:
/* duplicate key value */
break;
case -1401:
/* value too large */
break;
default:
/* do something here too */
break;
handle_delete_error(char *stmt)
printf("%s\n\n", stmt);
if (sqlca.sqlerrd[2] == 0)
/* no rows deleted */
{ ...
SQLCAの変数をチェックしてアクションの過程を決定する手順に注意してください。
DO BREAKとDO CONTINUEの利用
次の例では、コミッションを受け取っている従業員の分のみ、従業員の名前、給料、コミッションを表示する方法を示しています。
#include <sqlca.h>
#include <stdio.h>
main()
char *uid = "scott/tiger";
struct { char ename[12]; float sal; float comm; } emp;
/* Trap any connection error that might occur. */
EXEC SQL WHENEVER SQLERROR GOTO whoops;
EXEC SQL CONNECT :uid;
EXEC SQL DECLARE c CURSOR FOR
SELECT ename, sal, comm FROM EMP ORDER BY ENAME ASC;
EXEC SQL OPEN c;
/* Set up 'BREAK' condition to exit the loop. */
EXEC SQL WHENEVER NOT FOUND DO BREAK;
/* The DO CONTINUE makes the loop start at the next iteration when an error occurs.*/
EXEC SQL WHENEVER SQLERROR DO CONTINUE;
while (1)
EXEC SQL FETCH c INTO :emp;
/* An ORA-1405 would cause the 'continue' to occur. So only employees with */
/* non-NULL commissions will be displayed. */
printf("%s %7.2f %9.2f\n", emp.ename, emp.sal, emp.comm);
/* This 'CONTINUE' shuts off the 'DO CONTINUE' allowing the program to
proceed if any further errors do occur, specifically, with the CLOSE */
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL CLOSE c;
exit(EXIT_SUCCESS);
whoops:
printf("%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
exit(EXIT_FAILURE);
WHENEVER文の適用範囲
WHENEVER文は宣言部のため、そのスコープは論理的なものではなく位置的なものになります。つまり、WHENEVER文はプログラム・ロジックの流れではなく、ソース・ファイル内で物理的にWHENEVER文に続く実行SQL文をすべてテストします。したがって、WHENEVERディレクティブはテストする最初の実行SQL文の前に指定する必要があります。
あるWHENEVERディレクティブは、同じ条件をチェックする別のWHENEVERディレクティブに置き換えられるまでの間は有効です。
次の例では、最初のWHENEVER SQLERRORディレクティブは2番目のものに置き換えられます。したがって、このディレクティブの制御はCONNECT文のみに適用されます。2番目のWHENEVER SQLERRORディレクティブは、step1からstep3への制御の流れに関係なく、UPDATE文およびDROP文の両方に適用されます。
step1:
EXEC SQL WHENEVER SQLERROR STOP;
EXEC SQL CONNECT :username IDENTIFIED BY :password;
goto step3;
step2:
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL UPDATE emp SET sal = sal * 1.10;
step3:
EXEC SQL DROP INDEX emp_index;
WHENEVERのガイドライン
この項では、一般的な問題点を回避するためのガイドラインを示します。
通常、WHENEVERディレクティブはプログラムの最初の実行SQL文の前に指定します。この位置に指定したWHENEVERディレクティブはファイルの最後まで有効になるため、発生するすべてのエラーを確実にトラップできます。
データの終了条件の処理
カーソルを使用して行をフェッチするときは、プログラムでデータの終了条件を処理できるようにしておく必要があります。FETCHがデータを戻さないときは、プログラムは次のようにFETCHループを終了します。
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
EXEC SQL FETCH...
EXEC SQL CLOSE my_cursor;
行が挿入されていない場合、INSERTはNOT FOUNDを戻します。この条件を取り上げない場合は、INSERTの前にEXEC SQL WHENEVER NOT FOUND CONTINUEを使用します。
EXEC SQL WHENEVER NOT FOUND DO break;
for(;;)
EXEC SQL FETCH ...
EXEC SQL WHENEVER NOT FOUND CONTINUE;
EXEC SQL INSERT INTO ...
EXEC SQL CLOSE my_cursor;
無限ループの回避について
WHENEVER SQLERROR GOTOディレクティブが、実行SQL文を含むエラー処理ルーチンに分岐しているときに、そのSQL文にエラーが発生すると、プログラムが無限ループに陥るおそれがあります。無限ループを回避するには、次に示すようにSQL文の前にWHENEVER SQLERROR CONTINUEを記述します。
EXEC SQL WHENEVER SQLERROR GOTO sql_error;
sql_error:
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK RELEASE;
WHENEVER SQLERROR CONTINUE文を指定しなければ、ROLLBACKエラーが発生したときにこのルーチンが再び実行されるため、結果として無限ループに陥ります。
WHENEVER句は、注意して使用しないと問題が発生することがあります。たとえば、検索条件を満たす行がないためにDELETE文がNOT FOUNDを設定すると、次のコードは無限ループに陥ります。
/* improper use of WHENEVER */
EXEC SQL WHENEVER NOT FOUND GOTO no_more;
for (;;)
EXEC SQL FETCH emp_cursor INTO :emp_name, :salary;
no_more:
EXEC SQL DELETE FROM emp WHERE empno = :emp_number;
次の例では、GOTOのターゲットを再設定することで、NOT FOUND条件を適切に処理します。
/* proper use of WHENEVER */
EXEC SQL WHENEVER NOT FOUND GOTO no_more;
for (;;)
EXEC SQL FETCH emp_cursor INTO :emp_name, :salary;
no_more:
EXEC SQL WHENEVER NOT FOUND GOTO no_match;
EXEC SQL DELETE FROM emp WHERE empno = :emp_number;
no_match:
アドレス指定可能度の維持について
WHENEVER GOTOディレクティブによって制御されるすべてのSQL文が、必ずGOTOラベルに分岐するようにしてください。次のコードは、func1内のlabelAがfunc2内のINSERT文のスコープ内にないため、コンパイル時エラーが発生します。
func1()
EXEC SQL WHENEVER SQLERROR GOTO labelA;
EXEC SQL DELETE FROM emp WHERE deptno = :dept_number;
labelA:
func2()
EXEC SQL INSERT INTO emp (job) VALUES (:job_title);
WHENEVER GOTOディレクティブの分岐先のラベルは、この文と同じプリコンパイル・ファイル内にする必要があります。
エラー後の戻りについて
エラー処理後にプログラムに戻る必要がある場合は、DO routine_callアクションを使用します。または、次の例に示すように、sqlcodeの値をテストしてもかまいません。
EXEC SQL UPDATE emp SET sal = sal * 1.10;
if (sqlca.sqlcode < 0)
{ /* handle error */
EXEC SQL DROP INDEX emp_index;
アクティブなWHENEVER GOTOディレクティブまたはWHENEVER STOPディレクティブがないことを確認してください。