【実務・中級編】DB2埋め込みSQLにおける動的SQLの構築とPREPARE/EXECUTE – PL/Iの基本構文とデータ制御実践ガイド

お疲れ様です。今日も元気にオンラインのログや夜間バッチのABEND(アベンド)解析に追われていることかと思います。

さて、今回はPL/IとDB2の組み合わせにおける「動的SQL(Dynamic SQL)」の極意、特にPREPAREとEXECUTE、そしてパラメータマーカー(?)のバインド手法について、現場の生々しいノウハウを交えて徹底解説しよう。

レガシーシステムの現場では、「検索条件が画面や外部パラメータによって無限に変わるため、静的SQLでは太刀打ちできない」という要件に必ず直面する。そんな時、PL/Iの文法特性を理解せずに見様見真似でコードを書くと、データ例外(S0C7やS0C4など)や、DB2のSQLCODE -301、-805といった悪名高いエラーの泥沼にハマることになる。

今日のこの記事を読めば、明日からのバッチ改修やマイグレーション調査で迷うことはなくなるはずだ。さっそく、現場の頭脳をフル回転させて紐解いていこう。

—

1. なぜPL/Iと動的SQLなのか?(基本思想と構文の罠)

PL/Iという言語の最大の特徴の一つに、「文脈依存の予約語を持たない(あるいは極めて少ない)」という仕様がある。例えば、他の言語では `SELECT` や `UPDATE` が厳格な予約語になっていたりするが、PL/Iでは変数名として使えてしまう(もちろん推奨はしないが)。

この柔軟性ゆえに、PL/I上でDB2の埋め込みSQL(Embedded SQL)を扱う際、コンパイラは `EXEC SQL` プレフィックスを頼りにプリコンパイラ(SQLモジュール)経由でコードを翻訳する。静的SQLであれば、プリコンパイル時にバインドファイル(DBRM)が生成され、アクセスパスが固定されるため安全かつ高速だ。

しかし、実行時までSQL文の形が決まらない「動的SQL」の場合、話は別だ。
プログラム内で文字型変数にSQL文を組み立て、それをDB2に「動的にコンパイル(PREPARE)」させてから実行(EXECUTE)するという、ひと手間もふた手間もかかる処理が必要になる。

—

2. 現場で頻出する動的SQLの設計パターン

動的SQLを扱う上で、以下の3ステップを確実に踏む必要がある。

1. SQL文の組み立て: 文字列型変数(CHARACTER VARYING)に、条件に応じた `WHERE` 句などを動的に連結する。
2. PREPARE(SQLの構文解析と実行計画生成): 組み立てた文字列をDB2に送り、ステートメント名(STATEMENT-NAME)に割り当てる。
3. EXECUTE USING(パラメータバインド): SQLインジェクションを防ぎ、アクセスパスを効率化するために、値は直接文字列に埋め込まずパラメータマーカー(?)を使い、ホスト変数から値をバインドする。

ここで、実務でそのまま使えるサンプルコードを見てみよう。大文字ベースで記述し、適切なインデントとBUILTIN関数(LENGTHなど)を活用した、信頼性の高いPL/Iプログラムだ。

—

3. 実践PL/Iソースコード:動的SQLによる検索処理

以下のコードは、VSAM代替またはDB2の可変条件検索を想定し、顧客マスタから動的に条件を変えてデータを取得するバッチプログラムの抜粋である。

DCL 1 WK-AREA,
5 SQL-STMT CHARACTER(1000) VARYING, / 動的SQL格納用バッファ /
5 COND-CUST-ID CHARACTER(5), / 検索条件:顧客ID /
5 COND-STATUS CHARACTER(1); / 検索条件:ステータス /

DCL 1 OUT-CUST-REC,
5 CUST-ID CHARACTER(5),
5 CUST-NAME CHARACTER(40),
5 CUST-STATUS CHARACTER(1),
5 UPD-DATE CHARACTER(10);

DCL 99 SQLCA,
5 SQLAID CHARACTER(8),
5 SQLABC FIXED BIN(31),
5 SQLCODE FIXED BIN(31),
5 SQLERRM CHARACTER(70) VARYING;

/ EXEC SQL INCLUDE SQLCA; と同等 /

/ — 処理開始 — /

/ 1. 動的SQL文のベースを組み立てる /
SQL-STMT = ‘SELECT CUST_ID, CUST_NAME, CUST_STATUS, UPD_DATE ‘
// ‘FROM CUST_MASTER WHERE 1 = 1’;

/ 2. 実行時条件に応じて動的にWHERE句を追加する /
IF COND-CUST-ID ^= ” THEN
SQL-STMT = SQL-STMT // ‘ AND CUST_ID = ?’;

IF COND-STATUS ^= ” THEN
SQL-STMT = SQL-STMT // ‘ AND CUST_STATUS = ?’;

/ 3. SQL文の準備(PREPARE) /
EXEC SQL PREPARE S1 FROM :SQL-STMT;

IF SQLCODE ^= 0 THEN
DO;
PUT SKIP LIST(‘PREPARE ERROR: SQLCODE = ‘ || SQLCODE);
SIGNAL FINISH;
END;

/ 4. カーソルの宣言とオープン(パラメータマーカーがある場合の動的処理) /
/ ※ここでは分かりやすく単発EXECUTEの例、または動的カーソルを使用 /
EXEC SQL DECLARE C1 CURSOR FOR S1;

/ パラメータのバインド(USING句を使用) /
/ 注意: 組み立てたSQLの「?」の出現順にホスト変数を指定する /
IF COND-CUST-ID ^= ” & COND-STATUS ^= ” THEN
EXEC SQL OPEN C1 USING :COND-CUST-ID, :COND-STATUS;
ELSE IF COND-CUST-ID ^= ” THEN
EXEC SQL OPEN C1 USING :COND-CUST-ID;
ELSE IF COND-STATUS ^= ” THEN
EXEC SQL OPEN C1 USING :COND-STATUS;
ELSE
EXEC SQL OPEN C1;

/ 5. フェッチループ /
DO FOREVER;
EXEC SQL FETCH C1 INTO :OUT-CUST-REC;

IF SQLCODE = 100 THEN LEAVE; / データ終了 /
IF SQLCODE < 0 THEN DO; PUT SKIP LIST('FETCH ERROR: SQLCODE = ' || SQLCODE); LEAVE; END; / 正常データの処理 / PUT SKIP LIST('CUST-ID: ' || OUT-CUST-REC.CUST-ID || ' NAME: ' || OUT-CUST-REC.CUST-NAME); END; / 6. カーソルクローズ / EXEC SQL CLOSE C1; ---

4. ベテランが教える!デバッグとコーディングの勘所

現場でこの動的SQLを実装・改修する際、数々の若手エンジニアがハマってきた「罠」と、その回避策を伝授しよう。

① パラメータマーカー(`?`)の数とUSING句の不一致

動的SQLで最も多いバグは、SQL文内の `?` の個数と、`EXECUTE` や `OPEN` の `USING` 句で指定したホスト変数の数が一致しないことだ。これがズレると、DB2側でSQLCODE -301(ホスト変数のデータ型またはサイズの不一致)や -313(ホスト変数の数か不正)が即座に返される。
条件分岐によってSQL文を組み立てる際は、「どの条件の時に何番目の `?` にどの変数が対応するか」を徹底的に管理しなければならない。上記のコード例のように、分岐ごとに `USING` 句を明確に分けるか、あるいはダミーの条件(`1=1` など)を駆使してロジックを破綻させない設計が求められる。

② VARYING文字列の長さに注意

PL/Iの `CHARACTER VARYING` 型は、先頭に2バイトの長領域(プレフィックス)を持つ。SQL文を結合していく過程で、宣言した最大長(上記の例では `CHARACTER(1000)`)を超過すると、S0C4などのストレージ違反や、SQL文の途切れによる構文エラー(SQLCODE -104など)を引き起こす。
動的SQLを生成するバッファは、想定される最大長よりも十分に余裕を持ったサイズで定義し、必要に応じて `LENGTH` ビルトイン関数でデバッグ時にサイズをログ出力する習慣をつけてほしい。

③ インデックススキャン vs フルテーブルスキャン(アクセスパスの呪縛)

静的SQLであれば、BIND時に統計情報をもとに最適なインデックスが選択されるが、動的SQL(PREPARE)の場合、PREPAREが実行された時点のホスト変数の値やバインド状況によっては、意図しないフルテーブルスキャン(表の全件走査)が発生することがある。
夜間バッチで大量データを処理する際、動的SQLが原因でパフォーマンスが急激に劣化する場合は、EXPLAIN表を採取してアクセスパスを必ず確認すること。

—

5. おわりに

PL/Iにおける動態的なデータ制御とDB2の連携は、レガシーシステムの寿命を延ばし、柔軟な業務要件に応えるための強力な武器だ。
しかし、「動的に書けるから何でもあり」というわけではない。可読性の低下やデバッグの難易度上昇を考慮すると、「本当に動的SQLが必要な要件か?」を立ち返って検証することも、我々システムアーキテクトの重要な仕事である。

基本に忠実なデータ定義、堅牢なエラーハンドリング、そしてPL/Iの特性を活かしたスマートな文字列操作。これらをマスターすれば、どんな巨大なメインフレーム案件でも恐れるに足りない。

さて、そろそろ次のジョブネットの確認時間だ。現場の健闘を祈る!

タイトルとURLをコピーしました