6 OCIでのSQL文の使用

この章では、Oracle Call Interfaceを使用したSQL文の処理に関する概念およびステップを説明します。

この章には次のトピックが含まれます:

SQL文の処理の概要

OCIプログラムの最も一般的なタスクの1つは、SQL文の受入れと処理です。

第3章「OCIプログラミングの基本」では、OCIアプリケーションに含まれる基本ステップについて説明しました。この章では、OCIプログラムでSQL文を処理するときの特定の作業について、さらに詳細に説明します。

この項では、SQL文の受入れと処理に含まれる特定のステップについて説明します。

必要なハンドルを割り当て、Oracle Databaseに接続したら、図6-1で説明されているステップに従います。

図6-1 SQL文の処理におけるステップ

図6-1の説明が続きます
「図6-1 SQL文の処理におけるステップ」の説明
  1. 文を準備します。OCIStmtPrepare2()を使用してアプリケーション要求を定義します。OCIStmtPrepare2()は、文キャッシュのサポートのために導入されたOCIStmtPrepare()の拡張バージョンです。Oracle Database 12cリリース2 (12.2)以上では、OCIStmtPrepare()は非推奨です。

  2. 必要な場合は、プレースホルダをバインドします。入力変数のあるDML文および問合せの場合、次の1つ以上のバインド・コールを実行して、各入力変数(またはPL/SQL出力変数)または配列のアドレスを、文内の各プレースホルダにバインドします。

    • OCIBindByPos2()またはOCIBindByPos()

    • OCIBindByName2()またはOCIBindByName()

    • OCIBindObject()

    • OCIBindDynamic()

    • OCIBindArrayOfStruct()

  3. OCIStmtExecute()をコールして文を実行します。DDL文の場合は、これ以降のステップは必要ありません。

  4. 必要な場合は、OCIParamGet()およびOCIAttrGet()を使用して、選択リスト項目を記述します。このステップはオプションで、選択リスト項目の数および各項目の属性(長さやデータ型など)がコンパイル時にわかっている場合には、必要ありません。

  5. 必要な場合、出力変数を定義します。問合せの場合は、OCIDefineByPos2()またはOCIDefineByPos()OCIDefineObject()OCIDefineDynamic()、またはOCIDefineArrayOfStruct()の定義コールを1つ以上実行して、SQL文の各選択リスト項目に対して出力変数を定義します。無名PL/SQLブロックの出力変数の定義には定義コールを使用しないように注意してください。これは、データのバインド時に行いました。

  6. 必要な場合は、OCIStmtFetch2()をコールして、問合せの結果をフェッチします。

これらのステップの完了後、アプリケーションでは割り当てたハンドルを解放し、次にサーバーとの接続を解除するか、追加の文を処理できます。

ノート:

OCIプログラムでは明示的な解析ステップが不要になりました。文を解析する必要がある場合、このステップは実行時に発生します(リリース8.0以上のアプリケーションでは、DML文とDDL文の両方の実行コマンドを発行する必要があるということです)。

各ステップの詳細は、これ以降の項で説明します。

ノート:

ステップの順序には、いくつかのバリエーションがあります。たとえば、コンパイル時にデータ型と戻される値の長さがわかっている場合には、実行ステップの前に定義ステップを処理できます。

アプリケーションで次のいずれかを行う場合には、前述のステップの他に追加ステップが必要です。

  • 複数トランザクションの開始および管理

  • 実行の複数スレッドの管理

  • ピース単位の挿入、更新またはフェッチの実行

文の準備について

SQL文およびPL/SQL文は、文の準備コールおよび必要なバインド・コールを使用して、実行の準備をします。

このフェーズでは、アプリケーションはSQL文またはPL/SQL文を指定し、文中の関連付けられたプレースホルダをデータにバインドして実行できるようにします。クライアント側ライブラリによって、実行準備の完了した文をメンテナンスするための記憶域が割り当てられます。

アプリケーションでは、OCIStmtPrepare2()コールを使用して、SQL文またはPL/SQL文の実行準備を要求し、事前に割り当てられている文ハンドルをこのコールに渡します。これは、完全なローカル・コールであるため、サーバーへのラウンドトリップは必要ありません。この時点では、文と特定のサーバーとの関連付けは行われません。

要求コールの後、アプリケーションでは、文ハンドルでOCIAttrGet()をコールし、attrtypeパラメータへOCI_ATTR_STMT_TYPEを渡すことにより、準備されたSQL文の種類を判断できます。可能な属性値とそれに対応する文の種類は、表6-1に記載されています。

表6-1 OCI_ATTR_STMT_TYPE値および文の種類

属性値 文の種類

OCI_STMT_SELECT

SELECT

OCI_STMT_UPDATE

UPDATE

OCI_STMT_DELETE

DELETE

OCI_STMT_INSERT

INSERT

OCI_STMT_CREATE

CREATE

OCI_STMT_DROP

DROP

OCI_STMT_ALTER

ALTER

OCI_STMT_BEGIN

BEGIN... (PL/SQL)

OCI_STMT_DECLARE

DECLARE... (PL/SQL)

OCI_STMT_CALL

CALL... (PL/SQL)

OCI_STMT_MERGE

MERGE... (PL/SQL)

この項には次のトピックが含まれます: プリペアド文を複数のサーバーで使用する方法について

プリペアド文を複数のサーバーで使用する方法について

文ハンドルとサーバーの個々のサービス・コンテキスト・ハンドルの再関連付けによって、準備完了アプリケーション要求を実行時に複数のサーバーに対して実行できます。

新たな関連付けが行われると、現行のサービス・コンテキスト・ハンドルと文ハンドル間の関連付けについての情報はすべて失われます。

たとえば、複数のサーバーを管理するネットワーク・マネージャのようなアプリケーションを考えてみます。多くの場合、情報を取り出して表示するには、同一のSELECT文を複数のサーバーに対して実行する必要があります。OCIによって、ネットワーク・マネージャ・アプリケーションでは、一度準備したSELECT文を複数のサーバーに対して実行できます。準備した文を次のサーバーに再度関連付けする前に、各サーバーから必要なすべての行をフェッチしておく必要があります。

ノート:

プリペアド文を同じサーバーで頻繁に再実行する必要がある場合は、別のサービス・コンテキスト用に新しい文を準備すると効率的です。

OCIのプレースホルダのバインドについて

大部分のDML文と一部の問合せ(WHERE句を使用した問合せなど)では、プログラムでデータをSQLまたはPL/SQL文の一部としてOracle Databaseに渡す必要があります。

このデータは、プログラムのコンパイル時にわかっている定数またはリテラル・データです。たとえば、次のSQL文は従業員をデータベースへ追加するものですが、これには'BESTRY'および2365などのリテラルがいくつか含まれています。

INSERT INTO emp VALUES
    (2365, 'BESTRY', 'PROGRAMMER', 2000, 20)

このように文をアプリケーション内にコーディングすると、その使用範囲が非常に限定されます。データベースに新しい従業員を追加するたびに、文を変更してプログラムを再コンパイルする必要があります。ユーザーが実行時に入力データを指定できるようにすれば、プログラムにもっと柔軟性を持たせることができます。

実行時に提供する入力データを含むSQL文またはPL/SQLブロックを準備する場合は、SQL文またはPL/SQLブロック内のプレースホルダを使用して、データの提供場所にマークを付ける必要があります。たとえば、次のSQL文に最初がコロンで始まる5つのプレースホルダ(:enameなど)が含まれており、プログラムによって入力データが提供される場所を示しているとします。

INSERT INTO emp VALUES
    (:empno, :ename, :job, :sal, :deptno)

入力変数のプレースホルダは、DELETE文、INSERT文、SELECT文、UPDATE文またはPL/SQLブロックで、式またはリテラル値を使用できる位置であれば文中の任意の位置で使用できます。PL/SQLでは、出力変数にもプレースホルダを使用できます。

プレースホルダを、表などの他のOracleオブジェクトを表すために使用することはできません。たとえば、次に示すのは、empプレースホルダの正しい使用方法ではありません

INSERT INTO :emp VALUES
    (12345, 'OERTEL', 'WRITER', 50000, 30)

SQL文またはPL/SQLブロックの各プレースホルダに対して、プログラム内の変数のアドレスをプレースホルダにバインドするOCIルーチンをコールする必要があります。文を実行すると、プログラムで入力変数またはバインド変数に入れたデータが読み取られ、それがSQL文でサーバーに渡されます。

バインドは、問合せ以外の操作では入力変数と出力変数の両方に使用されます。例6-1では、変数empno_outename_outjob_outsal_outおよびdeptno_outをバインドする必要があります。これらは、(通常のインバインドとは対照的な)アウトバインドです。

例6-1 問合せ以外の操作での入力変数と出力変数両方のバインド

INSERT INTO emp VALUES
         (:empno, :ename, :job, :sal, :deptno)
      RETURNING
         (empno, ename, job, sal, deptno)
      INTO
         (:empno_out, :ename_out, :job_out, :sal_out, :deptno_out)

この項には次のトピックが含まれます: プレースホルダのルール

関連項目:

バインド操作実行の詳細は、「OCIでのバインドおよび定義」を参照してください

プレースホルダのルール

プレースホルダ形成に関するルールをリストし、説明します。

プレースホルダ形成に関するルールは次のとおりです。

  • 先頭の文字はコロン(:)です。

  • コロンの後には、アンダースコア(_)、AからZ、aからz、0から9のいずれかの組合せが続きます。ただし、コロンの後の最初の文字はアンダースコアにできません。

  • 文字は英語のアルファベットから始める必要があり、意味を持つのはコロンの後の最初の30文字のみです。名前は大/小文字の区別があります。

  • プレースホルダは、コロンの後、数字のみで構成できます。数字のみの場合、プレースホルダは65536未満である必要があります。数字で名前が始まる場合、使用できるのは数字のみです。

  • ハイフン(-)は使用できません。

文の実行について

OCIアプリケーションは、OCIStmtExecute()を使用して、プリペアド文を個別に実行します。

OCIアプリケーションで問合せを実行すると、問合せ指定に一致するデータをOracle Databaseから受け取ります。データベース内では、データはOracle独自の形式で格納されています。戻された結果に対して、OCIアプリケーションでは、データを特定のホスト言語形式に変換し、それを特定の出力変数またはバッファ内に格納することを要求できます。

問合せの各選択リスト項目に対し、問合せの結果を受け取るため、OCIアプリケーションで出力変数を定義してください。定義ステップでは、バッファのアドレスおよび取り出すデータの型を指示します。

ノート:

OCIStmtExecute()コール前に出力変数がSELECT文に対して定義されている場合は、itersパラメータで指定した行数が定義済の出力バッファに直接フェッチされ、プリフェッチ・カウントと同じ数の追加行がプリフェッチされます。追加行がない場合、フェッチはOCIStmtFetch2()をコールしないで完了します。

問合せ以外の場合、配列操作中の文の実行回数はiters - rowoffと等しくなり、rowoffはバインド済配列のオフセットであり、OCIStmtExecute()コールのパラメータでもあります。

たとえば、10項目の配列がINSERT文の1個のプレースホルダにバインドされていて、itersが10と設定されている場合は、rowoffが0 (ゼロ)であれば、10項目すべてが1回の実行コールで挿入されます。rowoffに2が設定されていれば、挿入されるのは8項目のみです。

この項には次のトピックが含まれます:

実行スナップショット

OCIStmtExecute()コールを使用すると、データベースのコミット済データの一貫した同一スナップショット上で、複数のサービス・コンテキストが確実に動作します。

これは、特定の OCIStmtExecute()コールのsnap_outパラメータの内容を読み取り、その値を次のOCIStmtExecute()コールのsnap_inパラメータとして渡すことによって実現されます。

ノート:

同じスナップショットを使用している場合でも、1つのサービス・コンテキスト内でコミットされていないデータは、他のコンテキストでは認識されません。

snap_outおよびsnap_inパラメータのデータ型は、どちらもOCISnapshotです。OCISnapshotは、OCIDescriptorAlloc()関数によって割り当てられるOCIスナップショット記述子です。

OCIStmtExecute()をコールする際には、スナップショットを指定する必要はありません。次のサンプル・コードは、snapshotパラメータがNULLとして渡される基本的な実行例です。

checkerr(errhp, OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0,
          (OCISnapshot *)NULL, (OCISnapshot *) NULL, OCI_DEFAULT));

ノート:

ユーザーが開発したcheckerr()関数によって、OCIアプリケーションからのリターン・コードが評価されます。

OCIStmtExecute()の実行モード

OCIStmtExecute()コールのために、いくつかのモードを指定できます。

この項では、OCIStmtExecute()コールについて説明します。パラメータmodeのその他の値については、「OCIStmtExecute()」を参照してください。

この項には次のトピックが含まれます:

関連項目:

OCIStmtExecute()

バッチ・エラー・モードの使用

OCIでは、配列DML操作を実行できます。

たとえば、アプリケーションでは、INSERT文、UPDATE文またはDELETE文の配列を1回の文実行で処理できます。一意性制約違反などのサーバーからのエラーのために操作のいずれかが失敗すると、配列操作は終了し、OCIはエラーを戻します。その場合、配列内の残りの行はすべて無視されます。このため、アプリケーションでは残りの配列を再実行する必要があり、さらにエラーが発生した場合は、この処理全体を再度繰り返すことが必要で、これによって追加のラウンドトリップが発生します。

配列DML操作を容易に処理するために、OCIではバッチ・エラー・モード (拡張DML配列機能とも呼ばれます)が用意されています。このモードはOCIStmtExecute()コールに指定するもので、1つ以上のエラーが発生する場合のDML配列処理を単純化します。このモードでは、OCIはすべての行の挿入、更新または削除を試行し、発生したエラーに関する情報を収集します。次に、アプリケーションでエラー情報を取り出し、最初のコールで失敗した任意のDML操作を再実行できます。この方法では、配列内のすべてのDML操作が最初のコールで試行され、失敗した操作を2番目のコールで再発行できます。

ノート:

この機能は、リリース8.1以上のサーバーに対して実行中のリリース8.1以上のOCIライブラリにリンクされているアプリケーションでのみ使用可能です。また、この項で説明している新しいプログラム・ロジックを反映するために、アプリケーションを再コーディングする必要があります。

このモードは次のように使用します。

  1. OCIStmtExecute()コールのmodeパラメータとしてOCI_BATCH_ERRORSを指定します。
  2. OCIStmtExecute()で配列DML操作を実行した後、アプリケーションでは、次のコード例に示すように、文ハンドルでOCIAttrGet()をコールしてOCI_ATTR_NUM_DML_ERRORS属性を取り出すことにより、操作中に発生したエラー数を取得できます。

    配列DML操作中に発生したエラー数を取り出すためのOCIAttrGet()のコール

    ub4   num_errs;
    OCIAttrGet(stmtp, OCI_HTYPE_STMT, &num_errs, 0, OCI_ATTR_NUM_DML_ERRORS,
               errhp);
    
  3. アプリケーションでは、OCIParamGet()を使用して、OCIStmtExecute()コールに渡されたエラー・ハンドルから各エラーを行情報とともに抽出します。この情報を取り出すには、アプリケーションでOCIParamGet()コール用の追加の新しいエラー・ハンドルを割り当てる必要があり、この新しいエラー・ハンドルには、バッチとして記録されたエラー情報が含まれています。アプリケーションでは、OCIErrorGet()で各エラーの構文を取得し、新しいエラー・ハンドルでOCIAttrGet()をコールすることにより、エラーが発生した行のDML配列内のオフセットを取得します。

    たとえば、num_errsの結果が取り出された後で、アプリケーションから次のコールが次のコード例のように発行される可能性があります。

    配列のDML操作後の各エラーに関する情報の取出し

    OCIError errhndl, errhp2;
    for (i=0; i<num_errs; i++)
    {
       OCIParamGet(errhp, OCI_HTYPE_ERROR, errhp2, (void **)&errhndl, i);
       OCIAttrGet(errhndl, OCI_HTYPE_ERROR, &row_offset, 0,
           OCI_ATTR_DML_ROW_OFFSET, errhp2);
       OCIErrorGet(..., errhndl, ...);
    

    この操作の後に、アプリケーションではバッチとして記録されたエラーから取り出された診断情報を使用し、配列内の該当するエントリのバインド情報を修正できます。該当するバインド・バッファが修正または更新されると、アプリケーションでそれに対応するDML文を再実行できます。

    最初の実行でエラーの原因となる行をコンパイル時に特定できないため、後続のDMLのバインドは、実行時に該当するバッファを渡すことにより、動的に実行する必要があります。最初のDML操作での配列バインドで使用されたバインド・バッファを再利用できます。

バッチ・エラー・モードの例

バッチ・エラー実行モードの使用方法を示します。

例6-2は、バッチ・エラー実行モードがどのように使用されるかの例を示しています。この例では、表に5つの行を(NUMBERおよびCHAR型の2つの列とともに)挿入するアプリケーションがあるものとします。さらに、2つの行(1および3)のみが最初のDML操作で正常に挿入されるものとします。ユーザーは次に、データ(最初の操作で挿入された誤ったデータ)を修正し、修正したデータのUPDATEを発行します。ユーザーは文ハンドルstmtp1およびstmtp2を使用して、INSERT文およびUPDATE文をそれぞれ発行します。

例6-2では、どの行がエラーとともに戻されるかコンパイル時にはわからないため、コールバックでOCIBindDynamic()が使用されています。コールバックでは、row_offに格納されたエラーを含む行の番号をコールバック・コンテキストを通じて渡し、更新または修正の必要がある行のみを送信できます。INSERT文の実行とUPDATE文の実行では、同じバインド・バッファを共有できます。

例6-2 バッチ・エラー実行モードの使用

OCIBind *bindp1[2], *bindp2[2];
ub4 num_errs, row_off[MAXROWS], number[MAXROWS] = {1,2,3,4,5};
char grade[MAXROWS] = {'A','B','C','D','E'};
OCIError *errhp2;
OCIError *errhndl[MAXROWS];
...
/* Array bind all the positions */
OCIBindByPos (stmtp1,&bindp1[0],errhp,1,(void *)&number[0],
     sizeof(number[0]),SQLT_INT,(void *)0, (ub2 *)0,(ub2 *)0,
            0,(ub4 *)0,OCI_DEFAULT);
OCIBindByPos (stmtp1,&bindp1[1],errhp,2,(void *)&grade[0],
     sizeof(grade[0]),SQLT_CHR,(void *)0, (ub2 *)0,(ub2 *)0,0,
            (ub4 *)0,OCI_DEFAULT);
/* execute the array INSERT */
OCIStmtExecute (svchp,stmtp1,errhp,5,0,0,0,OCI_BATCH_ERRORS);
/* get the number of errors. A different error handler errhp2 is used so that
 * the state of errhp is not changed */
OCIAttrGet (stmtp1, OCI_HTYPE_STMT, &num_errs, 0,
            OCI_ATTR_NUM_DML_ERRORS, errhp2);
if (num_errs) {
   /* The user can do one of two things: 1) Allocate as many  */
   /* error handles as number of errors and free all handles  */
   /* at a later time; or 2) Allocate one err handle and reuse */
   /* the same handle for all the errors */
   for (i = 0; i < num_errs; i++) {
      OCIHandleAlloc( (void *)envhp, (void **)&errhndl[i],
      (ub4) OCI_HTYPE_ERROR, 0, (void *) 0);
      OCIParamGet(errhp, OCI_HTYPE_ERROR, errhp2, &errhndl[i], i);
      OCIAttrGet (errhndl[i], OCI_HTYPE_ERROR, &row_off[i], 0,
                  OCI_ATTR_DML_ROW_OFFSET, errhp2);
      /* get server diagnostics */
      OCIErrorGet (..., errhndl[i], ...);
      }
   }
/* make corrections to bind data */
OCIBindByPos (stmtp2,&bindp2[0],errhp,1,(void *)0,sizeof(grade[0]),SQLT_INT,
     (void *)0, (ub2 *)0,(ub2 *)0,0,(ub4 *)0,OCI_DATA_AT_EXEC);
OCIBindByPos (stmtp2,&bindp2[1],errhp,2,(void *)0,sizeof(number[0]),SQLT_DAT,
     (void *)0, (ub2 *)0,(ub2 *)0,0,(ub4 *)0,OCI_DATA_AT_EXEC);
/* register the callback for each bind handle, row_off and position
 * information can be passed to the callback function by means of context
 * pointers.
 */
OCIBindDynamic (bindp2[0],errhp,ctxp1,my_callback,0,0);
OCIBindDynamic (bindp2[1],errhp,ctxp2,my_callback,0,0);
/* execute the UPDATE statement */
OCIStmtExecute (svchp,stmtp2,errhp,num_errs,0,0,0,OCI_BATCH_ERRORS);
...

関連項目:

OCIBindDynamic()

選択リスト項目の記述について

OCIアプリケーションで問合せを処理する場合には、選択リスト項目に関して詳細な情報を取得する必要があります。

実行時まで問合せの内容がわからない動的問合せの場合は、特にそうです。そのような場合、プログラムでは、選択リスト項目のデータ型および列の長さに関する情報を取得する必要があります。この情報は、問合せの結果を受け取る出力変数を定義するために必要です。

たとえば、プログラムにはemployees表の列についての事前情報を持たない問合せについて考えてみます。

SELECT * FROM employees

使用可能な記述の種類には、暗黙的および明示的の2つがあります。

暗黙的記述では、情報にアクセスするためには特別なコールが必要ですが、サーバーからの記述情報を取得するためには特別なコールは必要ありません。暗黙的記述では、特定の記述コールを実行せずに文を実行した後、アプリケーションは文ハンドルの属性として選択リスト情報を取得できます。これは、記述コールが不要なため、暗黙的と呼ばれます。記述情報は、文の実行により解放されます。

明示的な記述は、サーバーから記述情報を取得するために、アプリケーションから特定の関数をコールする必要があります。アプリケーションでは、選択リスト(問合せ)を暗黙的にも明示的にも記述できます。その他のスキーマ要素は、明示的に記述する必要があります。

OCIStmtExecute()のモードとしてOCI_DESCRIBE_ONLYを指定することで(文は実行されませんが、選択リスト記述は戻されます)、問合せを実行前に明示的に記述できます。パフォーマンス向上のためには、標準的な文の実行で自動的に得られる暗黙的記述をアプリケーションで使用することをお薦めします。

OCIDescribeAny()コールを使用した明示的な記述では、選択リストではなく、スキーマ・オブジェクトに関する情報を取得します。

すべての場合において、列およびデータ型に関する特定の情報は、ハンドル属性を読み取ることによって検索されます。

この項には次のトピックが含まれます:

関連項目:

暗黙的記述

SQL文を実行した後、文ハンドルの属性として選択リストに関する情報を取得できます。明示的な記述コールは必要ありません。

複数の選択リスト項目に関する情報を取り出すには、最初にposパラメータを1と設定したOCIParamGet()をコールし、次にposの値を反復して、ORA-24334OCI_ERRORが戻されるまでOCIParamGet()コールを繰り返します。また、アプリケーションでは、列をランダムに取得するために、位置nを指定できます。

アプリケーションでは、選択リストの位置のパラメータ記述子を割り当てた後、パラメータ記述子についてOCIAttrGet()をコールすることによって、特定の情報を取り出せます。パラメータ記述子から取得できる情報として、データ型とパラメータの最大サイズがあります。

例6-3のサンプル・コードでは、問合せの実行後に、問合せに対応する列名およびデータ型を取り出すループを示しています。この問合せは、事前にOCIStmtPrepare2()のコールによって文ハンドルと関連付けられています。

例6-3checkerr()関数は、エラー処理に使用します。完全なリストは、「OCIデモ・プログラム」の最初のサンプル・アプリケーションを参照してください。

OCIAttrGet()およびOCIParamGet()のコールは、文の実行後すべての選択リスト情報がクライアント側にキャッシュされるために、ネットワーク・ラウンドトリップを必要としないローカル・コールです。

例6-3 暗黒的記述 - 選択リストは文ハンドルの属性として取得可能

...
OCIParam     *mypard = (OCIParam *) 0;
ub2          dtype;
text         *col_name;
ub4          counter, col_name_len, char_semantics;
ub2          col_width;
sb4          parm_status;

text *sqlstmt = (text *)"SELECT * FROM employees WHERE employee_id = 100";

checkerr(errhp, OCIStmtPrepare2(svchp, &stmthp, errhp, (OraText *)sqlstmt,
                    (ub4)strlen((char *)sqlstmt), NULL, 0,
                    (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT));

checkerr(errhp, OCIStmtExecute(svchp, stmthp, errhp, 0, 0, (OCISnapshot *)0, (OCISnapshot *)0, OCI_DEFAULT));

/* Request a parameter descriptor for position 1 in the select list */
counter = 1;
parm_status = OCIParamGet((void *)stmthp, OCI_HTYPE_STMT, errhp,
               (void **)&mypard, (ub4) counter);

/* Loop only if a descriptor was successfully retrieved for
   current position, starting at 1 */

while (parm_status == OCI_SUCCESS) {
   /* Retrieve the data type attribute */
   checkerr(errhp, OCIAttrGet((void*) mypard, (ub4) OCI_DTYPE_PARAM,
           (void*) &dtype,(ub4 *) 0, (ub4) OCI_ATTR_DATA_TYPE,
           (OCIError *) errhp  ));

   /* Retrieve the column name attribute */
   col_name_len = 0;
   checkerr(errhp, OCIAttrGet((void*) mypard, (ub4) OCI_DTYPE_PARAM,
           (void**) &col_name, (ub4 *) &col_name_len, (ub4) OCI_ATTR_NAME,
           (OCIError *) errhp ));

   /* Retrieve the length semantics for the column */
   char_semantics = 0;
   checkerr(errhp, OCIAttrGet((void*) mypard, (ub4) OCI_DTYPE_PARAM,
           (void*) &char_semantics,(ub4 *) 0, (ub4) OCI_ATTR_CHAR_USED,
           (OCIError *) errhp  ));
   col_width = 0;
   if (char_semantics)
       /* Retrieve the column width in characters */
       checkerr(errhp, OCIAttrGet((void*) mypard, (ub4) OCI_DTYPE_PARAM,
               (void*) &col_width, (ub4 *) 0, (ub4) OCI_ATTR_CHAR_SIZE,
               (OCIError *) errhp  ));
   else
       /* Retrieve the column width in bytes */
       checkerr(errhp, OCIAttrGet((void*) mypard, (ub4) OCI_DTYPE_PARAM,
               (void*) &col_width,(ub4 *) 0, (ub4) OCI_ATTR_DATA_SIZE,
               (OCIError *) errhp  ));

   /* increment counter and get next descriptor, if there is one */
   counter++;
   parm_status = OCIParamGet((void *)stmthp, OCI_HTYPE_STMT, errhp,
          (void **)&mypard, (ub4) counter);
} /* while */
...

関連項目:

問合せの明示的記述

OCIStmtExecute()のモードとしてOCI_DESCRIBE_ONLYを指定することで、問合せを実行前に明示的に記述できます

これにより文は実行されませんが、選択リスト記述は戻されます。

ノート:

パフォーマンスを最大にするために、アプリケーションではデフォルト・モードで文を実行し、実行に伴う暗黙的な記述を使用することをお薦めします。

例6-4のコードは、列に関する情報を戻すための選択リストでの明示的記述の使用を説明しています。

例6-4 明示的記述 - 列ごとに選択リストの記述を戻す

...
int i = 0;
ub4 numcols = 0;
ub2 type = 0;
OCIParam *colhd = (OCIParam *) 0;   /* column handle */

text *sqlstmt = (text *)"SELECT * FROM employees WHERE employee_id = 100";

checkerr(errhp, OCIStmtPrepare2(svchp, &stmthp, errhp, (OraText *)sqlstmt,
                    (ub4)strlen((char *)sqlstmt), NULL, 0,
                    (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT));

/* initialize svchp, stmthp, errhp, rowoff, iters, snap_in, snap_out */
/* set the execution mode to OCI_DESCRIBE_ONLY. Note that setting the mode to
OCI_DEFAULT does an implicit describe of the statement in addition to executing
the statement */

checkerr(errhp, OCIStmtExecute(svchp, stmthp, errhp, 0, 0,
        (OCISnapshot *) 0, (OCISnapshot *) 0, OCI_DESCRIBE_ONLY));

/* Get the number of columns in the query */
checkerr(errhp, OCIAttrGet((void *)stmthp, OCI_HTYPE_STMT, (void *)&numcols,
                      (ub4 *)0, OCI_ATTR_PARAM_COUNT, errhp));

/* go through the column list and retrieve the data type of each column.
Start from pos = 1 */
for (i = 1; i <= numcols; i++)
{
  /* get parameter for column i */
  checkerr(errhp, OCIParamGet((void *)stmthp, OCI_HTYPE_STMT, errhp, (void **)&colhd, i));

  /* get data-type of column i */
  type = 0;
  checkerr(errhp, OCIAttrGet((void *)colhd, OCI_DTYPE_PARAM,
          (void *)&type, (ub4 *)0, OCI_ATTR_DATA_TYPE, errhp));
}
...

関連項目:

OCIStmtExecute()

OCIでの出力変数の定義について

問合せ文は、データベースのデータをアプリケーションに戻します。

問合せを処理するときには、データを検索する選択リストの各項目について、出力変数または出力変数の配列を定義する必要があります。定義ステップでは、戻される結果の格納場所と格納形式を判断する関連付けを行います。

たとえば、次の文を処理するには、通常は2つの出力変数を定義します。1つはname列から戻される値を受け取るための変数、もう1つはssn列から戻される値を受け取るための変数です。

SELECT name, ssn FROM employees
    WHERE empno = :empnum

結果のフェッチについて

OCIアプリケーションで問合せを処理した場合は通常、文の実行が完了した後にOCIStmtFetch2()を使用して、結果をフェッチする必要があります。

OCIStmtFetch2()関数では、スクロール・カーソルをサポートします。

フェッチされたデータは、定義操作で指定した出力変数に格納されます。

ノート:

OCIStmtExecute()のコール前にSELECT文に出力変数を定義した場合は、itersパラメータで指定した行数が、定義されている出力バッファに直接フェッチされます。

関連項目:

LOBデータのフェッチについて

LOB列または属性が選択リストの一部である場合、そのLOB列または属性は、ユーザーの定義方法に応じて、LOBロケータまたは実際のLOB値として戻すことができます。

LOBロケータがフェッチされると、アプリケーションでは、OCILobXXX関数を介してそれらのロケータに対してさらに操作を実行できます。

関連項目:

プリフェッチ・カウントの設定について

サーバー・ラウンドトリップを最小限にし、パフォーマンスを最適化するために、OCIでは問合せの実行時に結果セット行をプリフェッチできます。

ユーザーは、OCIAttrSet()関数を使用して、文ハンドルのOCI_ATTR_PREFETCH_ROWS属性またはOCI_ATTR_PREFETCH_MEMORY属性を設定することにより、このプリフェッチをカスタマイズできます。属性は次のように使用します。

  • OCI_ATTR_PREFETCH_ROWSは、プリフェッチする行数を設定します。この属性が設定されていない場合、デフォルト値は1です。OCIStmtExecute()itersパラメータが0で、プリフェッチが使用可能な場合、OCIStmtFetch2()のコール中に行がバッファされます。プリフェッチ値は、実行後およびフェッチ中に変更できます。

  • OCI_ATTR_PREFETCH_MEMORYは、プリフェッチする行に割り当てられたメモリーを設定します。その後、アプリケーションでは、メモリーの容量が許すかぎりの行数をフェッチします。

これらの属性を設定すると、OCIでは、OCI_ATTR_PREFETCH_MEMORYで設定した最大メモリーに到達しないかぎり、OCI_ATTR_PREFETCH_ROWSで設定した行数まで行をプリフェッチし、この場合OCIは、OCI_ATTR_PREFETCH_MEMORYのバッファ・サイズに入る行数を戻します。

デフォルトでは、プリフェッチは有効で、プリフェッチが問合せに対してサポートできない場合を除き、OCIでは1行余分にフェッチします(この後の注意を参照)。プリフェッチをオフにするには、OCI_ATTR_PREFETCH_ROWS属性およびOCI_ATTR_PREFETCH_MEMORY属性の両方に0 (ゼロ)を設定します。

OCI_ATTR_PREFETCH_ROWS属性とOCI_ATTR_PREFETCH_MEMORY属性の両方が明示的に設定されている場合、OCIでは2つの制限のうちより厳しい方を使用して、プリフェッチする行数を決めます。

メモリー制約に基づいて排他的にプリフェッチするには、OCI_ATTR_PREFETCH_MEMORY属性を設定し、OCI_ATTR_PREFETCH_ROWS属性をゼロに設定して(1行のデフォルト設定をオーバーライドするため)必ず無効にします。

行数制約に基づいて排他的にプリフェッチするには、OCI_ATTR_PREFETCH_ROWS属性を設定し、OCI_ATTR_PREFETCH_MEMORY属性をゼロに設定して(明示的にゼロ以外の値に設定されている場合)無効にします。

プリフェッチは、REF CURSORおよびネストされたカーソル列で可能です。デフォルトでは、プリフェッチはREF CURSORに対して有効になっていません。REF CURSORに対してプリフェッチを有効にするには、REF CURSORから行をフェッチする前に、OCI_ATTR_PREFETCH_ROWS属性またはOCI_ATTR_PREFETCH_MEMORY属性を設定します。REF CURSORがOCIアプリケーションとPL/SQLとの間で複数回渡され、OCIとPL/SQLでREF CURSORに対するフェッチが実行されると、OCIでプリフェッチ(有効な場合)された行が原因で、アプリケーションは、PL/SQLで適切でない行がフェッチされているかのように動作します。そのような場合、OCIプリフェッチはREF CURSORに対して有効にすべきではありません。

ノート:

LONG、LOBまたは不透明型の列(XMLTypeなど)が問合せの一部である場合、プリフェッチは無効になります。

OCIでのスクロール・カーソルの使用について

カーソルは、結果セット内での現在の位置です。

カーソルを実行すると、問合せの結果は結果セットと呼ばれる行セットに格納され、連続的または非連続的にフェッチできます。非連続のフェッチでは、カーソルはスクロール・カーソルと呼ばれます。

スクロール・カーソルは、結果セットに対する絶対的または相対的な行番号のオフセットが使用することで、指定した位置から結果セットへの前後方向へのアクセスをサポートします。

行番号は1から始まります。スクロール・カーソルの場合は、以前にフェッチした行、結果セットのn番目の行、あるいは現在の位置からn番目の行をフェッチできます。クライアント側で結果セットの一部または全部をキャッシュすると、サーバーへのコールが制限されるためパフォーマンスが向上します。

スクロール・カーソルでのDML操作はサポートしていません。選択リストにLONGデータ型が含まれている場合、カーソルはスクロール可能にできません。

さらに、スクロール可能な文ハンドルからのフェッチは、実行時のスナップショットに基づいています。OCIクライアント・プリフェッチは、OCIスクロール・カーソルと連動します。クライアント・プリフェッチ・キャッシュのサイズは、既存のOCI属性であるOCI_ATTR_PREFETCH_ROWSOCI_ATTR_PREFETCH_MEMORYによって制御できます。

ノート:

スクロール・カーソルは、多くのサーバー・リソースを使用し、スクロール不可カーソルより応答時間が長くなる場合があるため、この機能を必要としないかぎり使用しないでください。

OCIStmtExecute()コールには、スクロール・カーソル用の実行モードであるOCI_STMT_SCROLLABLE_READONLYが用意されています。文ハンドルのデフォルトはスクロール不可で、前方への順次アクセスに限られ、モードはOCI_FETCH_NEXTです。この実行モードは、文ハンドルを実行するたびに設定する必要があります。

文ハンドル属性OCI_ATTR_CURRENT_POSITIONは、OCIAttrGet()を使用することによってのみ取得できます。この属性はアプリケーションでは設定できず、結果セット内の現在の位置を示します。

スクロール不可カーソルの場合、OCI_ATTR_ROW_COUNTは、この文ハンドルの実行後にOCIStmtFetch2()コールでユーザー・バッファにフェッチされた行の合計数です。スクロール不可カーソルは前方への順次アクセスのみであるため、OCI_ATTR_ROW_COUNTは、アプリケーションで検出できる最大行数も表します。

Oracle Databaseリリース12.1より、行カウント値がOCIアプリケーションのUB4MAXVALの値を超えてもよい場合は、OCI_ATTR_ROW_COUNT属性よりもOCI_ATTR_UB8_ROW_COUNT属性をお薦めします。

スクロール・カーソルの場合、OCI_ATTR_ROW_COUNTは、ユーザー・バッファにフェッチされた行の最大(絶対)数を表します。アプリケーションでは、任意の位置でフェッチを行うことができるため、これは、(スクロール可能な)文の実行後にユーザーのバッファにフェッチされた行の合計数である必要はありません。

文ハンドルのOCI_ATTR_ROWS_FETCHED属性は、前回のフェッチ・コールまたは実行で、ユーザーのバッファに正常にフェッチされた行数を表します。これは、スクロール・カーソルとスクロール不可カーソルの両方で使用されます。

OCIStmtFetch()コールのかわりに、下位互換性が保持されているOCIStmtFetch2()コールを使用します。スクロール・カーソルが使用されていない場合でも、すべての新規アプリケーションでOCIStmtFetch2()を使用することをお薦めします。このコールはスクロール不可カーソルでも動作しますが、OCI_DEFAULTまたはOCI_FETCH_NEXT以外の方向が渡された場合にエラーが発生することがあります。

リモートでマップされた問合せでは、スクロール・カーソルがサポートされています。スクロール・カーソルでは、透過的アプリケーション・フェイルオーバー(TAF)がサポートされています。

ノート:

nrowsパラメータに0 (ゼロ)を設定してOCIStmtFetch2()をコールした場合は、カーソルが取り消されます。

スクロール・カーソルのパフォーマンスの向上について

OCIのクライアント側プリフェッチ・バッファを使用すると、応答時間が短縮します。

スクロール・カーソルに対してOCIStmtExecute()をコールした後、OCI_FETCH_LASTを使用してOCIStmtFetch2()をコールし、結果セットのサイズを取得します。次に、OCI_ATTR_PREFETCH_ROWSをそのサイズの約20%に設定し、結果セットで大量のメモリーを使用している場合はOCI_PREFETCH_MEMORYを設定します。

スクロール・カーソルでのアクセスの例

スクロール・カーソルの使用方法を示します。

次のSQL問合せにより結果セットが戻され、表EMPに14行あるとします。

SELECT empno, ename FROM emp

スクロール・カーソルの使用方法の1つを例6-5に示します。

例6-5 スクロール・カーソルでのアクセス

...
/* execute the scrollable cursor in the scrollable mode */
OCIStmtExecute(svchp, stmthp, errhp, (ub4)0, (ub4)0, (CONST OCISnapshot *)NULL,
               (OCISnapshot *) NULL, OCI_STMT_SCROLLABLE_READONLY ); 
                                  
/* Fetches rows with absolute row numbers 6, 7, 8. After this call,
 OCI_ATTR_CURRENT_POSITION = 8, OCI_ATTR_ROW_COUNT = 8 */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 3, 
                               OCI_FETCH_ABSOLUTE, (sb4) 6, OCI_DEFAULT);
                               
/* Fetches rows with absolute row numbers 6, 7, 8. After this call,
 OCI_ATTR_CURRENT_POSITION = 8, OCI_ATTR_ROW_COUNT = 8 */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 3, 
                               OCI_FETCH_RELATIVE, (sb4) -2, OCI_DEFAULT);
                               
/* Fetches rows with absolute row numbers 14. After this call,
 OCI_ATTR_CURRENT_POSITION = 14, OCI_ATTR_ROW_COUNT = 14 */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 1, 
                               OCI_FETCH_LAST, (sb4) 0, OCI_DEFAULT);

/* Fetches rows with absolute row number 1. After this call,
 OCI_ATTR_CURRENT_POSITION = 1, OCI_ATTR_ROW_COUNT = 14 */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 1, 
                               OCI_FETCH_FIRST, (sb4) 0, OCI_DEFAULT);

/* Fetches rows with absolute row numbers 2, 3, 4. After this call,
 OCI_ATTR_CURRENT_POSITION = 4, OCI_ATTR_ROW_COUNT = 14 */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 3, 
                               OCI_FETCH_NEXT, (sb4) 0, OCI_DEFAULT);

/* Fetches rows with absolute row numbers 3,4,5,6,7. After this call,
 OCI_ATTR_CURRENT_POSITION = 7, OCI_ATTR_ROW_COUNT = 14. It is assumed 
the user's define memory is allocated. */
checkprint(errhp, OCIStmtFetch2(stmthp, errhp, (ub4) 5, 
                               OCI_FETCH_PRIOR, (sb4) 0, OCI_DEFAULT);
...
}
checkprint (errhp, status)
{
 ub4 rows_fetched;
/* This checks for any OCI errors before printing the results of the fetch call
   in the define buffers */
 checkerr (errhp, status);
 checkerr(errhp, OCIAttrGet((CONST void *) stmthp, OCI_HTYPE_STMT,
         (void *) &rows_fetched, (uint *) 0, OCI_ATTR_ROWS_FETCHED, errhp));
}
...