おい、最近あちこちの現場で「オープン系への移行」だの「COBOLからJavaへ」だの騒がれているが、我々が守るべき基幹システムの心臓部には、今なお屈強なIBMメインフレームと、その深遠なる世界を支えるPL/Iが君臨している。
今日はな、そのPL/IとDB2を組み合わせたバッチ開発において、避けて通れない「NULL値の扱い」と「INDICATOR変数」の核心について、実務の現場で泥水をすすってきた私から、確実なノヴァを授けよう。
心して聞いてくれ。
—
1. PL/Iプログラマが直面する「DB2のNULL値」という名の罠
オープン系の連中からよくこんな質問を受ける。「なぁ、PL/Iって予約語がない(あるいは少ない)自由な言語だろ? DB2の検索結果でSQLの`NULL`が返ってきたとき、変数をどうやって初期化すりゃいいんだよ?」ってな。
COBOLなら`VALUE IS NULL`なんて句があったり、C言語ならポインタでNULL判定をしたりするが、PL/Iの世界では事情が少し違う。PL/Iの通常の変数には、残念ながら「値が入っていない状態(SQLのNULL)」を直接表現する神羅万象の魔法はない。何も値を代入しなければ、それはただの「ゼロ」か「ブランク(空白)」になってしまう。
ここでデータベースの値をそのまま取得しようものなら、数値項目なら「0」、文字項目なら「LOW-VALUES」やスペースと誤認し、「夜間バッチの集計金額が勝手にズレる」という、システム担当者が冷や汗を流して飛び起きるような重大インシデントを引き起こす。
この地雷原を華麗に回避するために存在する唯一にして最強の武器が、「INDICATOR(インジケータ)変数」だ。
—
2. INDICATOR変数の基本仕様とデータ定義のルール
INDICATOR変数は、DB2のホスト変数とペアで使用する、専ら「状態」を監視するための裏方だ。
ここがポイント!
- データ型の指定: 必ず `FIXED BINARY(15)`(COBOLでいうS9(4) COMP、いわゆる半ワード整数)として定義する。
- 配置の作法: SQL文の中でホスト変数の直後(あるいはコロンを挟んで並び)に指定する。
- 値の意味:
- `0` 以上: 通常の値が正常に格納されている。
- 負の値(通常は `-1`): そのホスト変数の値が SQLの `NULL` であることを示す。
言語仕様上の「予約語を持たない(文脈によって識別子が決定される)」というPL/Iの柔軟な構文規則のおかげで、INDICATOR変数の名前自体はプログラマが自由に命名できる。しかし、保守性の観点から、私はチーム全体で決まりきったプレフィックス(例えば `IND_` など)をつけるコーディング標準を強く推奨している。
—
3. 【実録】カーソル処理とINDICATOR判定の実装パターン
百聞は一見にしかずだ。実際のバッチプログラムで、VSAMやマスタファイルではなく、DB2からカーソル(CURSOR)を使ってデータを`FETCH`し、NULL値を安全にハンドリングする実用的なPL/Iソースコードを見てみよう。
大文字ベースの記述、適切なインデント、そして現場で泣きを見ないための丁寧なコメントを入れてある。そのままモジュール改修の参考にするといい。
1
/ ================================================================= /
/ モジュール名: EMPLST01 /
/ 処理概要 : 従業員マスタからデータを順次取得し、NULL値を判定する /
/ ================================================================= /
EMPLST01: PROC OPTIONS(MAIN);
/ — データベースホスト変数およびインジケータ変数の定義 — /
DCL HV_EMP_ID CHAR(6); / 従業員番号 /
DCL HV_EMP_NAME CHAR(30); / 従業員氏名 /
DCL HV_EMP_SALARY FIXED DEC(9,2); / 給与(数値) /
DCL HV_COMMISSION FIXED DEC(7,2); / 歩合(NULL許容) /
/ — INDICATOR変数の定義 (必ず FIXED BIN(15) とする) — /
DCL IND_COMM FIXED BIN(15); / 歩合用インジケータ /
/ — SQLCA(SQL通信領域)の埋め込み — /
EXEC SQL INCLUDE SQLCA;
/ — DB2 接続(環境に合わせて適宜変更) — /
EXEC SQL CONNECT TO PRODDB;
/ — カーソルの宣言 — /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT EMP_ID, EMP_NAME, SALARY, COMMISSION
FROM EMPLOYEE_TBL
WHERE DEPT_CODE = ‘DEV1’;
/ — カーソルのオープン — /
EXEC SQL OPEN C1;
IF SQLCODE < 0 THEN DO; PUT SKIP LIST(' ERROR: OPEN C1 FAILED. SQLCODE = ', SQLCODE); GOTO ERROR_RTN; END; / --- フェッチ・ループの開始 --- / DO WHILE(TRUE); EXEC SQL FETCH C1 INTO :HV_EMP_ID, :HV_EMP_NAME, :HV_EMP_SALARY, :HV_COMMISSION :IND_COMM; / ←ここでインジケータを指定 / / 終了コード(SQLCODE 100)の判定 / IF SQLCODE = 100 THEN LEAVE; / データ終了 / IF SQLCODE < 0 THEN DO; PUT SKIP LIST(' ERROR: FETCH FAILED. SQLCODE = ', SQLCODE); LEAVE; END; / ===================================================== / / NULL値の判定ロジック / / ===================================================== / IF IND_COMM < 0 THEN DO; / 歩合がNULLの場合の代替処理 / PUT SKIP EDIT ('社員番号: ', HV_EMP_ID, ' は歩合未設定(NULL)です。') (A, A, A); / 業務ロジック上の安全なデフォルト値をセット / HV_COMMISSION = 0.00; END; ELSE DO; / 通常の値が存在する場合の処理 / PUT SKIP EDIT ('社員番号: ', HV_EMP_ID, ' 歩合金額 = ', HV_COMMISSION) (A, A, A, F(9,2)); END; END; / DO WHILE 終了 / / --- カーソルのクローズ --- / EXEC SQL CLOSE C1; / --- DB2 切断 --- / EXEC SQL COMMIT; EXEC SQL DISCONNECT; RETURN; ERROR_RTN: / 異常終了時のハンドリング / EXEC SQL ROLLBACK; SIGNAL ERROR; END EMPLST01; ---
ベテランからのワンポイント・アドバイス
このコードを見て、「なんで `IND_COMM < 0` なんて書き方をしているんだ? `-1` じゃないのか?」と思ったそこの君、非常にいい着眼点だ。 DB2の仕様上、NULLを表すインジケータの値は基本的に `-1` だが、将来的な仕様変更や、複雑な算術演算結果のホスト変数マッピングによっては、マイナスの別ステータスが返ってくる可能性がゼロとは言えない。そのため、実務の現場では厳密に `IND_COMM = -1` と書くよりも、`IND_COMM < 0`(負の値かどうか)で判定する防御的プログラミング(Defensive Programming)が鉄則とされている。これを徹底するだけで、予期せぬデータ例外(S0C7などのデータ例外割り込み)を未然に防ぐことができるのだ。
—
4. ONユニットとの組み合わせによる例外制御
もし、万が一にもINDICATOR変数をつけ忘れたホスト変数にDB2からNULLが戻ってきた場合、PL/Iランタイムはどうなるか知っているか?
データ例外(DATAEXCEPTION)が発生し、有効なONユニット(`ON ERROR` や `ON CONDITION`)が捕捉していなければ、容赦なくアベンド(ABEND)の憂き目に遭う。
大規模バッチの真夜中の実行中に、こんな下らないポカミスでシステムを停止させないためにも、データベース入出力を行うモジュール群では、コンパイルオプションやエラー監視のONユニット設計を抜け目なく行う必要がある。PL/Iの `ON` ステートメントによるフロー制御は、メインフレームエンジニアの最後の砦だ。これを使いこなせてこそ、一人前のPL/Iプログラマと言える。
結びに代えて
PL/Iは歴史が古い分、構文の自由度が高く、書く人間のスキルによってコードの品質が大きく分かれる言語だ。しかし、今回解説したINDICATOR変数のような基礎的かつ不可欠な作法を一つひとつ確実に押さえていけば、Javaや最新の言語にも負けない堅牢で美しい基幹システムを維持し続けることができる。
若手のみんな、次のバッチ改修では「INDICATORのつけ忘れ」と「負値による安全なNULL判定」を絶対に忘れないように頼むぞ。現場からは以上だ!
