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

おい、最近配属された若手のエース君。夜間バッチのABEND(異常終了)解析で青い顔をして私のもとに駆け込んでくる前に、少しこの話を聴いてくれ。

君が今、まさに設計書を睨みながらコーディングしようとしている「DB2埋め込みSQLとPL/Iのホスト変数(Host Variable)のマッピング」、特に `FIXED DECIMAL` の扱いはな、基幹系システムの現場において「動いたから良し」で済ませると、半年後に血を吐くようなデバッグ地獄を引き起こす魔物が潜んでいるんだ。

今日のテーマは、まさにそこだ。PL/Iの `FIXED DECIMAL(p, q)` と、DB2側が構える `DECIMAL(p, q)` の深くて暗い関係について、ベテランの私が現場のノウハウを引っ提げて叩き込んでやろう。

—

なぜ、DB2とPL/Iの「型合わせ」で夜間バッチが落ちるのか?

メインフレームの世界では、COBOLが主流のように思われがちだが、我が社の骨幹を支える勘定系や大規模物流のコアロジックは、今でも最高性能のPL/Iでバリバリ動いている。そして、そのデータストアはもちろんIBM DB2 for z/OSだ。

ここで問題になるのが、SQLの `DECIMAL(p, q)`(ゾーン10進数またはパック10進数としてDB2内部で保持される)を、PL/I側でどう受け受けるかという点だ。

精度(Precision: p)とスケール(Scale: q)の罠

PL/Iの `FIXED DECIMAL(15, 2)` は、全体で15桁、そのうち小数点以下が2桁の固定小数点数を表す。ここで初心者がやりがちなミスが、DB2側の定義とPL/I側の定義の桁数ミスマッチだ。

  • DB2側: `DECIMAL(13, 2)`
  • PL/I側: `FIXED DECIMAL(15, 2)`

「おっ、PL/I側を大きめに取っておけば安全だな」なんて甘い考えでホスト変数を定義していないか?
実は、埋め込みSQLのプリコンパイラ(DB2precompiler)は厳密だ。型の不一致やサイズの不適切なオーバレイが発生すると、最悪の場合、SQLCODE `-305`(NULL値インジケータなしでのNULL格納)や `-802`(算術例外:データ例外、切り捨てエラーやオーバーフロー)を叩き出し、夜間バッチが盛大にクラッシュする。

特に、計算結果をINSERTする際や、SUM関数などの集計値をホスト変数に受けるとき、PL/I側の `FIXED DECIMAL` の宣言桁数がDB2側の定義に対して小さすぎたり、逆に大きすぎてプリコンパイラが警告を出したりするケースが後を絶たない。

—

実践:堅牢なホスト変数定義とONユニットによる例外管理

百聞は一見にしかずだ。実際のバッチプログラムを想定した、安全かつモダン(かつメインフレームスタンダード)なPL/Iコードを見てみよう。

大文字ベースの記述、適切なインデント、そして何より予期せぬデータ異常からシステムを守るための `ON` ユニットの組み込み方に注目してほしい。

1
/ ========================================================== /
/ MODULE NAME: DB2DEC01 /
/ DESCR : DB2 FIXED DECIMAL ホスト変数マッピングサンプル /
/ ========================================================== /
DB2DEC01: PROC OPTIONS(MAIN);

/ — 埋め込みSQL 宣言セクション — /
EXEC SQL INCLUDE SQLCA;

/ ホスト変数宣言の開始 /
EXEC SQL BEGIN DECLARE SECTION;
/ 顧客ID: 整数型 (DB2: INTEGER <-> PL/I: FIXED BINARY(31)) /
DCL HV_CUST_ID FIXED BIN(31) INIT(0);

/ 購買金額: DECIMAL(11,2) (DB2と完全に一致させること!) /
DCL HV_SALES_AMT FIXED DEC(11, 2) INIT(0);

/ 顧客名: 可変長文字列 /
DCL HV_CUST_NAME CHAR(50) VARYING;
EXEC SQL END DECLARE SECTION;

/ ワーク変数 /
DCL W_RETRY_CNT FIXED BIN(15) INIT(0);
DCL MSG_BUF CHAR(100);

/ — 異常系トラップ用 ONユニットの定義 — /
/ 算術オーバーフローやゼロ割り込みをキャッチする /
ON CONVERSION
BEGIN;
PUT SKIP LIST(‘ ERROR: データ変換エラーまたはオーバーフローが発生しました ‘);
/ 必要に応じてログ出力や異常終了処理へ誘導 /
GOTO ERROR_RTN;
END;

ON FIXEDOVERFLOW
BEGIN;
PUT SKIP LIST(‘ ERROR: FIXED DECIMAL演算でオーバーフロー発生 ‘);
GOTO ERROR_RTN;
END;

PUT SKIP LIST(‘=== 顧客売上集計バッチ 開始 ===’);

/ DB2データベース接続 /
EXEC SQL CONNECT TO :DB_NAME; / 実際には環境に応じた接続記述 /

IF SQLCODE ~= 0 THEN DO;
PUT SKIP LIST(‘DB2 CONNECT ERROR. SQLCODE =’, SQLCODE);
GOTO ERROR_RTN;
END;

/ — カーソル宣言とフェッチ処理 — /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT CUST_ID, CUST_NAME, SALES_AMOUNT
FROM CUST_SALES_TBL
WHERE SALES_AMOUNT > 1000.00;

EXEC SQL OPEN C1;

DO WHILE(TRUE);
EXEC SQL FETCH C1 INTO :HV_CUST_ID, :HV_CUST_NAME, :HV_SALES_AMT;

/ SQLCODE 100 はデータ終了(EOF) /
IF SQLCODE = 100 THEN LEAVE;

IF SQLCODE < 0 THEN DO; PUT SKIP LIST('FETCH ERROR. SQLCODE =', SQLCODE); LEAVE; END; / BUILTIN関数を活用したデータの検証と加工 / / 例えば、金額が妥当な範囲内か確認する / IF HV_SALES_AMT > 9999999.99 THEN DO;
PUT SKIP LIST(‘警告: 許容値を超える売上データを検知 ID:’, HV_CUST_ID);
END;

/ 画面やログへの出力(編集出力) /
PUT SKIP EDIT (‘CUST ID: ‘, HV_CUST_ID,
‘ NAME: ‘, HV_CUST_NAME,
‘ AMT: ‘, HV_SALES_AMT)
(A, F(10), A, A(50), A, F(11,2));

END;

EXEC SQL CLOSE C1;

/ 正常終了 /
EXEC SQL COMMIT;
PUT SKIP LIST(‘=== 顧客売上集計バッチ 正常終了 ===’);
RETURN;

ERROR_RTN:
/ 異常終了ルーチン /
EXEC SQL ROLLBACK;
PUT SKIP LIST(‘=== 顧客売上集計バッチ 異常終了 (ABEND) ===’);
/ 意図的な異常終了コードの設定(メインフレームのJCLへ通知) /
SIGNAL ERROR;

END DB2DEC01;

—

ベテランからの実践的アドバイス:ここだけは押さえろ!

上記のコードと、日々の保守経験から得た「黄金律」をいくつか授けよう。

1. 型と桁数は「完全一致」が正義
DB2側の `DECIMAL(p, q)` を変更したのに、PL/I側の `FIXED DEC(p, q)` を直さないバカがたまにいる。プリコンパイル時に警告が出ようとも、それを無視してビルドを通すと、実機テストのフェーズや、最悪本番稼働の初日にパリティエラーや予期せぬ数値化け(ゴミデータの混入)を引き起こす。DB2定義 = ホスト変数定義、これを鉄則にしろ。

2. スケール(小数位)の暗黙の切り捨てに怯えろ
PL/I内で `FIXED DEC(5, 2)` の変数を `FIXED DEC(5, 0)` の変数に代入・演算する際、コンパイラは文句を言わずに小数点を勝手に切り捨てる(あるいは四捨五入ではなく単なる切り捨て)ことがある。金銭データを扱うバッチでこれが起きると、数円のズレが発覚して監査法人から強烈な指摘を受けることになる。桁上げ・桁下げの算術演算を行う際は、必ず `ROUND` ビルトイン関数を使うか、十分な精度を持ったワークエリアを介すことだ。

3. ON ユニット(`FIXEDOVERFLOW` / `CONVERSION`)は命綱
DB2から受け取ったデータが想定外に大きかったり、ゴミデータが混入していたりした場合、PL/Iは標準ではハードウェア例外やプロセス強制終了を引き起こす。しかし、上記のように `ON FIXEDOVERFLOW` や `ON CONVERSION` を適切に配置しておけば、プログラム内でトラップしてログに詳細なキー情報を残し、安全にロールバックしてバッチを落とす(あるいはリトライする)ことができる。無防備なプログラムは、夜間運用者にとって最大の脅威なのだ。

—

さあ、理屈は分かったな?
マイグレーションや改修の際、「動けばいいや」ではなく、「なぜこの桁数なのか」「DB2の内部表現とPL/Iのパック10進数表現がどうメモリ上で整合しているか」を意識しながらコードを書くんだ。

次に君が書くプログラムは、深夜の運用担当者を起こさない、美しく堅牢なコードであると期待しているぞ。さあ、席に戻って作業を続けたまえ!

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