この章では、配列を使用してコーディングを簡略化し、プログラムのパフォーマンスを改善する方法について説明します。配列を使用してOracleデータを操作する方法、単一のSQL文を使用して配列内のすべての要素を処理する方法および処理対象となる配列の要素数を制限する方法を説明します。この章の項目は、次のとおりです。
配列を使用すると、プログラミングの所要時間が短縮され、パフォーマンスを改善できます。
配列により、単一のSQL文で配列全体を操作できます。このため、特にネットワーク環境では、Oracle通信のオーバーヘッドが著しく軽減されます。実行時間の大部分は、ネットワーク上でクライアント・プログラムとサーバー・データベース間のラウンドトリップとに費やされます。配列を使用すると、ラウンドトリップが減少します。
たとえば、およそ300人の従業員に関する情報をEMPという表に挿入する必要があるとします。配列がないと、プログラムは300の個々のINSERT(各従業員に1つ)を実行する必要があります。配列を使用すれば、INSERTの実行は1回で済みます。
次の例では3つのホスト配列を宣言するとともに、それぞれ要素の最大数を50に設定しています。
char emp_name[50][10]; int emp_number[50]; float salary[50];
VARCHARの配列も有効です。次の宣言は、有効なホスト言語宣言です。
VARCHAR v_array[10][30];
ホスト配列は、INSERT文、UPDATE文、DELETE文では入力変数として、またSELECT文およびFETCH文のINTO句では出力変数として使用できます。
ホスト配列に使用される埋込みSQL構文は、単純ホスト変数に使用される埋込みSQL構文とほとんど同じです。ただし、オプションのFOR句で配列処理を制御できるという点では違いがあります。また、ホスト配列と単純ホスト変数を単一のSQL文内で併用するときにも制限があります。
以降の項では、DML文中でホスト配列を使用する方法を説明します。
単一のSQL文で複数のホスト配列を使用する場合、要素の数は同じにする必要があります。同じでない場合には、プリコンパイル時に「配列サイズが一致しません」という警告メッセージが出ます。この警告を無視すると、プリコンパイラではSQL操作で最小数の要素が使用されます。
int emp_number[50];
char emp_name[50][10];
int dept_number[25];
/* Populate host arrays here. */
EXEC SQL INSERT INTO emp (empno, ename, deptno)
VALUES (:emp_number, :emp_name, :dept_number);
SQL文のホスト配列に添字を付け、それをループで使用することでデータを挿入またはフェッチできます。たとえば、次のようなループを使用して、配列内の5番目の要素ごとにINSERTできます。
for (i = 0; i < 50; i += 5)
EXEC SQL INSERT INTO emp (empno, deptno)
VALUES (:emp_number[i], :dept_number[i]);
ただし、処理する必要のある配列要素が連続している場合は、ループでホスト配列を処理しないでください。単に、添字の付いていない配列名をSQL文で使用してください。要素数nのホスト配列を含むSQL文は、n個の異なるスカラー変数を持つ同じSQL文としてn回実行するのと同様に扱われます。
インジケータ配列は、NULLを割り当ててホスト配列を入力し、出力ホスト配列でNULLまたは切り捨てられた値(文字列のみ)を検出する場合に使用できます。次の例は、インジケータ配列でINSERTを行う方法を示しています。
int emp_number[50];
int dept_number[50];
float commission[50];
short comm_ind[50]; /* indicator array */
/* Populate the host and indicator arrays. To insert a null
into the comm column, assign -1 to the appropriate
element in the indicator array. */
EXEC SQL INSERT INTO emp (empno, deptno, comm)
VALUES (:emp_number, :dept_number,
:commission INDICATOR :comm_ind);
VALUES、SET、INTOまたはWHERE句でスカラーのホスト変数とホスト配列を併用することはできません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。
配列インタフェースは、ANSI/ISOの埋込みSQL標準に対するOracleの拡張機能です。ただし、MODE=ANSIでプリコンパイルしても、配列のSELECTおよびFETCHは許可されます。必要に応じ、FIPSフラガー・プリコンパイラ・オプションを使用して、配列の使用にフラグを付けることが可能です。
配列をSELECTおよびFETCHするときは、必ずインジケータ配列を使用します。このようにして、関連する出力ホスト配列内にNULLがあるかどうかをテストできます。
DBMS=V7またはDBMS=v8のときに、インジケータ配列に対応付けられていないホスト配列にNULL列値をSELECTまたはFETCHすると、Oracleは処理を停止し、sqlerrd[2に処理済行数を設定しエラー・メッセージを出します。DBMS=V7またはDBMS=v8の場合、Oracleは切捨てをエラーとみなしません。
また、SELECTまたはFETCHの結果、NULLの使用によるORA-24347などの警告が発生した場合や、列にインジケータ配列がない場合には、Oracleは処理を停止します。
|
注意:
|
ホスト配列はSELECT文内の出力変数として使用できます。SELECTによって戻される最大行数がわかっている場合、その要素数(最大行数)でホスト配列を宣言してください。次の例では3つのホスト配列内にデータを直接選び出します。このときSELECTが戻すのは50行以下とわかっているため、配列は要素数50で宣言します。
char emp_name[50][20];
int emp_number[50];
float salary[50];
EXEC SQL SELECT ENAME, EMPNO, SAL
INTO :emp_name, :emp_number, :salary
FROM EMP
WHERE SAL > 1000;
この例ではSELECT文は50行まで戻します。選択される行数が50行に満たないとき、あるいは50行のみを取り出すときは、この方法を使用します。ただし、選択される行数が50行を超える場合、この方法ではすべての行を取り出せません。このSELECT文を再実行しても、他に選択対象の行がある場合も最初の50行のみがまた戻されます。この場合は大きな配列を宣言するか、FETCH文で使用するカーソルを宣言する必要があります。
宣言した要素数を超える行数がSELECT INTO文によって戻されると、SELECT_ERROR=NOを指定していないかぎりエラー・メッセージが出されます。
SELECTが戻す最大行数がわからない場合には、カーソルを宣言して、一括でフェッチできます。
ループ内の一括フェッチによって多数の行を簡単に取り出せます。FETCHを実行するたびに、次に続く行の集合がカレント・アクティブ・セットから戻されます。次の例では、20行ずつまとめて行をフェッチします。
int emp_number[20];
float salary[20];
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT empno, sal FROM emp;
EXEC SQL OPEN emp_cursor;
EXEC SQL WHENEVER NOT FOUND do break;
for (;;)
{
EXEC SQL FETCH emp_cursor
INTO :emp_number, :salary;
/* process batch of rows */
...
}
...
最後のフェッチで実際に戻された行数を必ずチェックして処理してください。
INSERT文、UPDATE文、DELETE文およびSELECT INTO文の場合は、sqlca.sqlerrd[2]に処理済行数が記録されます。FETCH文の場合は、処理した行の累積数が記録されます。
FETCHでホスト配列を使用しているときに、その時点での最後のループで戻された行数を確認するには、sqlca.sqlerrd[2]の現在の値と(別の変数内に保存した)前回の値との差分をとります。次の例では、その時点での最後のFETCHで戻された行数を判断します。
int emp_number[100];
char emp_name[100][20];
int rows_to_fetch, rows_before, rows_this_time;
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT empno, ename
FROM emp
WHERE deptno = 30;
EXEC SQL OPEN emp_cursor;
EXEC SQL WHENEVER NOT FOUND CONTINUE;
/* initialize loop variables */
rows_to_fetch = 20; /* number of rows in each "batch" */
rows_before = 0; /* previous value of sqlerrd[2] */
rows_this_time = 20;
while (rows_this_time == rows_to_fetch)
{
EXEC SQL FOR :rows_to_fetch
FETCH emp_cursor
INTO :emp_number, :emp_name;
rows_this_time = sqlca.sqlerrd[2] - rows_before;
rows_before = sqlca.sqlerrd[2];
}
...
配列の処理中にエラーが発生したときにもsqlca.sqlerrd[2]が役立ちます。処理はエラーが発生した行で停止するため、sqlerrd[2]には正常に処理された行数が格納されます。
各FETCHが戻すのは、最大でも配列の全行数分までです。次のような場合は、配列の全行数より少ない行が戻されます。
アクティブ・セットの最後に達したとき。「データが見つかりません」というOracleエラー・コードがSQLCA内でSQLCODEに戻されます。たとえば、要素数100の配列に行をフェッチしたとき20行しか戻されなかった場合にこれが起こります。
フェッチ対象の行が配列の全行数よりも少ないとき。たとえば、要素数20の配列に70行をフェッチすると、この状態となります。つまり、3回目のフェッチの後には、フェッチ対象の行は10行しか残っていません。
行の処理中にエラーが検出されたとき。FETCHは失敗し、該当するOracleエラー・コードがSQLCODEに戻されます。
戻された行の累積数は、このガイドではsqlerrd[2]と記載しているSQLCA内のsqlerrdの3番目の要素に保存されます。これはオープン状態のすべてのカーソルに適用されます。次の例では、各カーソルの状態がそれぞれ更新されている様子がわかります。
EXEC SQL OPEN cursor1; EXEC SQL OPEN cursor2; EXEC SQL FETCH cursor1 INTO :array_of_20; /* now running total in sqlerrd[2] is 20 */ EXEC SQL FETCH cursor2 INTO :array_of_30; /* now running total in sqlerrd[2] is 30, not 50 */ EXEC SQL FETCH cursor1 INTO :array_of_20; /* now running total in sqlerrd[2] is 40 (20 + 20) */ EXEC SQL FETCH cursor2 INTO :array_of_30; /* now running total in sqlerrd[2] is 60 (30 + 30) */
ホスト配列はスクロール可能カーソルとともに使用することもできます。スクロール可能カーソルを使用する場合、sqlca.sqlerrd[2]は処理済最大(絶対)行数を表します。アプリケーションではスクロール可能モードでフェッチを任意の場所に配置できるため、この値が処理済行数の合計である必要はありません。
スクロール可能モードのFETCH文にホスト配列を使用している間は、sqlca.sqlerrd[2]の現在の値と前回の値との差分を取っても、その時点での最後のループで戻された行数を確認することはできません。アプリケーション・プログラムでは、FETCH LASTを実行して、結果セット内の合計行数を判断します。sqlca.sqlerrd[2]の値は、結果セット内の合計行数を示します。スクロール可能カーソルでのホスト配列の使用方法を示す例は、「サンプル・プログラム: スクロール可能カーソルを使用するホスト配列」を参照してください。
この項のデモ・プログラムでは、Pro*C/C++で問合せを作成するときの配列の使用方法を示しています。特に、SQLCA(sqlca.sqlerrd[2])の処理済行数に注目してください。このプログラムは、demoディレクトリのファイルsample3.pcとして、オンラインで使用可能です。
/*
* sample3.pc
* Host Arrays
*
* This program connects to ORACLE, declares and opens a cursor,
* fetches in batches using arrays, and prints the results using
* the function print_rows().
*/
#include <stdio.h>
#include <string.h>
#include <sqlca.h>
#define NAME_LENGTH 20
#define ARRAY_LENGTH 5
/* Another way to connect. */
char *username = "SCOTT";
char *password = "TIGER";
/* Declare a host structure tag. */
struct
{
int emp_number[ARRAY_LENGTH];
char emp_name[ARRAY_LENGTH][NAME_LENGTH];
float salary[ARRAY_LENGTH];
} emp_rec;
/* Declare this program's functions. */
void print_rows(); /* produces program output */
void sql_error(); /* handles unrecoverable errors */
main()
{
int num_ret; /* number of rows returned */
/* Connect to ORACLE. */
EXEC SQL WHENEVER SQLERROR DO sql_error("Connect error:");
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("\nConnected to ORACLE as user: %s\n", username);
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:");
/* Declare a cursor for the FETCH. */
EXEC SQL DECLARE c1 CURSOR FOR
SELECT empno, ename, sal FROM emp;
EXEC SQL OPEN c1;
/* Initialize the number of rows. */
num_ret = 0;
/* Array fetch loop - ends when NOT FOUND becomes true. */
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
EXEC SQL FETCH c1 INTO :emp_rec;
/* Print however many rows were returned. */
print_rows(sqlca.sqlerrd[2] - num_ret);
num_ret = sqlca.sqlerrd[2]; /* Reset the number. */
}
/* Print remaining rows from last fetch, if any. */
if ((sqlca.sqlerrd[2] - num_ret) > 0)
print_rows(sqlca.sqlerrd[2] - num_ret);
EXEC SQL CLOSE c1;
printf("\nAu revoir.\n\n\n");
/* Disconnect from the database. */
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
print_rows(n)
int n;
{
int i;
printf("\nNumber Employee Salary");
printf("\n------ -------- ------\n");
for (i = 0; i < n; i++)
printf("%-9d%-15.15s%9.2f\n", emp_rec.emp_number[i],
emp_rec.emp_name[i], emp_rec.salary[i]);
}
void
sql_error(msg)
char *msg;
{
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s", msg);
printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
}
このプログラムは、スクロール可能カーソルとともにホスト配列を使用する方法を示します。このプログラムは、demoディレクトリのファイルscrolldemo2.pcとして、オンラインで使用可能です。
|
注意: 結果セット内の行数の判断にはFETCH LASTを実行していることに注意してください。 |
/*
* A Sample program to demonstrate the use of scrollable
* cursors with host arrays.
*
* This program uses the hr/hr schema.Make sure
* that this schema exists before executing this program
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlca.h>
#define ARRAY_LENGTH 4
/* user and passwd */
char *username = "hr";
char *password = "hr";
/* Declare a host structure tag. */
struct emp_rec_array
{
int emp_number;
char emp_name[20];
float salary;
} emp_rec[ARRAY_LENGTH];
/* Print the result of the query */
void print_rows()
{
int i;
for (i=0; i<ARRAY_LENGTH; i++)
printf("%d %s %8.2f\n", emp_rec[i].emp_number,
emp_rec[i].emp_name, emp_rec[i].salary);
}
/* Oracle error handler */
void sql_error(char *msg)
{
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s", msg);
printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(EXIT_FAILURE);
}
void main()
{
int noOfRows; /* Number of rows in the result set */
/* Error handler */
EXEC SQL WHENEVER SQLERROR DO sql_error("Connect error:");
/* Connect to the data base */
EXEC SQL CONNECT :username IDENTIFIED BY :password;
/* Error handle */
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:");
/* declare the cursor in scrollable mode */
EXEC SQL DECLARE c1 SCROLL CURSOR FOR
SELECT employee_id, first_name, salary FROM employees;
EXEC SQL OPEN c1;
EXEC SQL WHENEVER SQLERROR DO sql_error("Fetch Error:");
/* This is a dummy fetch to find out the number of rows
in the result set */
EXEC SQL FETCH LAST c1 INTO :emp_rec;
/* The number of rows in the result set is given by
the value of sqlca.sqlerrd[2] */
noOfRows = sqlca. sqlerrd[2];
printf("Total number of rows in the result set %d:\n",
noOfRows);
/* Fetch the first ARRAY_LENGTH number of rows */
EXEC SQL FETCH FIRST c1 INTO :emp_rec;
printf("******************** DEFAULT : \n");
print_rows();
/* Fetch the next set of ARRAY_LENGTH rows */
EXEC SQL FETCH NEXT c1 INTO :emp_rec;
printf("******************** NEXT : \n");
print_rows();
/* Fetch a set of ARRAY_LENGTH rows from the 3rd row onwards */
EXEC SQL FETCH ABSOLUTE 3 c1 INTO :emp_rec;
printf("******************** ABSOLUTE 3 : \n");
print_rows();
/* Fetch the current ARRAY_LENGTH set of rows */
EXEC SQL FETCH CURRENT c1 INTO :emp_rec;
printf("******************** CURRENT : \n");
print_rows();
/* Fetch a set of ARRAY_LENGTH rows from the 2nd offset
from the current cursor position */
EXEC SQL FETCH RELATIVE 2 c1 INTO :emp_rec;
printf("******************** RELATIVE 2 : \n");
print_rows();
/* Again Fetch the first ARRAY_LENGTH number of rows */
EXEC SQL FETCH ABSOLUTE 0 c1 INTO :emp_rec;
printf("******************** ABSOLUTE 0 : \n");
print_rows();
/* close the cursor */
EXEC SQL CLOSE c1;
/* Disconnect from the database. */
EXEC SQL COMMIT WORK RELEASE;
exit(EXIT_SUCCESS);
}
副問合せ文中を除き、SELECT文のWHERE句にホスト配列は使用できません。例は、「WHERE句の使用方法」を参照してください。
またSELECT文またはFETCH文のINTO句内では、単純ホスト変数とホスト配列の併用はできません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。
表8-1は、SELECT INTO文で有効なホスト配列の使用を示しています。
配列をSELECTおよびFETCHするときは、必ずインジケータ配列を使用します。このようにして、関連する出力ホスト配列内にNULLがあるかどうかをテストできます。
DBMS = V7またはDBMS=v8のときに、インジケータ配列に対応付けられていないホスト配列にNULL列値をSELECTまたはFETCHすると、Oracleは処理を停止し、sqlerrd[2]に処理済行数を設定しエラー・メッセージを出します。
また、SELECTまたはFETCHの結果、NULLの使用によるORA-24347などの警告が発生した場合や、列にインジケータ配列がない場合には、Oracleは処理を停止します。SELECTまたはFETCHのすべての列で標識変数を使用します。インジケータのない列がある場合は、プリコンパイラ・オプションunsafe_null=yesをかわりに使用できます。
ホスト配列はINSERT文内の入力変数として使用できます。プログラムでINSERT文を実行する前に、プログラム内にデータが含まれている配列があるかどうかを確認してください。
配列内に不適切な要素があるときは、FOR句を使用してINSERT対象の行数を制御できます。「FOR句の使用方法」も参照してください。
ホスト配列によるINSERTの例を次に示します。
char emp_name[50][20];
int emp_number[50];
float salary[50];
/* populate the host arrays */
...
EXEC SQL INSERT INTO EMP (ENAME, EMPNO, SAL)
VALUES (:emp_name, :emp_number, :salary);
挿入された行の累積数は処理済行数sqlca.sqlerrd[2]に保存されます。
次の例では、一度に1行ずつINSERTされます。各行の挿入ごとにサーバーをコールする必要があるため、この方法は前の例に比べるとかなり効率は悪くなります。
for (i = 0; i < array_size; i++)
EXEC SQL INSERT INTO emp (ename, empno, sal)
VALUES (:emp_name[i], :emp_number[i], :salary[i]);
次の例に示すように、ホスト配列をUPDATE文内の入力変数として使用できます。
int emp_number[50];
float salary[50];
/* populate the host arrays */
EXEC SQL UPDATE emp SET sal = :salary
WHERE EMPNO = :emp_number;
更新された行の累積数はsqlerrd[2]に保存されます。この累積数には更新カスケードによって処理された行は含まれていません。
配列内に不適切な要素がある場合は、埋込みSQLのFOR句を使用して更新対象の行数を制御できます。
前の例では一意キー(EMP_NUMBER)を使用した一般的な更新を示しています。このとき、配列要素はそれぞれ更新対象の行数を1行に制限しています。次の例では、それぞれの配列要素の削除対象の行数が複数行になっています。
char job_title [10][20];
float commission[10];
...
EXEC SQL UPDATE emp SET comm = :commission
WHERE job = :job_title;
UPDATE文のSET句またはWHERE句では、単純ホスト変数とホスト配列を併用できません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。さらに、SET句でホスト配列を使用するときには、WHERE句の要素数と同じ数のものを使用してください。
UPDATE文のCURRENT OF句では、ホスト配列は使用できません。
表8-2は、UPDATE文で有効なホスト配列の使用方法を示しています。
DELETE文内でもホスト配列を入力変数として使用できます。これは、WHERE句内のホスト配列の連続した要素を使用してDELETE文を繰り返し実行するときと同様です。つまり、1回の実行で表から0行、1行または複数行が削除されます。
ホスト配列による削除の例を次に示します。
...
int emp_number[50];
/* populate the host array */
...
EXEC SQL DELETE FROM emp
WHERE empno = :emp_number;
削除された行の累積数はsqlerrd[2]に保存されます。その累積数には、削除カスケードによって処理された行は含まれていません。
この例では一意キー(EMP_NUMBER)を使用した一般的な削除を示しています。このとき、配列要素はそれぞれ削除対象の行数を1行に制限しています。次の例では、それぞれの配列要素の削除対象の行数が複数行になっています。
...
char job_title[10][20];
/* populate the host array */
...
EXEC SQL DELETE FROM emp
WHERE job = :job_title;
...
埋込みSQLでオプションのFOR句を使用すると、次に示すSQL文が処理する配列要素の数を設定できます。
DELETE
EXECUTE
FETCH
INSERT
OPEN
UPDATE
特にUPDATE文、INSERT文およびDELETE文内でFOR句を使用すると便利です。これらの文に配列全体を使用する必要がないときもあります。次の例に示すように、FOR句を使用すると、使用する要素数を任意の数に制限できます。
char emp_name[100][20];
float salary[100];
int rows_to_insert;
/* populate the host arrays */
rows_to_insert = 25; /* set FOR-clause variable */
EXEC SQL FOR :rows_to_insert /* will process only 25 rows */
INSERT INTO emp (ename, sal)
VALUES (:emp_name, :salary);
FOR句では、配列要素をカウントするための整数型のホスト変数や、整数リテラルも使用できます。整数値を取るC言語の複合式は使用できません。たとえば、整数式を使用する次の文は無効です。
EXEC SQL FOR :rows_to_insert + 5 /* illegal */
INSERT INTO emp (ename, empno, sal)
VALUES (:emp_name, :emp_number, :salary);
FOR句の変数では、処理する配列の要素数を指定します。この数は、最も小さい配列サイズを超過しないように設定します。この値は内部的には記号の付いていない数量として扱われます。付号付きのホスト変数を使用して負の値を渡すと、予期せぬ動作が発生します。
2つの制限事項によってFOR句の使用方法が明確になります。FOR句はSELECT文では使用できません。またCURRENT OF句とともに使用することはできません。
SELECT文中でFOR句を使用すると、エラー・メッセージが戻されます。
FOR句は意味があいまいなため、SELECT文中では使用できません。「このSELECT文をn回実行する」なのか、「このSELECT文を1回実行し、n行戻す」なのかはっきりしません。前者のように解釈すると、実行ごとに複数行が戻されることになります。後者の解釈では、次に示すように、カーソルを宣言してからFETCH文中でFOR句を使用することをお薦めします。
EXEC SQL FOR :limit FETCH emp_cursor INTO ...
次の例に示すように、UPDATE文またはDELETE文中でCURRENT OF句を使用すると、FETCH文によって戻される最後の行を参照できます。
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT ename, sal FROM emp WHERE empno = :emp_number;
...
EXEC SQL OPEN emp_cursor;
...
EXEC SQL FETCH emp_cursor INTO :emp_name, :salary;
...
EXEC SQL UPDATE emp SET sal = :new_salary
WHERE CURRENT OF emp_cursor;
ただし、CURRENT OF句とFOR句の併用はできません。次の文はlimitの論理値が1に限定されているため無効です。(つまり、現在行を更新または削除できるのは1回のみです。)
EXEC SQL FOR :limit UPDATE emp SET sal = :new_salary WHERE CURRENT OF emp_cursor; ... EXEC SQL FOR :limit DELETE FROM emp WHERE CURRENT OF emp_cursor;
Oracleでは、要素数nのホスト配列を含むSQL文を、同一のSQL文をn個の異なるスカラー変数(個々の配列要素)でn回実行するのと同様に扱います。このような扱いがあいまいなときにかぎり、プリコンパイラによりエラー・メッセージが発行されます。
たとえば次の宣言をしたとします。
int mgr_number[50]; char job_title[50][20];
次の文、
EXEC SQL SELECT mgr INTO :mgr_number FROM emp WHERE job = :job_title;
を、次の架空の文のように処理した場合、不明瞭とはなりません。
for (i = 0; i < 50; i++)
SELECT mgr INTO :mgr_number[i] FROM emp
WHERE job = :job_title[i];
WHERE句の検索条件を満たす複数行があっても、データの受取りに使用できる出力変数が1つしかないためです。したがって、エラー・メッセージが発行されます。
一方、次の文
EXEC SQL UPDATE emp SET mgr = :mgr_number
WHERE empno IN (SELECT empno FROM emp
WHERE job = :job_title);
を、次の架空の文のように処理した場合、不明瞭とはなりません。
for (i = 0; i < 50; i++)
UPDATE emp SET mgr = :mgr_number[i]
WHERE empno IN (SELECT empno FROM emp
WHERE job = :job_title[i]);
これは、それぞれのjob_titleが複数行に一致する場合でも、WHERE句内のjob_titleに一致する各行についてSET句内にmgr_numberが指定されているためです。それぞれのjob_titleに一致する行すべてに、同一のmgr_numberをSETできます。したがってエラー・メッセージは発行されません。
スカラー配列を使用すると、1つの列での複数行操作が可能です。また、スカラー構造体を使用すると、1つの行での複数列操作が可能です。
しかし、複数列で複数行を操作する場合、これまでは複数のスカラー並列配列を個別にまたは1つの構造体中でカプセル化して割り当てる必要がありました。この方法よりも、このデータ構造を複数の構造体からなる1つの配列にしなおす方が便利です。
Pro*C/C++で構造体配列がサポートされ、アプリケーション・プログラマはC構造体の配列を使用して複数行および複数列の操作を実行できます。この機能拡張により、Pro*C/C++ではスカラー構造体の単純な配列を埋込みSQL文でバインド変数として処理できるため、データの処理がより簡単になります。これで、プログラミングがさらに直観的になり、データ編成もはるかに自由にできます。
Pro*C/C++では、構造体配列がバインド変数としてサポートされるのみでなく、インジケータ構造体の配列を構造体配列の宣言と併用できます。
|
注意: 構造体をPL/SQLレコードにバインドすることと、構造体の配列をPL/SQLレコードの表にバインドすることは、この新たな機能の一部ではありません。また、構造体の配列を埋込みPL/SQLブロック内で使用することもできません。「構造体の配列の制限」も参照してください。 |
構造体の配列は、複数の列で複数の行を操作するために使用され、通常次のように使用されると考えてください。
SELECT文またはFETCH文での出力バインド変数として使用
INSERT文のVALUES句での入力バインド変数として使用
構造体の配列という概念は、C言語のプログラマにとって特に目新しいものではありません。しかし、複数の並列配列からなる1つの構造体と比較してみると、データの格納方法に考え方の違いがあります。
複数の並列配列からなる1つの構造体では、個々の列のデータが連続して格納されます。一方、構造体の配列では、列のデータはインタリーブされます。この場合、配列内の各列の間は、その構造体内の他の列に必要な空白で区切られます。この空白はストライド(stride)と呼ばれます。
Pro*C/C++では、構造体配列の使用に次の制限事項があります。
構造体配列(通常の構造体による)を埋込みPL/SQLブロック内で使用できません。
構造体配列をWHERE句やFROM句で使用できません。
構造体配列はOracle動的SQL方法4では使用できません。ANSI動的SQLでは使用できます。「ANSI動的SQL」も参照してください。
構造体配列をUPDATE文のSET句で使用できません。
構造体配列の宣言では構文に違いはありません。ただし、構造体配列を使用する場合には留意事項がいくつかあります。
Pro*C/C++アプリケーションで使用する構造体配列を宣言する場合、プログラマは必ず次の点に留意してください。
構造体には必ず構造体タグを付けてください。例は次のとおりです。
struct person {
char name[15];
int age;
} people[10];
このコード・セグメントでは、person変数が構造体のタグです。このタグによって、プリコンパイラは構造体の名前を使用してストライドのサイズを計算できます。
構造体のメンバーは配列にしないでください。ただし、charやVARCHARなどの文字型の場合には、この規則は適用されません。このような型の変数の宣言で配列の構文が使用されるためです。
charおよびVARCHAR型のメンバーは2次元配列にしないでください。
ネストされた構造体は構造体配列のメンバーになることはできません。Pro*C/C++の旧リリースでは、ネストされた構造体はサポートされていなかったため、この制限は新しいものではありません。
構造体自体のサイズは、符号付き4バイト数で表すことのできる最大値を超えないようにしてください。通常、この最大値は2GBです。
構造体配列の使用に関する前述の制限事項を満たしているので、Pro*C/C++では次の宣言は有効です。
struct department {
int deptno;
char dname[15];
char loc[14];
} dept[4];
一方、次の宣言は無効です。
struct { /* the struct is missing a structure tag */
int empno[15]; /* struct members may not be arrays */
char ename[15][10]; /* character types may not be 2-dimensional */
struct nested {
int salary; /* nested struct not permitted in array of structs */
} sal_struct;
} bad[15];
データ型の同値化を、構造体配列自体や構造体内の個々のフィールドには適用できないことにも注意してください。たとえば、empnoが前述の無効な構造体内の配列として宣言されていない場合、次の文は無効です。
exec sql var bad[3].empno is integer(4);
プリコンパイラには、構造体配列中の個々の構造体要素を追跡し記録する機能はありません。ただし、次を実行すれば、期待した結果が得られます。
typedef int myint;
exec sql type myint is integer(4);
struct equiv {
myint empno; /* now legally considered an integer(4) datatype */
...
} ok[15];
Pro*C/C++の以前のリリースでは、個別の配列項目の同値化がサポートされていなかったため、これは予測できたことといえます。たとえば、次のスカラー配列宣言は何が有効で、何が有効でないかを示しています。
int empno[15]; exec sql var empno[3] is integer(4); /* illegal */ myint empno[15]; /* legal */
基本的には、個々の配列項目を同値化することはできません。
標識変数は、構造体の配列の宣言でも、通常の構造体の宣言の場合とほとんど同じはたらきをします。構造体のインジケータ配列の宣言は、構造体の配列についての次の規則にも従う必要があります。
インジケータ構造体に含まれるフィールド数は、対応する構造体の配列に含まれるフィールド数以下である必要があります。
フィールドの順序は、対応する構造体の配列のメンバーの順序に一致する必要があります。
インジケータ構造体に含まれるすべての要素のデータ型は、shortである必要があります。
インジケータ配列のサイズは、ホスト変数で宣言されたサイズ以上である必要があります。ホスト変数で宣言されたサイズより大きい値は指定できますが、それより小さい値は指定できません。
これらの規則のほとんどには、Pro*C/C++の以前のリリースでの構造体使用規則が反映されています。配列制限も以前使用されたスカラーの配列に対するものと同じです。
これらの規則が適用されている場合に、次のように構造体を宣言するとします。
struct department {
int deptno;
char dname[15];
char loc[14];
} dept[4];
次の標識変数の構造体の宣言は有効です。
struct department_ind {
short deptno_ind;
short dname_ind;
short loc_ind;
} dept_ind[4];
一方、次は標識変数として無効です。
struct{ /* missing indicator structure tag */
int deptno_ind; /* indicator variable not of type short */
short dname_ind[15];/* array element forbidden in indicator struct */
short loc_ind[14]; /* array element forbidden in indicator struct */
} bad_ind[2]; /* indicator array size is smaller than host array */
構造体配列へのポインタを宣言する方が適切な場合があります。これにより、構造体配列へのポインタを他の関数に渡したり、埋込みSQL文で直接指定したりできます。
|
注意: 構造体配列へのポインタが参照する配列の長さは、プリコンパイル中にはわかりません。このため、埋込みSQL文で構造体の配列へのポインタ型になっているバインド変数を使用するときには、明示的なFOR句を使用してください。 |
ただし、FOR句は埋込みSQL SELECT文では指定できません。したがって、データを取り出して構造体配列へのポインタに入れる場合には、必ずカーソルとFETCH文をFOR句とともに明示的に指定してください。
次の例は、Pro*C/C++での構造体配列の機能について、様々な使用方法を示しています。
次の構造体の宣言を指定したとします。
struct department {
int deptno;
char dname[15];
char loc[14];
} my_dept[4];
次のようにdeptデータを選択してmy_deptに入れることができます。
exec sql select * into :my_dept from dept;
また、最初にmy_deptにデータを入れてから、dept表に一括して挿入できます。
exec sql insert into dept values (:my_dept);
標識変数を指定するには、構造体のパラレル・インジケータ配列を宣言します。
struct deptartment_ind {
short deptno_ind;
short dname_ind;
short loc_ind;
} my_dept_ind[4];
データの選択に使用される問合せは、標識変数の指定が追加されたこと以外は同じです。
exec sql select * into :my_dept indicator :my_dept_ind from dept;
同様に、データの挿入時にもインジケータを指定できます。
exec sql insert into dept values (:my_dept indicator :my_dept_ind);
Pro*C/C++の旧リリースと同様に、ユーザー・データのバルク処理機能に複数の配列を使用する場合は、各配列のサイズを同じにする必要があります。同じにしないと、最も小さい配列のサイズが選択され、残りの配列は変更されません。
次の宣言を指定したとします。
struct employee {
int empno;
char ename[11];
} emp[14];
float sal[14];
float comm[14];
ただ1つの問合せで、すべての列に対して複数行を選択できます。
exec sql select empno, ename, sal, comm into :emp, :sal, :comm from emp;
また、コミッション列の値がNULLかどうかを確認する必要があります。次のように宣言すれば、1つのインジケータ配列を指定するのみで済みます。
short comm_ind[14]; ... exec sql select empno, ename, sal, comm into :emp, :sal, :comm indicator :comm_ind from emp;
問合せからのインジケータ情報をすべてカプセル化した構造体の単一インジケータ配列は宣言できません。次の例を考えます。
struct employee_ind { /* example of illegal usage */
short empno_ind;
short ename_ind;
short sal_ind;
short comm_ind;
} illegal_ind[15];
exec sql select empno, ename, sal, comm
into :emp, :sal, :comm indicator :illegal_ind from emp;
この列は無効です(またお薦めできません)。上の文にはSELECT...INTOリスト全体ではなく、comm列しかないインジケータ配列が対応付けられています。
構造体の配列とsal、commおよびcomm_ind配列にデータを挿入する場合、その挿入方法は非常に簡単です。
exec sql insert into emp (empno, ename, sal, comm) values (:emp, :sal, :comm indicator :comm_ind);
次の宣言をこの例として使用します。
struct employee {
int empno;
char ename[11];
char job[10];
} emp[14];
struct compensation {
int sal;
int comm;
} wage[14];
struct compensation_ind {
short sal_ind;
short comm_ind;
} wage_ind[14];
Oracleのプログラムでは、構造体配列を次のように指定できます。
exec sql declare c cursor for
select empno, ename, job, sal, comm from emp;
exec sql open c;
exec sql whenever not found do break;
while(1)
{
exec sql fetch c into :emp, :wage indicator :wage_ind;
... process batch rows returned by the fetch ...
}
printf("%d rows selected.\n", sqlca.sqlerrd[2]);
exec sql close c;
これまでのPro*C/C++リリースで、構造体配列内の1構造体に対する配列参照が可能となっています。これによりバインド式はスカラーの単純な構造体で解決できるため、次は有効です。
exec sql select * into :dept[3] from emp;
次の例に示すように、構造体配列内の特定の構造体のスカラー・メンバーを個別に参照することも可能です。
exec sql select dname into :dept[3].dname from dept where ...;
この場合には当然、問合せは単一行問合せにする必要があり、1行のみを選択してこのバインド式で表される変数に代入します。
これまでのPro*C/C++リリースでは、インジケータ構造体のフィールド数はそれに対応するバインド変数構造体と同じにする必要がありました。構造体を通常に使用する場合には、この制限は緩和されています。前述の構造体のインジケータ配列についてのガイドラインに従っているので、次の例が可能です。
struct employee {
float comm;
float sal;
int empno;
char ename[10];
} emp[14];
struct employee_ind {
short comm;
} emp_ind[14];
exec sql select comm, sal, empno, ename
into :emp indicator :emp_ind from emp;
標識変数はバインド値と1対1でマップされます。これらは、最初のフィールドから、対応した順番にマップされます。
ただし、他のフィールドでフェッチされた値がNULLであったり、インジケータが付いていなかったりすると、次のエラーが発生するため注意してください。
ORA-1405: fetched column value is NULL
たとえば、salがNULL値可能の場合にsalにはインジケータがないため、このエラーが発生します。
次のように構造体の配列を変更したとします。
struct employee {
int empno;
char ename[10];
float sal;
float comm;
} emp[15];
しかし、同じ構造体のインジケータ配列がまだ使用されているとします。
インジケータのマップは対応した順番に実行されるため、commインジケータがempnoフィールドにマップされて、commバインド変数にインジケータがないままとなり、再度ORA-01405エラーとなります。
対応するバインド変数の構造体よりもフィールド数の少ないインジケータ構造体を指定したときに、ORA-01405エラーが発生しないようにするには、NULL値可能の属性を最初から順番に並べる必要があります。
この例は、非配列の構造体を使用すれば複数列の単一行フェッチに簡単に変更でき、インジケータ構造体が次のように宣言されている場合と同等のはたらきをすると考えることができます。
struct employee_ind {
short comm;
short sal;
short empno;
short ename;
} emp_ind;
Pro*C/C++ではインジケータ構造体のフィールド数と対応する値の構造体のフィールド数が同じである必要がなくなったため、前述の例はこれまでPro*C/C++で無効でしたが現在は有効になりました。
Oracleのインジケータ構造体は、次のように簡単に指定できます。
struct employee_ind {
short comm;
} emp_ind;
次のように配列なし構造体であるempおよびemp_indを指定すると、単一行フェッチを実行できます。
exec sql fetch comm, sal, empno, ename into :emp indicator :emp_ind from emp;
この場合にも、commインジケータがcommバインド変数にどのようにマップされるかに注意してください。
この例では、構造体配列へのポインタの使用方法を示します。
次の型の宣言を考えます。
typedef struct dept {
int deptno;
char dname[15];
char loc[14];
} dept;
その型の構造体配列へのポインタを操作すると、様々な処理を実行できます。たとえば、構造体配列へのポインタを他の関数に渡すことができます。
void insert_data(d, n)
dept *d;
int n;
{
exec sql for :n insert into dept values (:d);
}
void fetch_data(d, n)
dept *d;
int n;
{
exec sql declare c cursor for select deptno, dname, loc from dept;
exec sql open c;
exec sql for :n fetch c into :d;
exec sql close c;
}
このような関数をコールするには、例に示すように構造体配列のアドレスを渡します。
dept d[4]; dept *dptr = &d[0]; const int n = 4; fetch_data(dptr, n); insert_data(d, n); /* We are treating '&d[0]' as being equal to 'd' */
一部の埋込みSQL文では、構造体配列へのポインタを直接使用することができます。
exec sql for :n insert into dept values (:dptr);
FOR句の使用方法を間違えないように、十分に注意してください。
DELETE文またはUPDATE文でCURRENT OF cursor句を使用すると、カーソルから最後にフェッチされた行を参照できます。ただし、CURRENT OF句とホスト配列の併用はできません。かわりに各行のROWIDを取得し、更新または削除するときにその値を使用して現在行を識別してください。
char emp_name[20][10];
char job_title[20][10];
char old_title[20][10];
char row_id[20][19];
...
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT ename, job, rowid FROM emp FOR UPDATE;
...
EXEC SQL OPEN emp_cursor;
EXEC SQL WHENEVER NOT FOUND do break;
for (;;)
{
EXEC SQL FETCH emp_cursor
INTO :emp_name, :job_title, :row_id;
...
EXEC SQL DELETE FROM emp
WHERE job = :old_title AND rowid = :row_id;
EXEC SQL COMMIT WORK;
}
Oracleプリコンパイラでは、ホスト表に対するDB2の挿入およびフェッチ構文もサポートします。サポートされている追加の配列の挿入およびフェッチ構文は、次の図にそれぞれ示しています。
オプションのROWSET句およびROWSET STARTING AT句は、フェッチ方向(FIRST、PRIOR、NEXT、LAST、CURRENT、RELATIVEおよびABSOLUTE)で使用されます。次の例を考えます。
FIRST ROWSET
PRIOR ROWSET
NEXT ROWSET
LAST ROWSET
CURRENT ROWSET
ROWSET STARTING AT RELATIVEn
ROWSET STARTING AT ABSOLUTEn
DB2配列の挿入/フェッチ構文の例と、対応するOracleプリコンパイラ構文との比較を、表8-3に示します。
表8-3 DB2配列構文とOracleプリコンパイラ構文の比較
| DB2配列構文 | Oracleプリコンパイラ構文 |
|---|---|
EXEC SQL INSERT INTO dsn8810.act (actno, actkwd, actdesc) VALUES (:hva1, :hva2, :hva3) FOR :NUM_ROWS ROWS; |
EXEC SQL FOR :num_rows INSERT INTO dsn8810.act (actno, actkwd, actdesc) VALUES (:hva1, :hva2, :hva3); |
EXEC SQL
FETCH NEXT ROWSET FROM c1
FOR 20 ROWS
INTO :hva_empno, :hva_lastname,
:hva_salary;
|
EXEC SQL
FOR :twenty
FETCH c1
INTO :hva_empno, :hva_lastname,
:hva_salary;
|
DB2構文では、行セットに位置付けられたカーソルは、データの行セットを取得する前に最初に宣言する必要があります。カーソルで行セットをフェッチできるようにするには、WITH ROWSET POSITIONING句をDECLARE CURSOR文で使用する必要があります。次の表で示すように、これはOracleプリコンパイラ構文では必要なく、適切ではありません。
| DB2配列構文 | Oracleプリコンパイラ構文 |
|---|---|
EXEC SQL
DECLARE c1 CURSOR
WITH ROWSET POSITIONING FOR
SELECT empno, lastname, salary
FROM dsn8810.emp;
|
EXEC SQL
DECLARE c1 CURSOR FOR
SELECT empno, lastname, salary
FROM dsn8810.emp;
|
このDB2配列構文のサポートは、プリコンパイラ・オプションdb2_arrayを指定して有効にできます。デフォルトのオプションはnoです。DB2配列構文のサポートは、Oracleプリコンパイラ構文と一緒に使用できません。一度にサポートされるのは、Oracleプリコンパイラ構文またはDB2構文のいずれか一方の構文のみです。
例8-1 DB2配列構文使用による行の挿入およびフェッチ
このプログラムでは、DB2配列挿入構文を使用して、INSCNT行をEMP表に挿入し、DB配列フェッチ構文を使用して、挿入された行をフェッチします。
/*
* db2arrdemo.pc
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlcpr.h>
#include <sqlca.h>
/* Number of rows to be inserted in one shot */
#define INSCNT 100
/* Number of rows to be fetched in one shot */
#define FETCHCNT 20
/* Define a host structure
for inserting data into the table
and for fetching data from the table */
struct emprec
{
int empno;
varchar ename[10];
varchar job[9];
int mgr;
char hiredate[10];
float sal;
float comm;
int deptno;
};
typedef struct emprec empdata;
/* Function prototypes */
void sql_error(char *);
void insertdata();
void fetchdata();
void printempdata(empdata *);
void main()
{
exec sql begin declare section;
char *uid = "scott/tiger";
exec sql end declare section;
exec sql whenever sqlerror do sql_error("ORACLE error--\n");
exec sql connect :uid;
printf("Inserting %d rows into EMP table using DB2 array insert syntax.\n",
INSCNT);
insertdata();
printf("\nFetching data using DB2 array fetch syntax.\n");
fetchdata();
exec sql rollback work release;
exit(EXIT_SUCCESS);
}
/* Inserting data into the table using DB2 array insert syntax*/
void insertdata()
{
int i, cnt;
char *str;
empdata emp_in[INSCNT];
/* To store temporary strings */
str = (char *)malloc (25 * sizeof(char));
/* Fill the array elements to insert */
for (i = 0; i < INSCNT; i++)
{
emp_in[i].empno = i+1;
sprintf(str, "EMP_%03d", i+1);
strcpy (emp_in[i].ename.arr, str);
emp_in[i].ename.len = strlen (emp_in[i].ename.arr);
sprintf(str, "JOB_%03d", i+1);
strcpy (emp_in[i].job.arr, str);
emp_in[i].job.len = strlen (emp_in[i].job.arr);
emp_in[i].mgr = i+1001;
sprintf(str, "%02d-MAY-06", (i%30)+1);
strcpy (emp_in[i].hiredate, str);
emp_in[i].sal = (i+1) * 10;
emp_in[i].comm = (i+1) * 0.1;
emp_in[i].deptno = 10;
}
free (str);
/* Inserting data using DB2 array insert syntax */
exec sql insert into emp values (:emp_in) FOR :INSCNT rows;
exec sql select count(*) into :cnt from emp where ename like 'EMP_%';
printf ("Number of rows successfully inserted into emp table: %d\n", cnt);
}
/* Fetches data from the table using DB2 array fetch syntax*/
void fetchdata()
{
empdata emp_out[FETCHCNT];
/* Declares scrollable cursor to fetch data */
exec sql declare c1 scroll cursor with rowset positioning for
select empno, ename, job, mgr, hiredate, sal, comm, deptno
from emp where ename like 'EMP_%' order by empno;
exec sql open c1;
exec sql whenever not found do break;
while(1)
{
/* Fetches data using DB2 array fetch syntax */
exec sql fetch next rowset from c1 for :FETCHCNT rows into :emp_out;
printempdata(emp_out);
}
exec sql whenever not found do sql_error("ORACLE ERROR");
exec sql close c1;
}
/* Prints the fetched employee data */
void printempdata(empdata *emp_out)
{
int i;
for (i=0; i<FETCHCNT; i++)
{
emp_out[i].ename.arr[emp_out[i].ename.len] = '\0';
emp_out[i].job.arr[emp_out[i].job.len] = '\0';
printf("Empno=%d, Ename=%s, Job=%s, Mgr=%d, Hiredate=%s, Sal=%6.2f,\n"
"Comm=%5.2f, Deptno=%d\n", emp_out[i].empno, emp_out[i].ename.arr,
emp_out[i].job.arr, emp_out[i].mgr, emp_out[i].hiredate,
emp_out[i].sal, emp_out[i].comm, emp_out[i].deptno);
}
}
/* Error handling function. */
void sql_error(char *msg)
{
exec sql whenever sqlerror continue;
printf("\n%s\n", msg);
printf("%.70s\n", sqlca.sqlerrm.sqlerrmc);
exec sql rollback release;
exit(EXIT_FAILURE);
}
パフォーマンスを高めるために、Pro*C/C++アプリケーション開発者は、埋め込んだSQL文のホスト配列を参照できます。これにより、データベースへの1回のラウンドトリップで、SQL文の配列を実行できます。配列実行によりパフォーマンスが大幅に向上するにもかかわらず、一部の開発者は、ANSI標準ではないという理由で、この機能を使用しません。たとえば、Oracleで配列実行を利用するために書かれたアプリケーションは、IBMのプリコンパイラを使用してプリコンパイルできません。
解決策としては、バッファされたINSERT文を使用します。これにより、ANSI標準の埋込みSQL構文をそのまま使用しながら、パフォーマンスを高めることができます。
コマンドライン・オプションmax_row_insertにより、INSERT文の実行前にバッファされる行数を制御します。デフォルトは0(ゼロ)で、機能は無効です。この機能を有効にするには、0(ゼロ)より大きな数値を指定します。
挿入バッファリングが有効の場合、プリコンパイラ・ランタイムでは、対応するカーソルにフラグを立て、次のことを実行します。
バインド値を保持するための余分のメモリーを割当てまたは再割当てします(最初の実行のみ)。
プログラムのホスト変数から内部ランタイム・バインド構造体へバインド値をコピーします。
バッファされる行数を増やします。
MAX_INSERT_ROWSがバッファされている場合、バッファされたINSERT文をフラッシュします。
MAX_INSERT_ROWSがヒットしなかった場合、値を内部バインド・バッファにコピーした後、フラッシュせずに戻ります。
新しい埋込みSQL文が実行され、バッファ挿入文がフラッシュされる場合、次のことが実行されます。
バッファをフラッシュします。
フラッシュを求めたコールを続行します。
アプリケーションには、標準のプリコンパイラ・エラー・メカニズム(Pro*Cのsqlcaなど)を介して、エラーが知らされます。
implicit_svptオプションにより、新しいバッチ挿入の開始前に、暗黙的セーブポイントを作成するかどうかを制御します。
yesの場合、行の新しいバッチの開始前に、セーブポイントが作成されます。挿入でエラーが発生した場合、暗黙的セーブポイントまでのロールバックが実行されます。
noの場合、暗黙的セーブポイントは作成されません。バッファ挿入でエラーが発生した場合、アプリケーションには報告されますが、ロールバックは実行されません。バッファ挿入についてエラーが非同期的に報告されます。アプリケーションでINSERT文が実行されると、挿入された行のエラーが報告されません。
INSERT以外の最初の文が実行されると、挿入された行についていくつかのエラーが後から報告されます。この文には、DELETE、UPDATE、INSERT(異なる表への)、COMMITおよびROLLBACKが考えられます。バッファ挿入文をクローズする文では、エラーが報告されます。そのような場合、エラーを報告する文は実行されません。まずエラーを処理し、バッファ挿入のエラーが報告された文を再実行する必要があります。さもなければ、トランザクションをロールバックし、再実行します。
たとえば、COMMIT文を使用して、バッファ挿入ループを終了することを考えます。COMMITにより、以前の挿入とキーが重複するためにエラーが報告される場合があります。この場合、コミットは実行されません。まずエラーを処理してから、COMMITを再実行する必要があります。さもなければ、トランザクションをロールバックし、再実行します。
挿入自体についてもいくつかエラーが報告され、以前に挿入された行のエラーが反映されている可能性があります。そのような場合、それ以上挿入は実行されません。以前挿入された行のエラーを処理し、現行の挿入を継続する必要がありますが、それには時間がかかります。かわりに、トランザクションをロールバックし、再実行してもかまいません。
たとえば、内部バッファの制限が10行であるのに、アプリケーションではループで15行挿入しようとしている場合を考えてみます。8行目にエラーがあるとします。11行目の挿入が行われたときにエラーが報告され、挿入はそれ以上実行されなくなります。
バッファ挿入中に発生すると考えられるエラーには、次のものがあります。
ORA-00001: 索引内でキーが重複しています
ORA-01400: 必須(非NULL)列がないか、挿入中NULLになっています
ORA-01401: 列に挿入した値が大きすぎます。
ORA-01438: この列に許容される指定精度より大きな値です
例8-2 表へのバッファ行の挿入
このプログラムでは、行のLOOPCNT番号をEMP表に挿入します。ループ・カウンタが5のところで、このプログラムでは無効なempnoを挿入しようとします。max_row_insertオプションが設定されていない場合、プログラムでは無効な行を除くすべての行が挿入されます。max_row_insertオプションがLOOPCNTに設定されている場合、最初の4行のみが挿入されます。
max_row_insertオプションを使用すると、間違った文が削除される場合、プログラムは配列の挿入プログラムと同様に機能します。
/*
* bufinsdemo.pc
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlcpr.h>
#include <sqlca.h>
/* Number of rows to be inserted into the table */
#define LOOPCNT 100
/* Define a host structure
for inserting data into the table
and for fetching data from the table */
struct emprec
{
int empno;
varchar ename[10];
varchar job[9];
int mgr;
char hiredate[10];
float sal;
float comm;
int deptno;
};
typedef struct emprec buffinstyp;
/* Function prototypes */
void sql_error();
void insertdata();
void fetchdata();
void printempdata(buffinstyp);
void main()
{
exec sql begin declare section;
char *uid = "scott/tiger";
exec sql end declare section;
exec sql whenever sqlerror do sql_error();
exec sql connect :uid;
printf("\nInserting %d rows into EMP table.\n", LOOPCNT);
insertdata();
printf("\nFetching inserted data from EMP table.\n");
fetchdata();
exec sql delete from emp where empno < 1000;
exec sql commit work release;
exit(EXIT_SUCCESS);
}
/* Inserting data into the table */
void insertdata()
{
int i, cnt;
char *str;
buffinstyp emp_in;
/* To store temporary strings */
str = (char *)malloc (25 * sizeof(char));
/*
* When max_row_insert option is set to LOOPCNT and when the errorneous
* statement is removed, all the rows will be inserted into the database in
* one stretch and hence maximum performance gain will be achieved.
*/
for (i = 1; i <= LOOPCNT; i++)
{
if (i != 5)
emp_in.empno = i;
else
/* Errorneous statement. In emp table, empno is defined as number(4). */
emp_in.empno = 10000;
sprintf(str, "EMP_%03d", i);
strcpy (emp_in.ename.arr, str);
emp_in.ename.len = strlen (emp_in.ename.arr);
sprintf(str, "JOB_%03d", i);
strcpy (emp_in.job.arr, str);
emp_in.job.len = strlen (emp_in.job.arr);
emp_in.mgr = i+1001;
sprintf(str, "%02d-MAY-06", (i%30));
strcpy (emp_in.hiredate, str);
emp_in.sal = (i) * 10;
emp_in.comm = (i) * 0.1;
emp_in.deptno = 10;
exec sql insert into emp values (:emp_in);
}
free (str);
exec sql commit;
exec sql select count(*) into :cnt from emp where ename like 'EMP_%';
printf ("Number of rows successfully inserted into emp table: %d\n", cnt);
}
/* Fetches data from the table*/
void fetchdata()
{
buffinstyp emp_out;
/* Declares cursor to fetch only the rows that are inserted */
exec sql declare c1 cursor for
select empno, ename, job, mgr, hiredate, sal, comm, deptno
from emp where ename like 'EMP_%' order by empno;
exec sql open c1;
exec sql whenever not found do break;
while(1)
{
/* Fetches single row at each call */
exec sql fetch c1 into :emp_out;
printempdata(emp_out);
}
exec sql whenever not found do sql_error();
exec sql close c1;
}
/* Prints the fetched employee data */
void printempdata(buffinstyp emp_out)
{
emp_out.ename.arr[emp_out.ename.len] = '\0';
emp_out.job.arr[emp_out.job.len] = '\0';
printf("Empno=%d, Ename=%s, Job=%s, Mgr=%d, Hiredate=%s, Sal=%6.2f,\n"
"Comm=%5.2f, Deptno=%d\n", emp_out.empno, emp_out.ename.arr,
emp_out.job.arr, emp_out.mgr, emp_out.hiredate, emp_out.sal,
emp_out.comm, emp_out.deptno);
}
/* Error handling function. */
void sql_error()
{
printf("Error %s\n", sqlca.sqlerrm.sqlerrmc);
printf(" Rows Processed: %d\n", sqlca.sqlerrd[2]);
printf(" Rows Rolled Back: %d\n", sqlca.sqlerrd[0]);
}