既存データから Insert スクリプトを生成する
最近、非常に興味深い難問を提示されました。現在、表に存在するデータの INSERT ステートメントを生成できますか?
例えば、表が以下のデータに含まれているとしましょう。
以下のような、対応する Insert ステートメントを生成できるでしょうか?
元々の要求は、指定された表のすべての行列に insert スクリプトを生成することでした。Insert スクリプトはインストール・プロセスの一部であり、そこで表にデータをプリロードしようとしていました。
しかし、わざわざプログラムを作成するなら、表のすべての行列に対してではなく、SQL ステートメントに基づいて insert スクリプトが生成されるよう、要件を拡張してはどうでしょうか?Select ステートメントを使用して、必要に応じて insert ステートメント中の行列の数を減らし、列の値を定数値に置き換えることができます。そのようなスクリプトは、ライブ・データからテスト・データを生成し、機密情報のコピーを防ぐ素晴らしい方法を提供してくれます。
どのように動作するのか
解決法としては、SQL 記述子を使用する RPG プログラム (DESC22) を呼び出す表関数 (CREATEINSERTSCRIPTC) を使用します。表関数が少し手ごわいと感じる人に、嬉しい知らせがあります。提供されたプログラムをコンパイルして、表関数を作成するだけで準備完了です。
これは、表関数を呼び出すことによって insert ステートメントを生成する方法です。結果セットは、選択したソース・メンバーにコピー・アンド・ペーストすることができます。
注意すべき重要なポイントは以下のとおりです。
- すべての列が選択されるわけではありません。
- すべての行が選択されるわけではありません。
- WHERE 節の定数値は (1 つではなく) 単一引用符 2 つで囲みます。こうして、リテラルに単一引用符を含めます。
- パラメーターとして渡された SQL ステートメントは、複数の表を結合、グループ化、順序付けするなどして、必要に応じて簡単にも複雑にもできます。
基本的に、以下の例は前述の例と同じです。ただし、全員の姓を定数値に置き換えています。
表関数の作成
以下のステートメントを使用して、表関数を作成します。
関数には、必要な SQL ステートメントである単一のパラメーター (FORSTATEMENT) が渡され、insert スクリプトの行が入った単一列 (LINEBACK) の表を返します。これら両方の変数は、SQL ステートメントのサイズおよび並んだ値のサイズに十分対応できるサイズでなければなりません。変数のサイズは関数と RPG プログラムの両方で定義されていることを覚えておいてください。
RPG プログラム
表関数が SQL ステートメントで使用されている場合、基本外部プログラムが何度も呼び出されます。初期設定呼び出しを含め、すべての行が処理されるまで、「行」が返されるごとに 1 回呼び出されます。呼び出しの種類 (初期設定、フェッチなど) は Call Type パラメーターで示されます。
これらはグローバル定義であり、RPG プログラム DESC22 の主部です。詳細については、ソースのコールアウトを参照してください。
A: パラメーターの定義は、関数のパラメーターの定義に対応している必要があります。
B: プログラムは SQLState を 02000 に設定し、すべての行が処理されたことを呼び出し元に指示します。
C: Call Type パラメーターは、呼び出しが初期設定かフェッチかを示します。
D: gv_types は、select ステートメントの各列のデータ型を保存する場合に使用する配列です。データ型は、値を生成された insert ステートメントで、引用符で囲むかどうか判断する場合に使用します。
E: 常に、意味のない値に意味を持つ名前を付けるのが良いでしょう。
F: プログラムを呼び出す場合は必ず、Call Type パラメーターの値に基づいて該当のサブプロシージャーが呼び出されます。
doCallOpen() サブプロシージャーは、プログラムに初期設定呼び出しが行われると呼び出されます。doCallOpen() は以下を行います。
- 要求された SQL ステートメントの妥当性を検査し、準備します。
- 記述子を使用して、必要なすべての列の記述を取得します。
- すべての列のデータ型の配列を構築します。
- 記述子を使用して、すべての値が VARCHAR として (瞬時に) 返されるよう指定します。
- 返された最初の行を構築します。これは INSERT 行で、挿入中の列の名前が入っています。この行はこの呼び出しでは返されませんが、doCallNext() の最初の呼び出しで返されます。
これは doCallOpen() サブプロシージャーです。詳細については、ソースのコールアウトを参照してください。
A: 要求された SQL ステートメントの処理に使用する記述子を割り振ります。
B: 渡されたパラメーターを作業フィールドにコピーします。渡されたパラメーターは CONST キーワードで定義されます。つまり、組み込み SQL ステートメントでは使用できません。
C: 要求されたステートメントを準備します。ステートメントが OK の場合のみ処理が続行します。
D: 準備したステートメントを記述子で記述し、select ステートメントの列の数を取得します。
E: 返された最初の行の開始を構築します。
F: 列定義それぞれをループし、各列のデータ型と名前を取得します。
G: 列名をリストに追加します。
H: 列のデータ型を保存します。
I: 記述子を使用して、列の値を VARCHAR としてキャストするよう指定します。SQL 記述子を使用して列の値を取得する場合、ホスト変数の定義はデータベース列の定義と完全一致しなければならないというように、非常に規則が厳しくなっています。VARCHAR にキャストすることで、単一のホスト変数を使用して任意の列の値を取得できます。
J: 準備したステートメントのカーソルを宣言し、カーソルを開きます。
K: 要求されたステートメントに問題があった場合は、呼び出し元にエラーを返します。
doCallNext() サブプロシージャーは、返される行ごとに 1 回呼び出されます。注意すべき重要なポイントは以下のとおりです。
- 最初にサブプロシージャーを呼び出すと、(doCallOpen() で準備された) 最初の行が返されます。
- すべての行が処理されると、最後の行にセミコロンを付けて返す必要があります。
- 列の値を返された行に追加する場合、引用符で囲む必要がある値および NULL 値を考慮する必要があります。
これは doCallNext() サブプロシージャーです。詳細については、ソースのコールアウトを参照してください。
A: これが最初の呼び出しの場合、doCallOpen() で構築された行を返します。
B: すべての行が返されたら、End of File 条件を設定し、終了します。
C: 返された行の構築を開始します。
D: 準備した SQL ステートメントから次の行をフェッチします。
E: すべての行が処理されたら、最後の ':' を返し、カーソルを閉じて、記述子を再度割り当てます。
F: 行を処理する場合、要求されたそれぞれの列をループします。
G: 列ごとに、列からデータおよび NULL 標識を取り出します。
H: 以前保存されていたデータ型を使用して、値を引用符で囲むかどうか判断します。
I: 列が NULL かどうか判断します。
J: 値を返す行に追加します。
最後に、クライアントがプログラムにクローン呼び出しをすると、doCallClose() サブプロシージャーが呼び出されます。doCallClose() サブプロシージャーはカーソルを閉じ、記述子を割り振り解除して、プログラムを終了します。
その他の考え
最初は、ストアード・プロシージャーを使用してこれを実行しようと思いました。ストアード・プロシージャーの問題は、呼び出し元に返す前に、プログラムですべての結果セットを生成する必要があるということです。表関数で行を一度に処理することで、簡単に作成できました。
(結果セットの) コピー・アンド・ペーストが大変な場合、結果セットをソース・メンバーまたは IFS ファイルに配置する「ラッパー」プログラムを作成できます。
これも、読者のユーティリティーに追加できるツールになることと思います。是非楽しんでください。