こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他の言語での経験をお持ちの方にとって、IBMメインフレームの世界や「PL/I(ピーエルワン)」という言語は、どこか要塞のようで少し近寄りがたく感じられるかもしれませんね。特に、画面いっぱいに広がる独特のコードや、歴史を感じさせるデータ定義(属性)を目にすると、「うわ、難しそう……」と身構えてしまうのも無理はありません。
でも、安心してください。ベースにある考え方を一つずつほぐしていけば、PL/Iは非常に合理的で、実は温かみのある(?)言語なんですよ。
今回は、そんなPL/Iと基幹システムの心臓部であるDB2データベースを繋ぐ、とっても重要なテーマ「INDICATOR(インジケータ)変数とNULL値の判定」について、実務の現場の空気感を交えながら優しく解説していきますね。
—
そもそも、なぜ「NULL(ヌル)」の扱いに気をつかうの?
私たちが普段JavaやCOBOLなどでビジネスロジックを書くとき、変数が「値を持っていない状態」を表現するのに苦労した経験はありませんか?
Javaなら `null` でお馴染みですが、データベース(DB2)の世界でも「データが存在しない」状態を表すために `NULL` という特別な概念が存在します。
ここで大きな問題が一つ。
COBOLや古い時代のプログラミング言語では、「数値型の変数には、常に何かしらの数字が入っていなければならない」という鉄の掟がありました。もしDB2から「ここ、データ入ってないです(NULLです)」と値を引き抜いたとき、受け皿の変数にその情報が入らないと、プログラムはパニックを起こして異常終了(お馴染みのABENDですね)してしまいます。
そこで登場するのが、INDICATOR変数(インジケータ変数)という名の「お助けバディ」です。
—
INDICATOR変数ってどんなもの?
イメージしてみてください。
あなたが倉庫から荷物(データ)を台車に乗せて運んできたとします。その荷物が「ちゃんとした中身が入っている段ボール」なのか、「中身がからっぽの空き箱」なのか、見た目だけだと分かりにくいですよね。
そこで、空き箱の上には「【注意】中身はありません!」と書かれた小さな札を一緒に添えておくことにします。この「小さな札」の役割をするのが、PL/IにおけるINDICATOR変数なんです。
データの宣言ルール
PL/IでDB2のホスト変数を定義するとき、データベースから値を受け取るための変数とペアで、INDICATOR変数を宣言します。
ここでPL/Iの大きな特徴である「データストレージ属性」が登場します。INDICATOR変数は、DB2の仕様で必ず「半精度整数(`SMALLINT`)」、つまりPL/Iで言うところの `FIXED BINARY(15)` として定義しなければなりません。
実際のコードを見てみましょう。怖くないですよ、一緒に見ていきましょうね。
1
/ ————————————————せ /
/ 顧客テーブルからデータを取得する際のホスト変数宣言 /
/ ————————————————せ /
DCL H-CUST-ID CHAR(5); ジュ / 顧客ID(文字型) /
DCL H-CUST-NAME CHAR(30); ジュ / 顧客名(文字型) /
DCL H-SALES-AMT FIXED DEC(9,2); ジュ / 売上金額(数値型) /
/ ↓ここが主役のINDICATOR変数です!(必ず FIXED BIN(15) にします) /
DCL H-SALES-IND FIXED BIN(15); ジュ / 売上金額のインジケータ /
どうですか? `FIXED BIN(15)` なんて見慣れない呪文に見えるかもしれませんが、「あ、COBOLでいう `PIC S9(4) COMP` みたいな、ちっちゃい整数なんだな」と思ってもらえればバッチリです。
—
FETCH時の値判定ロジックを覗いてみよう
では、実際にDB2からカーソルを使ってデータを取り出す(`FETCH`する)とき、このインジケータ変数をどう使うのか、具体的なロジックの流れを見てみましょう。
1
/ データベースから1件フェッチ(取得)する処理 /
EXEC SQL
FETCH CUST_CURSOR
INTO :H-CUST-ID,
:H-CUST-NAME,
:H-SALES-AMT :H-SALES-IND; ジュ / ←ここで変数とインジケータをペアで指定します /
/ インジケータ変数の値によって処理を分岐する /
IF H-SALES-IND < 0 THEN
DO;
/ 札の値がマイナス(通常は -1)なら、DB2のデータは「NULL(値なし)」だったということ! /
PUT SKIP EDIT ('売上金額は未設定(NULL)です。') (A);
H-SALES-AMT = 0; ジュ / 演算エラーを防ぐために安全な初期値 0 を入れておきます /
END;
ELSE
DO;
/ 札の値が 0 以上なら、ちゃんとしたデータが入っています /
PUT SKIP EDIT ('売上金額: ', H-SALES-AMT) (A, F(9,2));
END;
ここで注目してほしいポイントは、SQL文の中での書き方です。
`:H-SALES-AMT :H-SALES-IND` のように、変数名の後ろにスペースを空けて、コロンをつけたインジケータ変数をつなげるだけでOKなんです。カンマ(`,`)を入れたりしないのが、最初はちょっと引っかかりやすいポイントかもしれませんね。
そして判定ロジックの肝ですが、「インジケータ変数がマイナス(`< 0`)かどうんかを見る」のが鉄則です。DB2が正常にNULLを検知すると、このインジケータ変数に `-1` を返してくれる仕組みになっています。
—
予約語を持たないPL/Iの優しさ(と、ちょっぴり注意点)
ここで、PL/Iの少しユニークで面白い仕様についてお話しておきますね。
タイトルにもある通り、PL/Iには「厳密な意味での予約語(Keyword)」がほとんど存在しません。
JavaやCOBOLだと、`IF` や `DATA`、`ID` といった単語は「言語のシステム側で予約されているから、変数名に使っちゃダメ!」と怒られてしまいますよね。
しかし、PL/Iは懐が深いです。コンパイラが前後の文脈(Context)を読み取って、「おっ、ここは変数名だな」「おっ、ここは命令文(動詞)だな」と賢く判断してくれます。
例えば、極端な話ですが、こんな変数宣言もPL/Iではエラーになりません。
1
DCL IF CHAR(3); ジュ / 「IF」という名前の文字変数 /
(※コンパイラが混乱して人間が頭を抱えることになるので、実務でこんな名前をつけるのは絶対にやめましょうね!笑)
この「文脈で判断する」という大らかな性質があるおかげで、INDICATOR変数にどんな名前をつけようと、PL/I自身は寛大に受け止めてくれます。ただ、チームで開発する際は、後からコードを読む仲間のために、今回紹介した `H-SALES-IND` のように、変数名の後ろに `-IND` や `-INDICATOR` と分かりやすいサフィックス(接尾辞)をつけておくのが、レガシー開発現場の優しいマナーであり、愛されるプログラマの秘訣です。
—
おわりに
いかがでしたでしょうか?
「INDICATOR変数」という響きや、見慣れないデータ定義(`FIXED BIN(15)`)に最初は少しドキッとしたかもしれませんが、蓋を開けてみれば「DB2のNULL値を受け止めるための、ただの小さな番兵(フラグ変数)」に過ぎません。
基幹システムのマイグレーションや保守の現場では、こうした一見難解に見える仕様が山のように登場します。でも、一つひとつの部品が「何のために存在しているのか」を紐解いていけば、決して怖いものではありません。
これからも、レガシー世界の海原を航海するあなたを、そっと、そして力強くサポートしていきますね。次回の解説もお楽しみに!
