こんにちは!メインフレームのの世界へようこそ。
JavaやCOBOLといった他のモダンな言語や業務言語をご経験されてきた方にとって、IBMメインフレームの旗手である「PL/I(ピーエルワン)」、そしてそこに君臨するデータベース「DB2」との連携は、最初は少し独特な壁があるように感じられるかもしれませんね。
「なんだか記号が多くて難しそう…」
「COBOLのピクチャ句とも、JavaのBigDecimalとも違う宣言に戸惑う…」
そんな風に不安になっていませんか?大丈夫です。怖がる必要はまったくありません。今回は、基幹システムで最も頻出する「DB2のSQLとPL/Iの固定小数点数(FIXED DECIMAL)のマッピング」について、一緒に一つずつ、優しく紐解いていきましょう。ここさえ押さえれば、DB2とPL/Iの会話はバッチリ通じるようになりますよ!
—
1. なぜDB2とPL/Iの型合わせが重要なのか?
JavaやCOBOLで開発をされてきた方なら、「データベースの型と、プログラム内の変数の型を合わせる」というのは常識ですよね。
しかし、メインフレームの世界では、この「型合わせ」を少しでもサボったり、ルールを誤ったりすると、コンパイルエラーになるどころか、実行時に恐ろしい「S0C7(データ例外アブドンプ)」を引き起こしたり、最悪の場合、サイレントにデータが切り捨てられて金額が合わなくなったりという、夜も眠れなくなるようなドラマを生む原因になります。
特に、お金の計算や数量など、1円たりとも狂いが許されないデータで使われるのが、DB2の `DECIMAL` 型と、PL/Iの `FIXED DECIMAL` 型です。この二人がどのように手を繋いでいるのか、まずはその正体を見てみましょう。
—
2. PL/Iの `FIXED DECIMAL` ってどんな子?
Javaの `BigDecimal` や、COBOLの `PIC S9(p)V9(q) COMP-3` をイメージしてください。あれのPL/I版が `FIXED DECIMAL(p, q)` です。
ここで、初学者が一番混乱する「括弧の中の数字」についてお話しますね。
- `p`(精度 / Precision): 全体で何桁の数字を表現できるか(符号は含みません)。最大15〜31桁(コンパイラや設定によりますが、基本は15桁以内が安全です)。
- `q`(尺度 / Scale): そのうち小数点が何桁あるか。
例えば、`FIXED DECIMAL(9, 2)` と宣言したとします。
これは、「全体で9桁、そのうち下2桁が小数ですよ」という意味になります。
- 表現できる最大の値:`9999999.99`
- 必要なメモリ(ストレージ):パック十進数(Packed Decimal)として保持されるため、`(p + 2) / 2` の切り捨て、つまり `(9 + 2) / 2 = 5` バイトとなります。
COBOLの `COMP-3` と全く同じメモリの持ち方をしますので、メインフレームのストレージ効率を極限まで高めた、非常にエコで優秀な子なんです。
—
3. DB2埋め込みSQLにおけるホスト変数宣言の黄金ルール
さて、本題です。DB2のテーブル定義(CREATE TABLE)で以下のような列があったとします。
— DB2のテーブル定義の例
AMOUNT DECIMAL(9, 2) NOT NULL
この列に対して、PL/Iのプログラムから値をSELECTしたり、INSERTしたりするための「ホスト変数(Host Variable)」を定義する場合、どのように書けばよいでしょうか?
正解はこうです!
DCL HV_AMOUNT FIXED DECIMAL(9, 2);
……「えっ、そのままじゃん!」と思いましたか?
そうなんです、基本は「DB2の精度と、PL/Iの精度を完全に一致させる」、これが大原則であり、最も安全な道です。
しかし、実務の現場では、もう少し踏み込んだ「お作法」や「罠」が存在します。先輩たちがよくハマるポイントをいくつかご紹介しますね。
—
4. 実務で役立つ!ホスト変数定義の3大ポイントとコード例
実際のPL/I埋め込みSQLプログラムの断片を見てみましょう。SQLエリアやインジケータ変数も含めた、リアルな書き方です。
DCL 1 TBL_REC,
/ 顧客ID:整数型はFIXED BINARY(31)にマッピングするのが定番です /
5 CUST_ID FIXED BINARY(31),
/ 請求金額:DB2のDECIMAL(9,2)に対応するホスト変数 /
5 HV_AMOUNT FIXED DECIMAL(9, 2),
/ 金額用のインジケータ変数(NULLを受け取る可能性があるなら絶対必要!) /
5 IND_AMOUNT FIXED BINARY(15);
/ — 実際のSQL処理のイメージ — /
/
DB2のテーブルから金額を読み込むSQL
※NULL値を受け取るかもしれない項目には、必ず宿敵ならぬ宿命の「インジケータ変数」を添えます。
/
EXEC SQL
SELECT CUST_ID, AMOUNT
INTO :CUST_ID, :HV_AMOUNT :IND_AMOUNT
FROM CUSTOMER_BILLING
WHERE CUST_ID = :IN_CUST_ID;
ここで、実務上絶対に知っておいてほしい重要なポイントを3つ解説します。
① 宣言の桁数(p)は一致させていますか?
DB2側が `DECIMAL(11, 2)` なのに、PL/I側でうっかり `FIXED DECIMAL(7, 2)` などと小さく宣言してしまうと、コンパイルは通ることがあっても、実行時にデータ溢れ(Overflow)を起こしたり、上位の桁がスパッと切り捨てられて大惨事になります。DB2の定義書(DCLGENなど)を必ず正として、ホスト変数を定義してください。
② 小数点以下の桁数(q)の不一致に注意
もしDB2側が `DECIMAL(9, 2)` なのに、PL/I側を `FIXED DECIMAL(9, 0)` にしてしまうと、自動的に小数点以下が丸められてしまいます。「あれ?100円50銭だったはずが、100円になってる!?」という現象の多くはこれが原因です。型のスケール(q)は1ミリもズラさないのが鉄則です。
③ NULLを甘く見ない(インジケータ変数の活用)
DB2の列が `NULLABLE`(NULLを許容する)場合、プログラム側でインジケータ変数(上のコードの `IND_AMOUNT`)を用意し忘れると、DB側からNULLが飛んできた瞬間にプログラムが異常終了します。
PL/Iでは、ホスト変数のすぐ後ろに半角スペースを空けてインジケータ変数を書きます(カンマは不要です!ここもよくある初学者のミスポイントです)。
- `IND_AMOUNT < 0` なら「あ、今回はNULLなんだな」と判定できます。
—
5. おわりに
いかがでしたでしょうか?
PL/Iの `FIXED DECIMAL(p, q)` は、見慣れないうちは呪文のように見えるかもしれませんが、要するに「全体で何桁、小数点が何桁」というルールをDB2の定義とガッチリ合わせるだけの、とても素直で実直なデータ型です。
他の言語のご経験がある方なら、メモリの持ち方(パック十進数)の概念さえ掴んでしまえば、すぐに手に馴染むはずです。
メインフレームのレガシーな世界も、こうして一つずつ紐解いていけば、怖がるどころか、その堅牢で合理的な設計にむしろ感動すら覚えるようになりますよ。あなたのPL/Iライフ、そしてマイグレーションや保守の作業がスムーズに進むよう、心から応援しています!
