# CHAPTER 6: ダイナミック SQL コマンド

## 6.1 はじめに

この章ではプリコンパイラのためのダイナミック SQL のいくつかの新しいコマンドを 紹介します。これらのコマンドを記述する際に、スタテック SQL コマンドと同様に `EXEC SQL`キーワードを前に指定する必要があります。

EXECUTE IMMEDIATE

  文字列変数およびリテラルの SQL ステートメントを実行します。 このステートメントは、パラメータを含めることはできません。 また、問い合わせ(`SELECT`ステートメント)を実行しないでください。

PREPARE

  SQL ステートメントの準備(プリペア)は、後の実行のための文字列変数および リテラルを保持します。 ステートメントに含まれるパラメータは、実行する前に準備する必要があります。

EXECUTE

  プリペア済みの SQL ステートメントを実行します。 実行する前にプリペア済みステートメント中のパラメータへ値を割り当てます。 問い合わせ(クエリ)は実行できません。

DECLARE, OPEN, FETCH, FETCH_AGAIN CLOSE ダイナミックカーソル

  これらのコマンドはスタティック SQL のものと同様で、 `PREPARE` コマンドにより準備された 問い合わせを処理するために使用されます。

ALLOCATE, DEALLOCATE DESCRIPTOR

  プリペア済み SQL ステートメント中のパラメータについての情報を格納するために 使用されるデータ構造体(ディスクリプタ)の作成および削除。

DESCRIBE INPUT, OUTPUT

  SQL ステートメント中のパラメータ またはクエリによる選択されたアイテムについての情報を取得し、 ディスクリプタへその情報を格納。

GET, SET DESCRIPTOR

  ディスクリプタで保持している情報の取得および変更。

AUTOMATIC

  ポインタを使用してデータを取得する場合に スペースを自動的にアロケートする指定。

## 6.2 EXECUTE IMMEDIATE

### 6.2.1 パラメータなしのステートメントの実行

パラメータを含まず、問い合わせ(`SELECT`ステートメント)ではない SQL ステートメントは、実行する前の準備なしに実行することができます。 スタテック SQL 操作は、データ操作言語コマンド (`SELECT`、 `UPDATE`、 `INSERT`、 `DELETE`) およびトランザクションコマンドに制限されますが、 `EXECUTE IMMEDIATE`は、任意の SQL操作(`SELECT`とトランザクション関係を 除く)を実行することが可能です。 標準出力に出力するステートメント(例えば`DISPLAY DATABASE`)も 実行することができます。 注意として以下の問い合わせではなく、挿入操作と考慮される SQLステートメントは、`EXECUTE`コマンドにおいて許可されます。

       SELECT * FROM table1 INSERT INTO table2;

SQL ステートメントは、リテラル文字列 または、文字が含まれる文字列配列(文字列)の変数、 または文字のためのメモリ領域をアロケートしたポインタ変数 を指定することができます。 ( C 言語の変数を、SQLコマンドに埋め込む場合、変数の前にコロンを付ける必要が あることに注意してください。)

以下のように文字列のリテラルの実行ができます。

       EXEC SQL EXECUTE IMMEDIATE "CREATE INDEX ON books.publisher";

この場合、プログラムを実行している間、SQL ステートメントを変更できないため、 用法的には実際、動的ではありませんが、 スタテックSQLでは操作できないコマンドを実行するためには有用です。

また、文字配列の変数に保持したステートメントを実行する場合は、 以下のようになります。

       strcpy (sqls, "GRANT display, select ON books TO student1");
       EXEC SQL EXECUTE IMMEDIATE :sqls;

実行する前に動的にステートメントを作成する場合は、 以下のようになります。

       strcpy (sqls, "UPDATE books ");
       strcat (sqls, "SET price = price*1.2 ");
       strcat (sqls, "WHERE publisher = 102");
       EXEC SQL EXECUTE IMMEDIATE :sqls;

上の例のように文字配列およびポインタ変数を使用する場合、 プログラム中において実行する前にSQL ステートメントを 変更することができます。

Empress C インターフェイスを理解されているユーザーにとって、 `EXECUTE IMMEDIATE`コマンドは、 `mscall`ルーチンとほとんど同じ目的で実行されることに 気づくかもしれません。 `mscall`ルーチンは、問い合わせ（`SELECT`）や トランザクションを実行することができるため、より柔軟性があります。 `mscall`ルーチンの詳細な情報は、 Empress ホスト言語: コマンド言語インターフェイス を参照してください。

### 6.2.2 サンプルプログラム

以下のプログラムは、文字列のリテラルと文字配列を伴った `EXECUTE IMMEDIATE`コマンドの使用例を示します。 `books`というテーブルを作成し、 そのテーブルにいくつかのレコードを挿入し、テーブル構造を表示します。 実行時に`INSERT`ステートメントを作成することに注意してください。 また、このプログラムではグローバル変数`SQLCODE`と`SQLERRMC`を 使用して、実行時のエラーを検出する方法を紹介します。

       #include <mscc.h>

       /*  SQL コミュニケーションエリアは、
         アプリケーションとのコミュニケーションをするために
          Empressによって使用されるデータ構造体             */

       EXEC SQL INCLUDE SQLCA;

       /* SQL ステートメントに埋め込まれたすべての C 変数は、
          以下のセクションに宣言する必要があります。
          str はSQL ステートメントを保持するために使用される文字配列です。           */

       EXEC SQL BEGIN DECLARE SECTION;
            char        str[512];
       EXEC SQL END DECLARE SECTION;

       /* テーブルに挿入するためのデータを含む文字列変数 */

       char  *data[] =
       {
          " (125, 'Introduction to SQL', '$35')",
          " (134, 'UNIX Shell Programming', '$29.90')",
          " (134, 'Using X-Windows', '$32.95')",
          " (125, 'Database Design', '$30.50')",
          0
       };

       main ()
       {
          int i;

       /* データベースアクセスのためのプログラムの初期化                 */

       EXEC SQL INIT;
       EXEC SQL DATABASE IS "bookstore";

       /* テーブルを作成するためのリテラル文字列の実行
          実行時のエラーかどうかSQLCODEでチェック                  */

       EXEC SQL EXECUTE IMMEDIATE "CREATE TABLE books
          (publisher INTEGER, title CHAR(40,1), price DOLLAR(4,1))";
       if (SQLCODE) error("Create");

       /* data 配列の値として動的に作成したSQLステートメントを格納し、その値を実行。        */

       for (i=0; data[i]; i++)
       {
          strcpy (str, "INSERT books VALUES");
          strcat (str, data[i]);
          EXEC SQL EXECUTE IMMEDIATE :str;
          if (SQLCODE) error("Insert");
       }

       /* テーブルの詳細情報を表示するためにリテラル文字列を実行。
          出力は標準出力に送られる。                    */

       EXEC SQL EXECUTE IMMEDIATE "DISPLAY books ALL";

       /* アプリケーションを終了する前にクリーンアップ処理を行う。                */

       EXEC SQL EXIT;
       }

       /* エラーコードとメッセージを出力する関数           */

       error (cmd)
       char *cmd;
       {
          printf ("%s Error,  SQLCODE: %d\n", cmd, SQLCODE);
          printf ("SQLERRMC: %s\n", SQLERRMC);
          EXEC SQL EXIT;
          exit (1);
       }

## 6.3 PREPARE & EXECUTE

### 6.3.1 準備のためのステートメント

パラメータが含まれているSQL ステートメントを 実行する前には準備する必要があります。 SQL ステートメントの`PREPARE`コマンドは、 名前と関連したSQL ステートメントです。 SQL ステートメントは パラメータのマーカー`?`によって示された 任意の数のパラメータを持つことができます。 ステートメントは 文字列リテラル、文字配列変数、または 有効なメモリ領域を指し示す文字型ポインタ変数として `PREPARE`コマンドに渡すことができます。

文字列リテラルを準備するための例として、 以下の例を検討してみます。

       EXEC SQL PREPARE u1 FROM
          "UPDATE books SET price = ? WHERE title = ?";

上記のステートメントは、 実行時にテーブル`books`内の アトリビュート`title`が、指定された文字列と等しい レコードのアトリビュート`price`を更新します。

> Empress バージョン 8.62より前のバージョンでは、 文字列値のためのプレイスフォルダには、 引用符を付ける必要がありました。 `title`は、文字型のアトリビュートとして仮定すると 上記の準備するステートメントは以下のようになります。

>        EXEC SQL PREPARE u1 FROM
>              "UPDATE books SET price = ? WHERE title = '?'";

> プレイスフォルダに引用符がない場合、 文字型のアトリビュートのためのプレイスフォルダに パラメータを割り付けるために引用符をつける必要がありました。 Empress バージョン v8.62 では、 プレイスフォルダに引用符をつける必要はありません。 これは、ダイナミック SQL の ANSI SQL 標準の互換のためです。

以下は 文字配列変数、またはメモリがアロケートされたポインタ変数の ステートメントを準備する例になります。

       strcpy (sqls, "DELETE FROM books WHERE publisher = ?");
       EXEC SQL PREPARE del2 FROM :sqls;

上記のステートメントは 実行時にテーブル`books`の `publisher`に指定された値 のレコードを削除します。

以下の例は動的に作成されたステートメントを準備します。

       strcpy (sqls, "CREATE UNIQUE INDEX");
       strcat (sqls, " ? ");
       strcat (sqls, "ON ? ");
       strcat (sqls, "(?, ?)");
       EXEC SQL PREPARE cindex FROM :sqls;

上記のステートメントは２つのアトリビュート上にユニークインデックスを作成します。 インデックスの名前とテーブルおよびアトリビュートは実行に全て指定します。

ステートメント名`u1`、`del2`、`cindex`は、 変数ではなく、 プリコンパイラによって識別子として使用されます。 それらは後に 各ステートメントを実行することを 識別するために使用されます。 指定されたステートメント名は、 同一SQL ステートメントまたは異なったSQL ステートメントと共に 複数回、準備することができます。

### 6.3.2 プリペア済みのステートメントの実行

プリペア済みの SQL ステートメントは`EXECUTE`コマンドを使用し実行します。 コマンドはまた `USING`句によって指定された プリペア済みのステートメント中のパラメータを 埋め込み変数に関連付けます。 実行時、`EXECUTE`コマンドは、 プリペア済みのステートメント中の パラメーターを変数の値に置き換え、 その結果のステートメントを実行します。 プリペア済みのステートメントは複数回実行することができます。 (変数の異なった値、また異なった変数を持っていても) 問い合わせ(`SELECT` ステートメント)は `EXECUTE`コマンドでは処理することができません。

以下の例は異なった変数を持つ、プリペア済みのステートメントを２回実行します。

       EXEC SQL PREPARE s1 FROM "GRANT select ON ? TO ?";
       strcpy (tab1, "books");
       strcpy (user1, "teacher1");
       strcpy (user2, "student2");
       EXEC SQL EXECUTE s1 USING :tab1, :user1;
       EXEC SQL EXECUTE s1 USING :tab1, :user2;

上記のステートメントは、変数はすべて文字列配列です。 実行時、これらの変数に格納された文字列は、 プリペア済みSQL ステートメント中のパラメータと置き換わります。

以下の ２つのSQL ステートメントを作成します。

       GRANT select ON books TO teacher1
       GRANT select ON books TO student2

以下の例は、同じ変数で異なった値を持つ場合、 プリペア済みのステートメントを２度実行する例です。

       EXEC SQL PREPARE s2 FROM "UPDATE books SET price = price*? WHERE title
    MATCH ?";
       percent = 1.15;
       strcpy (where_c, "Intro*");
       EXEC SQL EXECUTE s2 USING :percent, :where_c;
       percent = 1.2;
       strcpy (where_c, "SQL*");
       EXEC SQL EXECUTE s2 USING :percent, :where_c;

上記のステートメントは、変数の利用について説明しています。 実行時、以下のSQL ステートメントが作成されます。

       UPDATE books SET price = price*1.15 WHERE title match 'Intro*'
       UPDATE books SET price = price*1.2  WHERE title match 'SQL*'

`USING`句の変数の順番は、 プリペア済みステートメント内の対応するパラメータに値を置き換えるため に重要ですので注意してください。 `USING`句の変数の数は、 プリペア済みステートメント内のパラメータの数と同じでなければなりません。 そうでない場合、実行時にエラーが発生します。 この規則は制御変数には適用されません。

`USING`句は、また プリペア済みの SQL ステートメント内のパラメータと SQL ディスクリプタを関連付けます。 ディスクリプタの使用については、後のセクションで説明します。

### 6.3.3 変数のデータタイプ

`USING` 句の変数は、任意の基本的な C のデータタイプになることができます。 Empressは、実行時に必要がある場合、データタイプ変換を実行します。 アトリビュート値を割り当てる際、アトリビュートのデータタイプに対応した おおよそのデータタイプの変数を使用します。 例えば、通常 Empress `REAL`アトリビュートの値を割り当てるために C の float 変数を使用しますが、 C の int 変数を使用することもできます。

文字配列変数および文字ポインタは 任意のデータタイプのアトリビュートの値をアサインするために 使用することができます。 (Empressが、そのデータが外部(文字列)フォーマットと 仮定します。)

`BULK`アトリビュートに値を割り付けるには、 以下のような構造体か、その構造体のポインタを使用する必要があります。

       typedef struct
       {
          long  size;          /* データのバイト数       */
          char  data[1024];    /* バルクデータを格納するための
                                  任意のタイプの配列をここに指定することができます。*/
       }

外部フォーマットデータの割り当てに使用されるため、 文字型配列以外にも、他の配列変数を`BULK`アトリビュートの割り当て のために使用することができます。

### 6.3.4 制御変数

`USING`句内の変数の数は、 プリペア済みのパラメータの数と同じでなくてはなりません。 そうではない場合、実行時エラーが発生します。

上記の規則は、制御変数に適用されません。 以下の例は、ステートメント`s3`は、 ２つのパラメータを持ちますが、 `USING`句は3つの変数を持ちます。 この場合、変数`cntl_pr`は、 セパレータのカンマなしに 変数`price_var`がそれに先行する 制御変数です。

       EXEC SQL PREPARE s3 FROM "INSERT books (title, price) VALUES (?, ?)"
       EXEC SQL EXECUTE s3 USING :title_var, :price_var :cntl_pr;

制御変数`cntl_pr`が負の値である場合、 `price_var`の値に関わらず、 アトリビュート`price`に `NULL`値が挿入されたこと示します。

### 6.3.5 サンプルプログラム

以下のプログラムは、`PREPARE`と`EXECUTE` コマンドの使用について示しています。 テーブル`books`のアトリビュートを更新することを許可しています。 アトリビュートを更新するためには、 実行時にユーザーから、 新しい値と`WHERE`句の内容を取得します。

       #include <mscc.h>

       EXEC SQL INCLUDE SQLCA;

       EXEC SQL BEGIN DECLARE SECTION;
            char  sqls[512];          /* SQL ステートメント            */
            char  aname1[50];         /* 更新するアトリビュート        */
            char  new_val[128];       /* アトリビュートの新しい値      */
            char  aname2[50];         /* 比較するためのアトリビュート  */
            char  test_val[128];      /* 比較するための値              */
            char  cond[50];           /* 比較オペレータ                */

       EXEC SQL END DECLARE SECTION;

       main ()
       {
          EXEC SQL INIT;
          EXEC SQL DATABASE IS "my_db";

          /*
             SQL ステートメントを準備します。
             ここでのポイントは、更新するためのアトリビュートとその新しい値
             および更新するレコードを検索するための条件の全てを知らないということです。
          */
          strcpy (sqls, "UPDATE books SET price = ? WHERE title match ?");
          EXEC SQL PREPARE s1 FROM :sqls;
          if (SQLCODE) error("Prepare");

          /* ユーザーからデータを取得します。                                */

          printf ("New value for price: ", aname1);
          scanf ("%s", new_val);
          printf ("Compare title  to what value? ", aname2);
          scanf ("%s", test_val);

          /* データと共にプリペア済みステートメントを実行します
             この場合、全ての変数は文字配列です           */

          EXEC SQL EXECUTE s1 USING :aname1, :new_val, :test_val;
          if (SQLCODE) error("Execute");

          EXEC SQL EXIT;
       }

       /* 終了する前にエラーコードとメッセージを表示する関数  */

       error (cmd)
       char *cmd;
       {
          printf ("%s Error,  SQLCODE: %d\n", cmd, SQLCODE);
          printf ("SQLERRMC: %s\n", SQLERRMC);
          EXEC SQL EXIT;
          exit (1);
       }

以下は実行例で、太字はユーザーの入力です。

       New value for attribute price: 32.50
       Compare title to what value? *program*

以下のSQL ステートメントが上記のセッションの終了時点で実行されます。

       UPDATE books SET price = 32.50 WHERE title match '*program*'

文字列の値（例えばサンプルプログラムの*program*のような) に単一引用符(')あるいは二重引用符(")は、 必要がない ことに注意してください。

## 6.4 ダイナミックカーソル

### 6.4.1 ダイナミックSQLのクエリ

`EXECUTE IMMEDIATE`コマンド、また `PREPARE`と`EXECUTE`コマンドも クエリ(`SELECT` ステートメント)を実行することはできません。 スタテックSQLコマンドの`SELECT INTO`は、 1 レコードの検索のために使用することができますが、 ダイナミックSQL はこれに相当するコマンドを持っていません。 そのため、ダイナミック SQL のクエリを処理するためのカーソルを使用する 必要があります。 ダイナミックカーソルを使用するクエリを処理するために必要なステップは スタテックカーソルのステップと似ていますが、コマンドの構文が異なります。

1. `PREPARE` statement_name `FROM` select_statement
2. `DECLARE` cursor_name `CURSOR FOR` statement_name
3. `OPEN` cursor_name [`USING` variable_list]
4. `FETCH` cursor_name `INTO` variable_list
5. `CLOSE` cursor_name

### 6.4.2 クエリの準備

`PREPARE`コマンドは、 ステートメント名(プリコンパイラが識別するための名前でプログラムの変数ではありません) とステートメントを関連付け、 `SELECT`ステートメントを準備するために使用されます。 `SELECT`ステートメントは文字列リテラルかあるいは文字配列変数かまたは 文字型ポインタ(そのポインタにメモリをアロケートした)のいずれかであり、 `?`で表される任意の数のパラメータを含む可能性があります。

以下はパラメータなしのクエリの準備の例になります。、

       EXEC SQL PREPARE q1 FROM "SELECT title, price FROM books";

上記のステートメントは、 `books`テーブルから 全てのレコード(`WHERE`句は指定されていない。)の `title`と`price`アトリビュートのみを取得するクエリの準備です。

以下はパラメータ付きのクエリの準備の例になります。、

       EXEC SQL PREPARE q2 FROM "SELECT title FROM books WHERE price = ?";

ステートメントは、`books`テーブルの不定のアトリビュートを 検索するためのクエリの準備です。 この時点では、検索条件となる本の定価はわかりません。

スタテック SQL クエリでは単にアトリビュートを選択することができますが、 ダイナミック SQL クエリでは、算術およびユーザー定義関数またオペレータ のビルトインを含む式を使用することができます。

       strcpy (sqls, "SELECT ");
       strcat (sqls, "w_date, ");
       strcat (sqls, "SUBSTR(comments, 1, 20), ");
       strcat (sqls, "celsius*1.8+32 ");
       strcat (sqls, "FROM weather WHERE ");
       strcat (sqls, "monthof(w_date) = 'January'");
       EXEC SQL PREPARE q3 FROM :sqls;

このクエリはテーブル`weather` から 3つのアイテム [月 January:、 コメントの最初の20文字(`SUBSTR`(comments,1,20)), 華氏気温 (celsius*1.8+32)] を取得するクエリです。

### 6.4.3 カーソルの宣言

カーソルは、 クエリとカーソル名を関連付けることにより 宣言されます。 通常、クエリはパラメータを含んだプリペア済みのステートメントです。 カーソル名は、プリコンパイラの識別子であり、プログラムの変数ではありません。

パラメータ付きのプリペア済みのステートメントに対してのカーソルの宣言は、 以下のような例になります。

       EXEC SQL PREPARE q2 FROM "SELECT title FROM books WHERE price = ?";
       EXEC SQL DECLARE c2 CURSOR FOR q2;

上の例は、カーソル`c2`を定義し、 ステートメント`q2`とそれを関連付けします。

`SELECT` ステートメントにパラメータが含まれていない場合は、 プリペアする必要はありません。 この場合、プリペア済みステートメントの名前を使用する代わりに カーソルは、 文字列リテラルかあるいは変数として指定されたクエリと関連付けられます。

       EXEC SQL DECLARE c3 CURSOR FOR "SELECT * FROM books WHERE price > 40";
       strcpy (query, "SELECT title FROM books WHERE publisher = 140");
       EXEC SQL DECLARE c4 CURSOR FOR :query;

デフォルトでは、 カーソルは読み込み操作モードでのみ宣言されます。 更新操作にカーソルを使用したい場合は、 明示的に`OPEN_TABLE`コマンドを使用して テーブルをオープンするか、 あるいは`FOR UPDATE`または`FOR DEFERRED`を指定して カーソルを宣言するかのいずれかになります。

更新操作のためのカーソルの宣言は以下の例になります。

       EXEC SQL PREPARE q4 FROM "SELECT title, publisher FROM books WHERE price = ?";
       EXEC SQL DECLARE c4 CURSOR FOR q4 FOR UPDATE;

上記のステートメントは、 テーブル`books`に対し更新操作を実行の許可をする カーソル`c4`を定義します。

指定されたカーソル名は、1回だけ宣言することができます。 また、カーソルは同時に複数宣言でき、オープンすることができます。

### 6.4.4 カーソルのオープン

カーソルの定義の後、検索したレコードにアクセスする前にカーソルをオープン しなくてはなりません。 ダイナミックカーソルをオープンするためのコマンドは、 クエリステートメント中のパラメータを変数に保持された値に 置き換えるための`USING`句を付加した スタテック SQL の同等のコマンドと非常によく似ています。 `USING`句は クエリステートメントはパラメータを持っていないのであれば 必要ではありません。

パラメータを含まないクエリのカーソルをオープンする例を以下に示します。

       EXEC SQL DECLARE c3 CURSOR FOR
            "SELECT * FROM books WHERE price > 40";
       EXEC SQL OPEN c3;

この場合、`OPEN`コマンドは、スタテック SQL の `USING`句がないものと同一です。

パラメータを含むクエリのカーソルをオープンする例を以下に示します。

       val2 = 130;
       EXEC SQL PREPARE q2 FROM "SELECT title FROM books WHERE publisher = ?";
       EXEC SQL DECLARE c2 CURSOR FOR q2;
       EXEC SQL OPEN c2 USING :val2;

上記のステートメントはこのクエリを実行します。

       SELECT title FROM books WHERE publisher = 130

`OPEN`コマンドは、`EXECUTE`コマンドと同等になります。 最初はクエリのために使用され、2番目はクエリではない SQL ステートメントの ために使用されます。 `USING`句の規則は、同じで、 変数の数はプリペア済みのSQL ステートメント中のパラメータの数と一致しなければ なりません。また制御変数を使用することができます。 変数の代わりに、SQL ディスクリプタを使用することもできます。 (SQL ディスクリプタについてはこのマニュアルの"SQL ディスクリプタ" セクションを参照してください。)

### 6.4.5 レコードのフェッチ

レコードは、`FETCH`コマンドにより取得されます。 次のレコードがカレントとなり、アトリビュート値を変数に読み込みます。

1 レコードの取得

       strcpy (val, "Database Design");
       EXEC SQL PREPARE q5 FROM
            "SELECT publisher, title, price FROM books WHERE title = ?";
       EXEC SQL DECLARE c5 CURSOR FOR q5;
       EXEC SQL OPEN c5 USING  :val;
       EXEC SQL FETCH c5 INTO :pub, :titl, :prc;

上記のステートメントは、`title`が`Database Design`で ある最初のレコードを取得し、 アトリビュート`publisher`の値は変数`pub`へ、 アトリビュート`title` の値は変数`titl`へ、 アトリビュート`price` の値は変数`prc`へ格納します。 `INTO`句中の変数の順番は、 `SELECT`ステートメントのアトリビュートと関連しているため 重要であることに留意してください。

一般的に、クエリによって検索されたレコード数は前もって知らされません。 全てのレコードを取得するためには、これ以上レコードがない(`SQLCODE = 100`) まで、`FETCH`コマンドを繰り返し呼び出す必要があります。 他のプロセスによってレコードがロックされ、 そのレコードにアクセスできない場合は、 `SQLCODE` に１がセットされます。 この場合に、後に同じレコードを取得するには、 `FETCH`コマンドの代わりに`FETCH_AGAIN`コマンドを 使用し、再フェッチすることができます。

       EXEC SQL OPEN c5 USING :atr, :val;
       EXEC SQL FETCH c5 INTO :pub, :titl, :prc;
       while (SQLCODE != 100)
       {
          switch (SQLCODE)
          {
             case 0:  printf ("%d\t%s\t%s\n", pub, titl, prc);
                      EXEC SQL FETCH c5 INTO :pub, :titl, :prc;
                      break;
             case 1:  EXEC SQL FETCH_AGAIN c5 INTO :pub, :titl, :prc;
                      break;
             default: printf ("Error Code %d\n", SQLCODE);
          }
       }

上記のプログラムの一部は全ての検索されたレコードを取得します。 レコードがロックされていた場合は、 同じレコードの取得をし続けます。

`FETCH` と`FETCH_AGAIN`コマンドの構文は、 変数のリストの変わりにSQL ディスクプタを指定するための `USING`句によって置き換えることができる `INTO`句を除き、 スタティック SQL のものと同様である

スタテック SQL では、最後にオープンされたカーソルだけがアクセスする ことができます。ダイナミックSQL はこのような制限はありません。 `FETCH`ステートメントは、オープンされた任意のカーソルを参照することが できます。

### 6.4.6 カーソルのクローズ

カーソルを使用してレコードを取得した後、カーソルはクローズしなくてはなりません。 `CLOSE CURSOR`コマンドはスタティック SQL の`CLOSE CURSOR`と同一です。

以下はカーソル名`c1`をクローズする例です。

       EXEC SQL CLOSE c1;

カーソルをクローズした場合、その定義は破棄されません。 そのカーソルを、前に説明した`OPEN`コマンドで 再びオープンすることができます。 カーソルが再オープンされるごとに クエリは`USING`句中の異なった変数を使用されるか、 あるいは同じ変数で異なった値が割り付けられ 変更された可能性があります。

### 6.4.7 ポインタ変数でのフェッチ

アトリビュート値のフェッチをするためにポインタ変数を使用した場合、 ユーザー自身でスペースをアロケートするか（デフォルト)、 あるいはEmpressにスペースをアロケートさせるかを選択する ことができます。 `AUTOMATIC`コマンドは、どちらかのオプションを 選択するために使用されます。

ユーザー自身でスペースをアロケートする場合は、以下のように指定します。

       EXEC SQL AUTOMATIC MEMORY OFF;

この場合、Empressは、ポインタ変数に あらかじめスペースがアローケートされていることを仮定します。 ポインタにメモリがアロケートされていない場合は、エラーが発生します。

Empressによって自動的にスペースをアロケートするには、 以下のように指定します。

       EXEC SQL AUTOMATIC MEMORY ON;

Empressは、ポインタ変数に自動的にメモリをアロケートし、 そのメモリスペースにアトリビュートの取得したデータをコピーします。 このメモリスペースが必要がなくなった場合や AUTOMATIC オプションをON にして 他の値をフェッチするために同じポインタ変数を使用する場合は、 スペースを解放する必要があります。 アロケートされたスペースの解放は Empressの`mpdfree()`関数を使用します。 ユーザーが、C の`malloc()`ルーチンによってポインタに メモリをアロケートした場合は、メモリの解放は C の`free()`ルーチンを使わなければなりません。 Empressの`mpdfree()`ルーチンは C の`free()`ルーチンとは異なった働きをするためです。

Empressは、ユーザーがポインタにメモリをアロケートしたかどうかを 知る方法を持っていません。従って、 AUTOMATIC オプションをON にした場合は Empressによって自動的にアロケートされたか、あるいはユーザー自身が アロケートしたかに関わらず、ポインタにアロケートしたどのようなメモリ領域も 必ず解放する必要があります。 AUTOMATIC オプションをOFF にした場合は、 データをフェッチするために使用するポインタの アロケートしたエリアが、データに対して十分であるかを 確かめる必要があります。

AUTOMATIC オプションは、`TEXT` や `BULK`データタイプの ような可変長データを取得の際に、あらかじめデータのサイズを知らない 場合において有用です。

### 6.4.8 NULL 値のフェッチ

制御変数は、フェッチした値が`NULL`であるかどうかを チェックするために使用されます。

       EXEC SQL FETCH c1 INTO :val :cntl;

フェッチした値が`NULL`である場合、 変数`cntl` は負の値がセットされ、 変数`val`は変更されません。 値が`NULL`ではない場合は、 変数`cntl` は0がセットされ、 変数`val`には、その値が設定されます。

制御変数を使用しない場合、

       EXEC SQL FETCH c1 INTO :val;

フェッチした値が `NULL`である場合にそれを知ることができないため、 この場合において変数`val`は変更されずそのままの状態で残ることになります。

### 6.4.9 カレントレコードの修正

レコードのフェッチの後、その内容を変更するために `WHERE CURRENT OF`句を伴って`UPDATE`コマンドを使用することが できます。 このコマンドは SQL `UPDATE`コマンドと同等です。 唯一の違いは、カレントレコードはダイナミックカーソルから になります。 例えば、以下のステートメントは、 各フェッチしたレコードの`price` アトリビュートを変更します。

       EXEC SQL DECLARE c1 CURSOR FOR "SELECT * FROM books" FOR UPDATE;
       EXEC SQL OPEN c1;
       EXEC SQL FETCH c1 INTO :pub, :title, :price;
       while (SQLCODE != 100)
       {
          price = price*1.2;
          EXEC SQL UPDATE books SET price = :price WHERE CURRENT OF c1;
          EXEC SQL FETCH c1 INTO :pub, :title, :price;
       }

同様に、`WHERE CURRENT OF`句を伴って、 `DELETE` コマンドを使用してカレントレコードを削除する ことができます。 以下の例は、テーブルからレコードを取得し、その後に `publisher`アトリビュートがある値と等しい場合は 削除します。

       EXEC SQL FETCH c1 INTO :pub, :title, :price;
       while (SQLCODE != 100)
       {
          if (pub = not_valid)
          EXEC SQL DELETE books WHERE CURRENT OF c1;
          EXEC SQL FETCH c1 INTO :pub, :title, :price;
       }

カレントレコードの削除および更新は、 カーソルは`FOR UPDATE`で宣言されるか あるいは、明示的に`UPDATE`か`DEFERRED`モードで オープンされたテーブルでのみ実行することができます。

複数のテーブルを参照しているカーソルや 集計関数を含むカーソルは更新および削除操作は実行することはできません。

### 6.4.10 サンプルプログラム

       /* バルクアトリビュートを取得するためのダイナミックカーソルを使用します。

           Table name:  images
           Attributes:  code         integer
                        description  char(128,1)
                        bitmap       bulk
       */

       #include <mscc.h>

       EXEC SQL INCLUDE SQLCA;

       EXEC SQL BEGIN DECLARE SECTION;
            char  sqls[512];                    /* SQL ステートメント              */
            int   num;                          /* アトリビュート　code の値        */
            char  string[129];                  /* アトリビュート　description の値 */

            typedef struct
            {
                 long  size;
                 char  data[1];                 /* データ配列のサイズのダミー */
            } bulk;

            bulk  *blkptr;                      /* アトリビュート bitmap の値 */
            short cntl;                         /* 制御変数　　               */

       EXEC SQL END DECLARE SECTION;

       main ()
       {
          EXEC SQL INIT;
          EXEC SQL DATABASE IS "db";

          /* ポインタを伴ったフェッチのときにEmpressが
            メモリをアロケートします。      */

          EXEC SQL AUTOMATIC ON;

          /* テーブルから3つのアトリビュートを選択するクエリの準備   */
          strcpy (sqls, "select code, description, bitmap from images");
          EXEC SQL PREPARE s1 FROM :sqls;
          if (SQLCODE) error("Prepare");

          /* プリペア済みのクエリのためのカーソル定義                       */

          EXEC SQL DECLARE c1 CURSOR FOR s1;
          if (SQLCODE) error("Declare");

          /* カーソルのオープン */

          EXEC SQL OPEN c1;
          if (SQLCODE) error("Open");

          /* 各レコードの取得
             INTEGER アトリビュートは、C の int に読み込まれます。
             CHAR アトリビュートは、C の char 配列 に読み込まれます。
             BULK アトリビュートは、Empress によって自動的にアロケートされた
             スペースに取り込まれます。
             制御変数は、BULK アトリビュートが NULL であるかをチェックするために
             使用されます。                   */

          do
          {
             EXEC SQL FETCH c1 INTO :num, :string, :blkptr :cntl;
             switch (SQLCODE)
             {
             case 0:   if (cntl>=0)
                       {
                          printf ("Code: %d \tSize: %d \tDescription: %s\n",
                                   num, blkptr->size, string);

                       /* 画像を表示する関数を想定  */

                          display_image (blkptr->data);
                       }
                       else
                          printf ("Code: %d \tSize: 0 \tDescription: %s\n",
                                   num, string);
                       break;

             case 1:   printf ("Record Locked\n");
                       break;

             case 100: break;                        /* これ以上レコードはない   */

             default:  error("Fetch");
             }
             mpdfree(blkptr);          /* スペースの解放 */

          } while (SQLCODE == 0);

          /* 使用したカーソルのクローズと終了                             */

          EXEC SQL CLOSE c1;
          EXEC SQL EXIT;
       }

       /* 終了する前に、エラーコードとメッセージを表示する関数        */

       error (cmd)
       char *cmd;
       {
          printf ("%s Error,  SQLCODE: %d\n", cmd, SQLCODE);
          printf ("SQLERRMC: %s\n", SQLERRMC);
          EXEC SQL EXIT;
          exit (1);
       }

## 6.5 SQL ディスクリプタ

### 6.5.1 はじめに

SQL ディスクリプタは ダイナミックSQL ステートメント中のパラメーターあるいは ダイナミッククエリによって検索されたアイテム を describe するために使用することが可能な動的にアロケートされた データ構造体です。 ディスクリプタの内部構造はアプリケーションからは隠されています。 この章で記述されている特別なコマンドを使用することにより 唯一、ディスクリプタのデータにアクセスすることができます。

グローバル領域と比較して、ディスクリプタは パラメータまたは選択アイテムについて情報を保持するための 複数のアイテム領域を含んでいます。

表 5-1: グローバル領域

フィールド名 | データタイプ | 説明
`COUNT` | integer | パラメータ数または選択アイテム数

表 5-2: アイテム領域

フィールド名 | データタイプ | 説明
`DA_TYPE` | integer | 各アイテムのデータタイプのコード
`DA_NLEN` | integer | アイテム名の長さ
`DA_NAME` | string | アイテム名
* `DA_DLEN` | integer | データ値の長さ
`DA_DATA` |  | アイテムの値
`DA_CNTRL` | C short | 制御変数と同等

注意 `DA_DLEN`は、現在のEmpressバージョンではサポートしていないことに 注意してください。

### 6.5.2 SQL ディスクリプタの使用

`USING` 句または `INTO`句で変数が使用されている場合、 挿入、更新または検索のアトリビュート数およびアトリビュートのデータタイプ をあらかじめ知っている 必要があります。それにより、必要な変数の数分、値を入れ、そして 変数に対し適切なデータタイプを選択することができます。

SQL ステートメントが動的に作成された場合、 コンパイル時にいくつのアトリビュートに挿入されたか、あるいは いくつのアイテムがクエリによって検索されたかを 知ることはできません。 このような場合、パラメータか検索されたアトリビュートの describe のために ディスクリプタを使用することができます。

ディスクリプタは、入力あるいは出力のためのいずれか一方が使用されます。 入力ディスクリプタは、プリペア済みのSQLステートメント中のパラメータを describe するために使用されます。 出力ディスクリプタは、クエリで検索されたレコードのアトリビュート値を フェッチするために使用されます。

ディスクリプタを使用する前には、、 プログラムの最初のグローバルセクション中に以下のステートメントを 記述する必要があります。

       EXEC SQL INCLUDE SQLDA;

### 6.5.3 ディスクリプタの作成

アプリケーションは必要な分のディスクリプタを持つことができ、また、 必要なときにディスクリプタを作成ができ、不要になった場合は破棄すること ができます。

ディスクリプタを作成するためには、 `ALLOCATE DESCRIPTOR`コマンドを使用して スペースをアロケートする必要があります。

       EXEC SQL ALLOCATE DESCRIPTOR desc1;

このステートメントは、ディスクリプタ名`desc1`を作成します。 このディスクリプタ名はプリコンパイラ識別子で、プログラムでの変数ではありません。

デフォルトでは、ディスクリプタの最大アイテム領域は10で、 各アイテムの名前の最大長は 32文字です。 これらのデフォルト値は、オプションの`WITH MAX`句を 使用して変更することができます。 例えば、最大のアイテム領域数を 20 を含む ディスクリプタ`in_desc`をアロケートする場合、 以下のように指定します。 (アイテム名はデフォルトの 最大長 32 文字です。)

       EXEC SQL ALLOCATE DESCRIPTOR in_desc WITH MAX (20);

アイテム名の最大長を指定するには以下のようになります。

       EXEC SQL ALLOCATE DESCRIPTOR out_desc WITH MAX (20,50);

このステートメントは、最大のアイテム領域数として 20 各アイテムの名前の最大長は 50 文字を持つ ディスクリプタ`out_desc`をアロケートします。

ディスクリプタが、必要でなくなった場合は、 ディスクリプタを破棄するために`DEALLOCATE DESCRIPTOR`コマンドを 使用してください。

       EXEC SQL DEALLOCATE DESCRIPTOR desc1;

### 6.5.4 ディスクリプタを使用した実行

以前に言及しましたが、パラメータを含むSQLステートメントは、 問い合わせではない場合でもプリペアし、実行することができます。 また、 `EXECUTE`ステートメントの `USING`句中での変数の使用についても既に説明しましたが、 プリペア済みのステートメント中のパラメータ数が、 実行時に唯一、決められる場合、 `USING`句内に指定するための変数がどのくらいの数あるか 知りません。 この問題を克服するために、 プリペア済みステートメント中のパラメータへ値を割り当てるために入力ディスクリプタ を使用することができます。

例えば、以下のように`INSERT`ステートメントが、動的に作成されます。

       strcpy (sqls, "INSERT INTO weather ");
       strcat (sqls, "(w_date, location, celsius) ");
       strcat (sqls, "VALUES ( ?, ?, ? )");
       EXEC SQL PREPARE ins1 FROM :sqls;

プリペア済みステートメントのパラメータへ値を割り当てるために入力ディスクリプタ を初期化するには、以下のようにします。

       EXEC SQL DESCRIBE INPUT ins1 INTO SQL DESCRIPTOR in_desc;

ステートメント`ins1`は、3つのパラメータを持っているため、 ディスクリプ`in_desc`の3つの領域は、上記のステートメントの結果として 有効になります

`GET DESCRIPTOR`コマンドを使用して、 `COUNT`フィールドを読むことで、 ディスクリプタ中の有効な領域の数を調べることができます。

       EXEC SQL GET DESCRIPTOR in_desc :var = COUNT;

上記のステートメントは 、 ディスクリプタ`in_desc`中の有効な領域の数を integer 変数`var`に割り当てます。 また、入力ディスクリプタの他のフィールドを読み込もうとすると無視されます。

`SET DESCRIPTOR`コマンドを使用して、 ディスクリプタ領域のフィールドへ値を割り付けることができます。 唯一、値を割り付けることができる2つのフィールドは `DA_DATA`と`DA_CNTRL`フィールドになります。 他のフィールドへ割り付けると無視されます。 例えば、 日付として外部フォーマット(文字列で表現する) を使用する最初のディスクリプタ領域に 日付に`June 30, 1993` を割り当てる場合、 以下になります。

       EXEC SQL SET DESCRIPTOR in_desc VALUE 1
       DA_DATA = "June 30, 1993";

上記のステートメントにおいては、データフィールドは 文字列リテラル値を値としてアサインすることができ、 変数もまた使用することができます。 例えば、2番目のデータフィールドに文字列をアサインするためには 以下のように行います。

       area = 2;
       strcpy (str, "Toronto");
       ctrl = 0;
       EXEC SQL SET DESCRIPTOR in_desc VALUE :area
       DA_DATA = :str, DA_CNTRL = :ctrl;

このサンプルでは、`DA_CNTRL`フィールドもまた設定していることに 留意してください。 ディスクリプタ領域の`DA_CNTRL`フィールドは、 `NULL`値のハンドリングをするため、 制御変数のように使用することができます。例えば以下のように使用します。

       area = 3;
       set_null = -1;
       EXEC SQL SET DESCRIPTOR in_desc VALUE :area
       DA_CNTRL = :setnull, DA_DATA = 13.7;

ディスクリプタ領域へデータを割り付けた後、 プリペア済みのステートメントはディスクリプタを使用して 実行することができます。 プリペア済みのステートメント中のパラメータは、 ディスクリプタ領域のデータによって置き換えられます。

       EXEC SQL EXECUTE ins1 USING SQL DESCRIPTOR in_desc;

上記のステートメントの手順は、 日付型アトリビュート`w_date`に`June 30, 1993`をセットし、 アトリビュート`location`に`Toronto`をセットし、 アトリビュート`celsius`に`NULL`をセットし、 （`DA_CNTRL` フィールドに対応するディスクリプタ領域は負になります。) テーブル`weather`に新しいレコードを挿入します。

### 6.5.5 サンプルプログラム 1

以下のプログラムはテーブル中のレコードを挿入することをユーザーに許可します。 テーブル名、アトリビュート数およびアトリビュート名は、実行するまで知らされて いません。 このプログラムは、ユーザーからこれらの情報を取得し、 ディスクリプタを 使用して SQL ステートメントを作成します。

       #include <mscc.h>

       EXEC SQL INCLUDE SQLCA;
       EXEC SQL INCLUDE SQLDA;

       EXEC SQL BEGIN DECLARE SECTION;
            char    sqls[1024];        /* SQL ステートメントを格納する文字列 */
            char    tab[32];           /* テーブル名                         */
            char    att[32] [32];      /* アトリビュート名                   */
            char    val[32] [32];      /* アトリビュートへインサートする値   */
            int     area;              /* ディスクリプタアイテム領域         */
       EXEC SQL END DECLARE SECTION;

       main ()
       {
          int i, num;
          EXEC SQL INIT;                             /* 初期化 */
          EXEC SQL DATABASE IS "db";

          /* ディスクリプタは 25 アイテム領域まで作成                  */

          EXEC SQL ALLOCATE DESCRIPTOR d WITH MAX (25);

          /* ユーザーからテーブル名とアトリビュート数を取得します。         */

          printf ("Insert into table: ");
          scanf ("%s", tab);
          printf ("Number of attributes to insert: ");
          scanf ("%d", &num);

          /* プリペアするためにステートメントを作成します。   */

          strcpy (sqls, "insert into ? (?");
          for (i=2; i<=num; i++) strcat (sqls, ",?");
          strcat (sqls, ") values (?");
          for (i=2; i<=num; i++) strcat (sqls, ",?");
          strcat (sqls, ")");

          EXEC SQL PREPARE s1 FROM :sqls;     /* ステートメントの準備 */
          if (SQLCODE) error("Prepare");

          /* 入力のためのステートメントとディスクリプタを関連付けます。      */

          EXEC SQL DESCRIBE INPUT s1 INTO SQL DESCRIPTOR d;
          if (SQLCODE) error("Describe");

          /* ディスクリプタ領域 1へテーブル名を割り付けます                */

          EXEC SQL SET DESCRIPTOR d VALUE 1 DA_DATA = :tab;
          if (SQLCODE) error("Set table name");

          /* ディスクリプタ領域へ　アトリビュート名を割り付けます。           */

          printf ("Enter attributes names: ");
          for (i=0; i<num; i++)
          {
             scanf ("%s", att[i]);
             area = i+2;
             EXEC SQL SET DESCRIPTOR d VALUE :area DA_DATA = :att[i];
             if (SQLCODE) error("Set attribute names");
          }

          /* ディスクリプタ領域へアトリビュート値を割り付けます。
          ここでは文字配列が使用されていますが、他の C のデータタイプや
          リテラル値も使用することができます。                        */

          printf ("Enter values for the attributes: ");
          for (i=0; i<num; i++)
          {
             scanf ("%s", val[i]);
             area = i+num+2;
             EXEC SQL SET DESCRIPTOR d VALUE :area DA_DATA = :val[i];
             if (SQLCODE) error("Set attribute values");
          }

          /* テーブル名、アトリビュート名および挿入するための値を
            得るためにディスクリプタを使用した 挿入ステートメントを実行します。 */

          EXEC SQL EXECUTE s1 USING SQL DESCRIPTOR d;
          if (SQLCODE) error("Execute");

          /*  不要なディスクリプタを解放します。        */

          EXEC SQL DEALLOCATE DESCRIPTOR d;
          if (SQLCODE) error("Deallocate");

          EXEC SQL EXIT;
       }

       error (cmd)                         /* エラー処理関数 */
       char *cmd;
       {
          printf ("%s Error,  SQLCODE : %d\n", cmd, SQLCODE);
          printf ("SQLERRMC: %s\n", SQLERRMC);
          EXEC SQL EXIT;
          exit (1);
       }

### 6.5.6 ディスクリプタを使用したカーソルのオープン

ダイナミックカーソルをオープンする際での 入力ディスクリプタの使用は、 プリペア済みステートメントの実行することと同じです。 プリペア済みクエリおいて、 テーブル名を選択するためのアイテム または`WHERE`句は、 パラメータによって表すことができます。 入力ディスクリプタに保持したデータは、 プリペア済みのクエリ中のパラメータを置き換えるために使用されます。 例えば、以下の`SELECT`ステートメントは動的に作成されます。

       strcpy (sqls, "SELECT ");
       strcat (sqls, "?, ?, ? ");
       strcat (sqls, "FROM weather ");
       strcat (sqls, "WHERE ? > ?");
       EXEC SQL PREPARE qry1 FROM :sqls;

プリペア済みのパラメータへ値を割り当てるために 入力ディスクリプタを初期化するためには以下のように記述します。

       EXEC SQL DESCRIBE INPUT qry1 INTO SQL DESCRIPTOR in_desc;

プリペア済みのクエリ`qry1` は、5つのパラメータを持っているため、 ディスクリプタ`in_desc`の 5 つの領域は 上記のステートメントの結果として有効になります。

`SET DESCRIPTOR`コマンドを使用してディスクリプタの フィールドに値を割り付けます。 以下の例は、最初に3つのディスクリプタ領域は、 クエリによって選択されたアイテムと関連付けられます。

       EXEC SQL SET DESCRIPTOR in_desc VALUE 1 DA_DATA="w_date";
       EXEC SQL SET DESCRIPTOR in_desc VALUE 2 DA_DATA="location";
       EXEC SQL SET DESCRIPTOR in_desc VALUE 3 DA_DATA="celsius*1.8+32 PRINT fahr";

最後の2つのディスクリプタ領域は検索条件と関連付けられます。

       EXEC SQL SET DESCRIPTOR in_desc VALUE 4 DA_DATA="celsius";
       EXEC SQL SET DESCRIPTOR in_desc VALUE 5 DA_DATA=25;

カーソルは、`DECLARE CURSOR`コマンドにより、 プリペア済みのクエリに対し宣言されます。

       EXEC SQL DECLARE c1 CURSOR FOR qry1;

カーソルは ディスクリプタ領域からプリペア済みのクエリ中のパラメータに 値をアサインするために ディスクリプタを使用してオープンされます。

       EXEC SQL OPEN c1 USING SQL DESCRIPTOR in_desc;

上記のステートメントによって作成されたクエリは、 次の`SELECT`ステートメントと同等です。

       SELECT w_date, location, celsius*1.8+32 PRINT fahr
          FROM weather WHERE celsius > 25

最後の選択アイテムは、`PRINT`句を伴なった式であることに留意してください。 対話型 SQL では、列のヘッダーとして使用する名前を指定します。 クエリでディスクプタを使用した場合、 名前は、`DA_NAME`フィールドに返されます。

### 6.5.7 ディスクリプタを使用したフェッチ

カーソルをオープンした後、検索したレコードからデータをフェッチし、 変数またはディスクリプタ領域にコピーすることが可能です。 出力ディスクリプタが使用された場合は、 最初にそのクエリの出力のために 以下のように describe する必要があります。 (単にカーソルをオープンした後に)

       EXEC SQL DESCRIBE OUTPUT qry1 INTO SQL DESCRIPTOR out_desc;

プリペア済みのクエリ`qry1`は、その選択リスト中に3つのアイテムを持つため、 ディスクリプタ`out_desc`の3つの領域は、上記のステートメントの結果として 有効になります。

`GET DESCRIPTOR`コマンドを使用して `COUNT`フィールドを読むことで ディスクリプタ中の有効な領域の数を 以下のように調べることができます。

       EXEC SQL GET DESCRIPTOR out_desc :cnt = COUNT;

クエリが選択アイテムリストを持っていない場合(例えば`SELECT * FROM tab1`)は、 テーブルの全てのアトリビュートは検索されるため、 `COUNT`には、テーブルのアトリビュート数が戻されます。

カーソルからレコードをフェッチし、ディスクリプタへ選択したデータを格納するには 以下のようになります。

       EXEC SQL FETCH c1 INTO SQL DESCRIPTOR out_desc;

変数へ直接、選択データをコピーする代わりに、 選択データは有効なディスクリプタ領域へ割り当てられます。

`GET DESCRIPTOR` コマンドを使用することでそのデータにアクセスする ことができます。 You can access the data using the `GET DESCRIPTOR` command, specifying which item area you want to read:

       for (area=1; area<=cnt; area++)
       {
          EXEC SQL GET DESCRIPTOR out_desc VALUE :area
             :dt=DA_TYPE, :nm=DA_NAME, :val=DA_DATA, :nul=DA_CNTRL;
          ...
       }

上記のループは、3つのディスクリプタ領域を読み込みます。 選択アイテムのデータタイプコード、 選択アイテムの名前、 カレントレコードの値、 および値が`NULL`である場合、負を返すインジケータ。 値が`NULL`である場合、フィールド`DA_CNTRL`は 負の値を戻しますが、この場合、変数`val`は `DA_DATA`フィールドにアクセスした後でさえも 変更されず残っていることに注意してください。

### 6.5.8 サンプルプログラム 2

以下のプログラムはユーザーがプロンプトにクエリを入力し、それを実行します。 選択アイテム数およびそれらのデータタイプは、クエリが入力されるまでわかりません。 このサンプルプログラムでは、 カーソルをオープンしたときにパラメータの置き換えを実行しないため、 ユーザーによって入力されるクエリは`?`パラメータを含むべきではないことに 注意してください。

       #include <mscc.h>

       EXEC SQL INCLUDE SQLCA;
       EXEC SQL INCLUDE SQLDA;

       EXEC SQL BEGIN DECLARE SECTION;
          char   sql_str[1024];        /* SQL ステートメントを格納する文字列 */
          char   dname[33][25];        /* アイテム名を格納            　　　 */
          int    dtype[25];            /* アイテムのデータタイプを格納       */
          int    da_num;               /* 有効なディスクリプタ領域数         */
          int    area;                 /* ディスクリプタアイテム領域         */
          char   *pstr;                /* 文字データ取得のため               */
          long   vlong;                /* integer データ取得のため           */
          double vdouble;              /* float データ取得のため             */
          int    i;
          typedef struct
          {
             long size;
             char data[1];
          } bulk;
          bulk   *pbulk;               /* bulk データ取得のため              */
          short  ctrl;                 /* NULL チェックのための制御変数　    */

          typedef struct
          {
            long    gen_year;       /* 実際の年. 例えば 1999, 2000, etc */
            long    gen_month;      /* 1 から 12. 1 = January, ... */
            long    gen_day;        /* 1 から 31 */
            long    gen_hour;       /* 0 から 23 */
            long    gen_minute;     /* 0 から 59 */
            long    gen_second;     /* 0 から 59 */
            long    gen_microsec;   /* 0 から 999999 */
          } gen_date;
          gen_date  *pdate;

       EXEC SQL END DECLARE SECTION;

       main ()
       {
          int j;
          EXEC SQL INIT;                                  /* 初期化 */
          EXEC SQL DATABASE IS "e;db"e;;

          /* ポインタ変数のための自動的にメモリーをアロケートする指定      */

          EXEC SQL AUTOMATIC MEMORY ON;

          /* ディスクリプタ領域を 25 まで作成                       */

          EXEC SQL ALLOCATE DESCRIPTOR desc_out WITH MAX (25);

          printf ("e;Query: "e;);           /* ユーザーからSQLステートメントを取得  */
          fgets (sql_str, 1024, stdin);

          /* ステートメントの準備と宣言、カーソルのオープン               */

          EXEC SQL PREPARE s1 FROM :sql_str;
          if (SQLCODE)  error("e;Prepare"e;);
          EXEC SQL DECLARE curs CURSOR FOR s1;
          if (SQLCODE)  error("e;Declare"e;);
          EXEC SQL OPEN curs;
          if (SQLCODE)  error("e;Open cursor"e;);

          /* ステートメントと出力ディスクリプタを関連付けます。                   */

          EXEC SQL DESCRIBE OUTPUT s1 INTO SQL DESCRIPTOR desc_out;
          if (SQLCODE)  error("e;Describe output"e;);

          /* 選択アイテム数を調べます。                              */

          EXEC SQL GET DESCRIPTOR desc_out :da_num = COUNT;
          if (SQLCODE)  error("e;Get count"e;);
          printf ("e;Number of selected items: %d\n"e;,da_num);

          /* 各選択アイテム名とデータタイプを調べます。          */

          for (i=1; i<=da_num; i++)
             EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                     :dtype[i]=DA_TYPE, :dname[i]=DA_NAME;

          /*　データを取得するためにディスクリプタを使用し、
              最初のレコードを得ます。    */

          EXEC SQL FETCH curs INTO SQL DESCRIPTOR desc_out;
          while (SQLCODE != 100)
          {
             if (SQLCODE)  error("e;Fetch"e;);

             /* 各選択アイテムの情報を表示します。                 */

             for (i=1; i<=da_num; i++)
             {
                printf ("e;Name: %s \tType: %d \t"e;,dname[i], dtype[i]);
                switch (dtype[i])
                {
                  case DTGCHAR:          /* 文字列ポインタを使用したデータを取得 */
                  case DTGTEXT:
                    EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                         :pstr = DA_DATA, :ctrl = DA_CNTRL;
                    if (SQLCODE)  error("e;Get string"e;);
                    if (ctrl<0) printf ("e;Value: null\n"e;);
                    else
                    {
                       printf ("e;value: %s\n"e;,pstr);
                       mpdfree (pstr);
                    }
                    break;
                  case DTGINTEGER:         /* long integer を使用したデータを取得 */
                    EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                         :vlong = DA_DATA, :ctrl = DA_CNTRL;
                    if (SQLCODE)  error("e;Get integer"e;);
                    if (ctrl<0) printf ("e;Value: null\n"e;);
                    else printf ("e;Value: %d\n"e;,vlong);
                    break;
                  case DTGFLOAT:                 /* doubleを使用したデータを取得 */
                  case DTGDECIMAL:
                    EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                         :vdouble = DA_DATA, :ctrl = DA_CNTRL;
                    if (SQLCODE)  error("e;Get float"e;);
                    if (ctrl<0) printf ("e;Value: null\n"e;);
                    else printf ("e;Value: %f\n"e;,vdouble);
                    break;
                  case DTGBULK:            /* bulk ポインタを使用したデータを取得 */
                    EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                         :pbulk = DA_DATA, :ctrl = DA_CNTRL;
                    if (SQLCODE)  error("e;Get bulk"e;);
                    if (ctrl<0) printf ("e;Value: null\n"e;);
                    else
                    {
                       printf ("e;Value: "e;);        /* 最初の10 バイト表示*/
                       for (j=0; j<pbulk->size; j++)
                       {
                          if (j==10) { printf ("e;..."e;); break; }
                          else printf ("e;%x "e;,(unsigned char)pbulk->data[j]);
                        }
                        printf ("e;\n"e;);
                        mpdfree (pbulk);
                     }
                     break;
                  case DTGDATE:
                  case DTGTIME:
                    EXEC SQL GET DESCRIPTOR desc_out VALUE :i
                         :pdate = DA_DATA, :ctrl = DA_CNTRL;
                    if (SQLCODE)  error("e;Get date"e;);
                    if (ctrl<0) printf ("e;Value: null\n"e;);
                    else {
                            if (dtype[i] == DTGDATE)
                                    printf ("e;Value: %d.%d.%d\n"e;,
                                            pdate->gen_year, pdate->gen_month,
                                            pdate->gen_day);
                            else
                                    printf ("e;Value: %d.%d.%d %d:%d:%d\n"e;,
                                            pdate->gen_year, pdate->gen_month,
                                            pdate->gen_day, pdate->gen_hour,
                                            pdate->gen_minute, pdate->gen_second);
                            mpdfree (pdate);
                     }
                     break;
                  default:
                     printf ("e;Unknown data type\n"e;);
                }
             }

             /* 次のレコードを取得 */

             EXEC SQL FETCH curs INTO SQL DESCRIPTOR desc_out;

          }
          EXEC SQL CLOSE curs;                                  /* クリーンアップ */
          EXEC SQL DEALLOCATE DESCRIPTOR desc_out;
          EXEC SQL EXIT;
       }

       error (cmd)                            /* エラー処理関数 */
       char *cmd;
       {
          printf ("%s Error,  SQLCODE : %d\n", cmd, SQLCODE);
          printf ("SQLERRMC: %s\n", SQLERRMC);
          EXEC SQL EXIT;
          exit (1);
       }

## 6.6 エラー処理

各 SQL コマンドの実行後、グローバル変数`SQLCODE`に integer 値がセットされます。 通常は 0 が設定されていますが、エラーが発生した場合には 負の数が設定されます。 `SQLCODE`は、エラーのタイプを識別に使用することができます。 グローバル変数`SQLERRMC`は、適切なエラーメッセージを提供する ための文字列型のポインタです。

このコマンドの失敗は後に実行するコマンドの実行に影響することがあるため 各 SQL コマンド実行後は常に`SQLCODE`の値をチェックし、 エラーである場合はプログラムを終了すべきです。 例えば、SQL ディスクリプタのアロケートに失敗した場合、 このディスクリプタを使用するためのそれ以上の試みは 予測不能の結果を生じる可能性があります。 また、 プログラムを終了する前には `SQL EXIT`を実行することを忘れないようにしてください。

SQL ステートメントの実行に失敗した場合、 以下のチェックをしてください。

- アクセスしているデータベースは正しいデータベースですか？
- そのデータベース中にテーブルとアトリビュートは存在しますか？
- SQL ステートメントに構文エラーはないですか？

ダイナミック SQL では、実行時にステートメントが作成されるため、 SQL ステートメントに構文エラーがある場合、それを見つけることは 難しいかもしれません。 また、パラメータを含むプリペア済みのSQL ステートメントを実行する場合は 特に難しいかもしれません。 グローバル変数`SQLCMD` は、プロセスをデバッグする機能として 提供されます。 完全な SQL ステートメントを提供する文字列へポインタであり、 (パラメータがある場合、実際のデータに置き換えたステートメント) `EXECUTE`、`EXECUTE IMMEDIATE` `OPEN`コマンドを実行した後の 完全な SQL ステートメントを提供する文字列へポインタです。

以下は SQL ステートメントをプリペア、実行するプログラムの 一部を抜き出したものです。 実行後、エラーが発生したかどうかをチェックします。

       EXEC SQL PREPARE s1 FROM "insert patients (name, age) values (?, ?)"
       strcpy (str1, "Jack");
       v2 = 63;
       EXEC SQL EXECUTE s1 USING :str1, :v2;
       if (SQLCODE)
       {
          printf ("Error Code:    %d \n", SQLCODE);
          printf ("Error Message: %s \n", SQLERRMC);
          printf ("SQL Statement: %s \n", SQLCMD);
       }

上記の SQL ステートメントは失敗し、 以下のようなエラーを出力します。

       Error Code:    -65
       Error Message: Error detected by the SQL processor:
       *** User Error ***  Attribute specification not allowed
       SQL Statement: insert patients (name, age) values (Jack, 63)

`SQLCMD`によって返された文字列は、 SQL ステートメント内の文字列 `Jack`は 引用符で囲まれていないため、このステートメントを 実行した場合、構文エラーになります。

`SQLERRMC`と`SQLCMD`は テンポラリバッファを使用していることに注意してください。 (次にSQL コマンドの実行すると上書きされます。)

グローバル変数 `mroperr`は、より詳細なエラーコード情報を 取得するために使用することができます。 データベース操作で失敗した場合は常に Empressの`include/mrerrno.h`ヘッダーファイルに 定義されたエラーコードの１つがセットされます。

`mroperr`を使用した例として、プログラムの一部を以下に示します。

       EXEC SQL PREPARE s1 FROM "insert patients (name, age) values (?, ?)"
       strcpy (name1, "Mary");  age1 = 54;
       EXEC SQL EXECUTE s1 USING :name1, :age2;
       if (SQLCODE)
       {
          printf ("SQL Error Code:    %d \n", SQLCODE);
          printf ("SQL Error Message: %s \n", SQLERRMC);
          printf ("MR  Error Code:    %d \n", mroperr);
       }

テーブルのロックレベルがテーブルレベルである場合、 この挿入操作は失敗します。

       SQL Error Code:    -65
       SQL Error Message: *** Dynamic SQL error *** table 'patients' lock busy
       MR  Error Code:    6