プライマリ・コンテンツに移動
Pro*C/C++プログラマーズ・ガイド
12c リリース1(12.1)
B71397-03
目次へ移動
目次
索引へ移動
索引

前
次

WHENEVERディレクティブの使用について

デフォルトでは、プリコンパイルされたプログラムはOracleエラーおよび警告の状態を無視し、可能であれば処理を続行します。自動条件チェックおよびエラー処理を実行するにはWHENEVERディレクティブが必要です。

WHENEVERディレクティブによって、OracleでエラーやSQLERROR、SQLWARNINGまたはNOT FOUND条件が検出されたときのアクションを指定できます。これらのアクションには、次の文の継続実行、ルーチンのコール、ラベル付きの文への分岐、停止などがあります。

WHENEVERディレクティブの構文は次のとおりです。

EXEC SQL WHENEVER <condition> <action>; 

WHENEVERの条件

Oracleに自動的にSQLCAをチェックさせて、次の状態が存在しないかどうかを調べることができます。

SQLWARNING

sqlwarn[0]は、Oracleから警告(sqlwarn[1]からsqlwarn[7]までのいずれか1つも設定されます)が戻されたか、SQLCODEが+1403以外の正の値になっているために設定されます。たとえば、切り捨てられた列値が出力ホスト変数に割り当てられると、sqlwarn[0]が設定されます。

MODE=ANSIのときは、SQLCAの宣言はオプションです。ただし、WHENEVER SQLWARNINGを使用するには、必ずSQLCAを宣言してください。

SQLERROR

Oracleがエラーを戻したことにより、SQLCODEに負の値が設定されています。

NOT FOUND

OracleがWHERE句の検索条件を満たす行を検出できなかったか、SELECT INTOまたはFETCHが行を戻さなかったため、SQLCODEに+1403が設定されています(MODE=ANSIのときは+100)。

MODE=ANSIのときは、どの行もINSERTできなければ+100がSQLCODEに戻されます。

WHENEVERのアクション

Oracleで前述の状態のいずれかが検出されたときは、プログラムに次のいずれかのアクションを実行させることができます。

CONTINUE

可能であれば、プログラムは次の文からの実行を継続します。これはデフォルトの動作で、WHENEVERディレクティブを使用しない場合と同じです。このアクションを使用すると条件チェックを終了できます。

DO

制御をプログラム中のエラー処理関数に移します。ルーチンの最後に達すると、制御は失敗したSQL文の次の文に移ります。

関数の入力および終了について、通常の規則が適用されます。EXEC SQL WHENEVER ... DO ...ディレクティブで起動されるエラー・ハンドラにパラメータを渡し、関数によって値を戻すことができます。

DO BREAK

実際のbreak文はプログラム中に置かれます。このアクションはループ内で使用します。WHENEVER条件が成立すると、プログラムはそのループを抜けます。

DO CONTINUE

実際のcontinue文がプログラム中に置かれます。このアクションはループ内で使用します。WHENEVER条件が成立すると、プログラムはそのループの次の反復に移ります。

GOTO label_name

プログラムはラベル付き文に分岐します。ラベル名の長さに制限はありませんが、有効なのは最初の31文字のみです。異なる最大長を必要とするCコンパイラもあります。使用しているCコンパイラのユーザーズ・ガイドを参照してください。

STOP

プログラムは実行を停止し、COMMITされていない作業がロールバックされます。

STOPが実際に行うのは、WHENEVER条件発生時のexit()コールの生成のみです。注意してください。STOPアクションは、Oracleからの切断前に何もメッセージを表示しません。

WHENEVERの例

たとえば、プログラムで次のアクションが必要だとします。

  • 「データが見つかりません」という条件が発生したときには、close_cursorに進みます。

  • 警告が発生したときは、次の文を続行します。

  • エラーが発生したときは、error_handlerに進みます。

最初の実行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 */ 
        ... 
    } 
    else 
    {   ...
    } 
    ... 
} 

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内のlabelAfunc2内の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ディレクティブがないことを確認してください。