ヘッダーをスキップ

Pro*C/C++ プログラマーズ・ガイド
10gリリース2(10.2)

B19273-01
目次
目次
索引
索引

戻る 次へ

8 ホスト配列

この章では、配列を使用してコーディングを簡略化し、プログラムのパフォーマンスを改善する方法について説明します。配列を使用して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];

制限

オブジェクト型を除き、ポインタのホスト配列は宣言できません。

文字配列(文字列)を除き、SQL文内で参照できるホスト配列は1次元に限定されます。したがって、次の例で宣言されている2次元配列は無効です。

int hi_lo_scores[25][25];   /* not allowed */ 

配列の最大サイズ

1回のフェッチでアクセス可能なSQL文の配列要素の最大数は32KB(またはプラットフォームと使用可能なメモリーによっては32KB以上も可能)です。最大サイズを超えたホスト配列にアクセスすると、「パラメータの値が有効範囲外です。」というランタイム・エラーが生じます。文が無名PL/SQLブロックの場合、アクセス可能な配列要素数は32512をデータ型のサイズで割った値までに制限されます。

SQL文での配列の使用

ホスト配列は、INSERT文、UPDATE文、DELETE文では入力変数として、またSELECT文およびFETCH文のINTO句では出力変数として使用できます。

ホスト配列に使用される埋込みSQL構文は、単純ホスト変数に使用される埋込みSQL構文とほとんど同じです。ただし、オプションのFOR句で配列処理を制御できるという点では違いがあります。また、ホスト配列と単純ホスト変数を単一のSQL文内で併用するときにも制限があります。

以降の項では、DML文中でホスト配列を使用する方法を説明します。

ホスト配列の参照

単一のSQL文で複数のホスト配列を使用する場合、要素の数は同じにする必要があります。同じでない場合には、プリコンパイル時に「配列サイズが一致しません」という警告メッセージが出ます。この警告を無視すると、プリコンパイラではSQL操作で最小数の要素が使用されます。

次の例では、INSERTされるのは25行のみです。

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); 

Oracle制限事項

VALUES、SET、INTOまたはWHERE句でスカラーのホスト変数とホスト配列を併用することはできません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。

ホスト配列は、UPDATE文またはDELETE文でCURRENT OF句とともに使用できません。

ANSI制限事項および要件

配列インタフェースは、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またはFETCHでは、すべての列に標識変数を使用します。インジケータのない列がある場合は、プリコンパイラ・オプションunsafe_null=yesをかわりに使用できます。 


配列への選択

ホスト配列は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_ERRORオプションの詳細は、「プリコンパイラのオプション」を参照してください。 

カーソルのフェッチ

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 */ 
    ... 
} 
...

最後のフェッチで実際に戻された行数を必ずチェックして処理してください。

関連項目

「FETCHされる行数」 

sqlca.sqlerrd[2]の使用方法

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される行数

各FETCHが戻すのは、最大でも配列の全行数分までです。次のような場合は、配列の全行数より少ない行が戻されます。

戻された行の累積数は、このガイドでは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]の値は、結果セット内の合計行数を示します。スクロール可能カーソルでのホスト配列の使用方法を示す例は、「サンプル・プログラム: スクロール可能カーソルを使用するホスト配列」を参照してください。

サンプル・プログラム3: ホスト配列

この項のデモ・プログラムでは、Pro*C/C++で問合せを作成するときの配列の使用方法を示しています。特に、SQLCA(sqlca.sqlerrd[2])の処理済行数に注目してください。このプログラムは、demoディレクトリのファイルsample3.pcとして、オンラインで使用可能です。

関連項目

SQLCAの詳細は、「ランタイム・エラーの処理」を参照してください。 

/*
 *  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を実行していることに注意してください。 


ScrollDemo2.pc

/*
 *  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文で有効なホスト配列の使用を示します。

表8-1    SELECT INTOで有効なホスト配列 
INTO句  WHERE句  有効/無効 

配列 

配列 

無効 

スカラー 

スカラー 

有効 

配列 

スカラー 

有効 

スカラー 

配列 

無効 

NULL値のフェッチ

配列をSELECTおよびFETCHするときは、必ずインジケータ配列を使用します。このようにして、関連する出力ホスト配列内にNULLがあるかどうかをテストできます。

DBMS = V7またはDBMS=v8のときに、インジケータ配列に対応付けられていないホスト配列にNULL列値をSELECTまたはFETCHすると、Oracleは処理を停止し、sqlerrd[2]に処理済行数を設定しエラー・メッセージを出します。

また、SELECTまたはFETCHの結果、NULLの使用によるORA-24347などの警告が発生した場合や、列にインジケータ配列がない場合には、Oracleは処理を停止します。SELECTまたはFETCHのすべての列で標識変数を使用します。インジケータのない列がある場合は、プリコンパイラ・オプションunsafe_null=yesをかわりに使用できます。

切り捨てられた値のフェッチ

DBMS=V7のときは、切捨てによって警告メッセージが発行されますが、処理は継続されます。

また、配列をSELECTおよびFETCHするときは必ずインジケータ配列を使用します。これにより、Oracleで1つ以上の切り捨てられた列値が出力ホスト配列に割り当てられたときに、列値の元の長さが対応するインジケータ配列内に保存されます。

配列での挿入

ホスト配列は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]); 

配列での挿入の制限

INSERT文のVALUES句内ではポインタ配列は使用できません。つまり配列要素はすべてデータ項目である必要があります。

INSERT文のVALUES句では、スカラー・ホスト変数とホスト配列を併用できません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。

配列での更新

次の例に示すように、ホスト配列を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句では、ホスト配列は使用できません。

関連項目

代替方法については、「CURRENT OFの疑似実行」を参照してください。 

表8-2は、UPDATE文で有効なホスト配列の使用を示しています。

表8-2    UPDATE文で有効なホスト配列 
SET句  WHERE句  有効/無効 

配列 

配列 

有効 

スカラー 

スカラー 

有効 

配列 

スカラー 

無効 

スカラー 

配列 

無効 

配列での削除

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;
...

配列での削除の制限

DELETE文のWHERE句内では、単純ホスト変数とホスト配列を併用できません。ホスト変数のうち1つでも配列があれば、すべてのホスト変数を配列にする必要があります。

DELETE文のCURRENT OF句ではホスト配列は使用できません。

関連項目

代替方法については、「CURRENT OFの疑似実行」を参照してください。 

FOR句の使用方法

埋込みSQLでオプションのFOR句を使用すると、次に示すSQL文が処理する配列要素の数を設定できます。

特に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句の変数では、処理する配列の要素数を指定します。この数は、最も小さい配列サイズを超過しないように設定します。この値は内部的には記号のついていない数量として扱われます。付号付きのホスト変数を使用して負の値を渡すと、予期せぬ動作が発生します。

FOR句の制限

2つの制限事項によってFOR句の使用方法が明確になります。FOR句はSELECT文では使用できません。またCURRENT OF句とともに使用することはできません。

SELECT文中

SELECT文中でFOR句を使用すると、エラー・メッセージが戻されます。

FOR句は意味があいまいなため、SELECT文中では使用できません。「このSELECT文をn回実行する」なのか、「このSELECT文を1回実行し、n行戻す」なのかはっきりしません。前者のように解釈すると、実行ごとに複数行が戻されることになります。後者の解釈では、次に示すように、カーソルを宣言してからFETCH文中でFOR句を使用することをお薦めします。

EXEC SQL FOR :limit FETCH emp_cursor INTO ... 

CURRENT OF句を使用する場合

次の例に示すように、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; 

WHERE句の使用方法

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ブロック内で使用することもできません。「構造体の配列の制限」も参照してください。 


構造体の配列は、複数の列で複数の行を操作するために使用され、通常次のように使用されると考えてください。

構造体の配列の使用方法

構造体の配列という概念は、C言語のプログラマにとって特に目新しいものではありません。しかし、複数の並列配列からなる1つの構造体と比較してみると、データの格納方法に考え方の違いがあります。

複数の並列配列からなる1つの構造体では、個々の列のデータが連続して格納されます。一方、構造体の配列では、列のデータはインタリーブされます。この場合、配列内の各列の間は、その構造体内の他の列に必要な空白で区切られます。この空白はストライド(stride)と呼ばれます。

構造体の配列の制限

Pro*C/C++では、構造体配列の使用に次の制限事項があります。

構造体配列の宣言では構文に違いはありません。ただし、構造体配列を使用する場合には留意事項がいくつかあります。

構造体の配列の宣言

Pro*C/C++アプリケーションで使用する構造体配列を宣言する場合、プログラマは必ず次の点に留意してください。

このコード・セグメントでは、person変数が構造体のタグです。このタグによって、プリコンパイラは構造体の名前を使用してストライドのサイズを計算できます。

構造体配列の使用に関する前述の制限事項を満たしているので、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 */

基本的には、個々の配列項目を同値化することはできません。

変数のガイドライン

標識変数は、構造体の配列の宣言でも、通常の構造体の宣言の場合とほとんど同じはたらきをします。構造体のインジケータ配列の宣言は、構造体の配列についての次の規則にも従う必要があります。

これらの規則のほとんどには、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++での構造体配列の機能について、様々な使用方法を示しています。

例1: スカラー構造体の単純な配列

次の構造体の宣言を指定したとします。

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);

例2: スカラーの配列と構造体の配列との組合せ使用

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列しかないインジケータ配列が対応付けられています。

構造体の配列とsalcommおよびcomm_ind配列にデータを挿入する場合、その挿入方法は非常に簡単です。

exec sql insert into emp (empno, ename, sal, comm)
   values (:emp, :sal, :comm indicator :comm_ind);

例3: 複数の構造体配列と1つのカーソルとの組合せ使用

次の宣言をこの例として使用します。

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;
FOR句の使用方法

FOR句を使用しても、FETCHで取り出す行数を指定できます。FOR句は、SELECT文を使用している場合には指定できませんが、INSERT文やFETCH文の場合には指定できます。

元の宣言に次を追加します。

int limit = 10;

次は、それに対応したコードの例です。

   exec sql for :limit
      fetch c into :emp, :wage indicator :wage_ind;

例4: 個々の配列メンバーおよび構造体メンバーの参照

これまでのPro*C/C++リリースで、構造体配列内の1構造体に対する配列参照が可能となっています。これによりバインド式はスカラーの単純な構造体で解決できるため、次は有効です。

exec sql select * into :dept[3] from emp;

次の例に示すように、構造体配列内の特定の構造体のスカラー・メンバーを個別に参照することも可能です。

exec sql select dname into :dept[3].dname from dept where ...;

この場合には当然、問合せは単一行問合せにする必要があり、1行のみを選択してこのバインド式で表される変数に代入します。

例5: 標識変数の使用(特殊な場合)

これまでの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-01405: フェッチした列の値が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バインド変数にどのようにマップされるかに注意してください。

例6: 構造体配列へのポインタの使用

この例では、構造体配列へのポインタの使用方法を示します。

次の型の宣言を考えます。

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句の使用方法を間違えないように、十分に注意してください。

CURRENT OFの疑似実行

DELETE文またはUPDATE文でCURRENT OF cursor句を使用すると、カーソルから最後にフェッチされた行を参照できます。ただし、CURRENT OF句とホスト配列の併用はできません。かわりに各行のROWIDを取得し、更新または削除するときにその値を使用して現在行を識別してください。

関連項目

「CURRENT OF句」 

次に例を示します。

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
Copyright © 2005 Oracle Corporation.

All Rights Reserved.
目次
目次
索引
索引