現場で泣かないためのPL/I:DB2インジケータ変数とNULL値ハンドリングの極意
メインフレームの現場で、古びたPL/Iソースと向き合う若手エンジニアからよく相談を受ける。「NULL値の処理でなぜかプログラムが落ちる」「SQLCODEが-305になる」。これらは、メインフレームにおけるDB2連携の「登竜門」であり、同時に避けては通れない鉄則だ。
今日は、長年大規模バッチ改修の最前線に立ってきた経験から、PL/Iにおけるインジケータ変数の正しい扱いと、現場で「事故」を起こさないための鉄則を伝授する。
—
1. なぜNULL値で頭を抱えることになるのか?
まず大前提だ。PL/Iのホスト変数(ホスト構造体)は、DB2の「NULL値」という概念をネイティブに持っていない。PL/I側で変数に `CHARACTER` や `FIXED BIN` を定義しても、DB2からNULLが返ってきた瞬間、PL/I側のホスト変数には「何が入っているか分からない」という状態に陥る。
ここでSQLCODE -305(インジケータ変数が指定されていない)が飛んでくるわけだ。我々アーキテクトは、必ず「インジケータ変数」という名の番人を配置しなければならない。
—
2. インジケータ変数によるNULL検知の定石
インジケータ変数は `FIXED BIN(15)` で定義するのがルールだ。DB2から値を受け取る際、対応するインジケータ変数の値を確認する。
- 0以上: 値は正常。
- -1: その項目は「NULL」である。
この「-1」を検知した瞬間に、プログラム側でデフォルト値をセットするか、処理をスキップするかの分岐が必要になる。以下に、保守現場で即戦力となる実装例を示す。
実装例:インジケータ変数を活用したDB2フェッチ処理
/i
/ — PROCEDURE定義 — /
PROCESS OPTIONS(MAIN);
TEST_PGM: PROCEDURE OPTIONS(MAIN);
/ ホスト変数とインジケータ変数の定義 /
DCL 1 HOST_DATA,
3 USER_ID CHAR(10),
3 USER_NAME CHAR(20),
3 BALANCE FIXED DEC(9,0);
/ NULL判定用のインジケータ変数群(FIXED BIN(15)で定義) /
DCL IND_USER_ID FIXED BIN(15);
DCL IND_USER_NAME FIXED BIN(15);
DCL IND_BALANCE FIXED BIN(15);
/ 処理ロジック /
EXEC SQL SELECT ID, NAME, BALANCE
INTO :HOST_DATA.USER_ID :IND_USER_ID,
:HOST_DATA.USER_NAME :IND_USER_NAME,
:HOST_DATA.BALANCE :IND_BALANCE
FROM TBL_USER
WHERE ID = ‘0000000001’;
/ SQLCODEの確認 /
IF SQLCODE = 0 THEN DO;
/ NULL値のハンドリング処理 /
IF IND_BALANCE = -1 THEN DO;
/ NULLの場合は0として扱うなどの業務要件処理 /
HOST_DATA.BALANCE = 0;
PUT SKIP LIST (‘残高はNULLのため0として処理しました’);
END;
END;
ELSE DO;
/ SQLCODEが負の場合はエラー処理(ONユニット等での制御を推奨) /
PUT SKIP LIST (‘DB2エラーが発生しました。SQLCODE=’ || SQLCODE);
END;
END TEST_PGM;
—
3. ベテランが教える「事故を防ぐ」コツ
① ONユニットでのSQLエラー一括制御
個別の `IF SQLCODE` チェックも重要だが、大規模なバッチでは `ON CONDITION` や `ON ERROR` を活用して、システム全体の異常終了を制御する仕組みを設けておくべきだ。特にDB2の通信異常やデッドロック発生時は、個別のロジックで拾うのではなく、共通のリカバリ・ルーチンへ飛ばすのが「現場の品格」である。
② VSAMアクセスとの併用時の注意
PL/Iの現場では、DB2だけでなくVSAMファイルへのアクセスも頻出する。VSAMの入出力は `RECORD` モードで行うことが多いが、DB2のホスト変数とVSAMのレコードエリアを混同してはいけない。DB2から取得した値をVSAMへ書き出す際は、必ず「NULL判定後の安全な値」に置き換わっていることを確認するバリデーションを挟むこと。
③ 構造体(DECLARE 1 …)の活用
インジケータ変数は、単体で宣言するよりも、ホスト構造体に合わせてインジケータ用の構造体を別途定義しておくと、保守性が格段に上がる。
/i
DCL 1 IND_STRUCT,
3 IND_USER_ID FIXED BIN(15),
3 IND_USER_NAME FIXED BIN(15),
3 IND_BALANCE FIXED BIN(15);
このように定義しておけば、`INTO :HOST_DATA :IND_STRUCT` のように、SQL文をスッキリ記述できるからだ。
—
最後に:コードは「対話」である
PL/Iは古臭いと言われることもある。しかし、その厳格なデータ型制御とSQLとの連携には、何十年もの歴史に裏打ちされた堅牢さがある。
NULL値を放置して「何となく動く」コードを書くのではなく、「ここはNULLの可能性がある」という前提に立ち、インジケータ変数を配置する。その一手間が、深夜のシステム障害呼び出しから君を守る唯一の盾となる。
困ったときは、コンパイラが出すメッセージとSQLCODEに誠実に向き合ってほしい。機械は決して嘘をつかない。君が書いたコードの通りに、淡々と、そして正確に実行されるだけなのだから。
