こんにちは!IBMメインフレームの世界へようこそ。
JavaやCOBOLといった他のプログラミング言語の経験がおありなら、「メインフレームのバッチ処理やデータベース連携って、なんだか硬そうだし、古いお作法が多そうで怖そうだな…」なんて身構えてしまうかもしれませんよね。
でも、安心してください。今日一緒に紐解いていく「PL/I(ピーエルワン)におけるDB2カーソル処理」も、基本のライフサイクルとちょっとした言語のクセさえ掴んでしまえば、決して怖いものではありません。むしろ、非常に理路整然とした美しい仕組みになっています。
今回は、他言語の経験者であるあなたが「なるほど、そういうことね!」とスムーズに理解できるように、身近な例えを交えながら優しく丁寧に解説していきますね。
—
1. そもそもPL/Iってどんな言語? & 予約語の話
カーソル処理に入る前に、PL/Iのちょっとユニークな特徴に少しだけ触れておきましょう。
JavaやC言語、COBOLなどでは、「この単語はシステム側で特別な意味を持つから、変数名に使ってはいけないよ」という予約語(キーワード)がたくさんありますよね。
ところが、PL/Iには「文脈によってキーワードの形が変わる」という、懐の深い(あるいは少しおせっかいな)特徴があります。
例えば、`IF` や `READ` といった単語であっても、文脈上「あ、これは変数名だな」とコンパイラが判断できれば、エラーにならずに変数として受け入れてくれたりします。(※もちろん、可読性のためにあえて予約語っぽい名前を付けるのは避けるのがプロの作法ですが!)
変数の命名規則も、最大31文字までの英数字と「_(アンダースコア)」や「$」「#」「@」が使え、大文字・小文字を区別しない(通常はすべて大文字で書くのがメインフレームの流儀です)など、COBOLに近い感覚で直感的に書くことができますよ。
—
2. DB2カーソル処理のライフサイクルを「レストランの予約」に例えてみる
さて、本題のDB2カーソル処理です。
データベース(DB2)から大量のデータを一件ずつ処理したいとき、私たちは「カーソル」という仕組みを使います。これは、イメージとしては「レストランのテーブル予約と、分厚いメニューからの注文取り」にそっくりです。
全体の流れ(ライフサイクル)は、以下の4ステップで完結します。
1. DECLARE(宣言):お店のスタッフに「こういう条件のコース料理(SQL文)をください」とメニューの内容を伝えて控えてもらう。
2. OPEN(オープン):お店に入り、実際にキッチンで料理の準備をスタートしてもらう。
3. FETCH(フェッチ):料理が1皿ずつ運ばれてきて、それをテーブルで受け取って味わう(1行ずつデータを取得する)。
4. CLOSE(クローズ):食事が終わり、テーブルを片付けてお会計をして店を出る。
この4つのステップを、実際のPL/Iコードでどのように表現するのか、具体的なサンプルを見ていきましょう。
—
3. 実践!PL/I + DB2 カーソル処理のサンプルコード
以下のサンプルコードは、社員テーブル(`EMP`)から特定の部署(例:開発部)の社員を1人ずつ取り出して処理する、よくあるバッチプログラムの断片です。
/ ———————————————— /
/ DB2カーソル処理のサンプルプログラム /
/ ———————————————— /
EMP_REPORT: PROC OPTIONS(MAIN);
/ 1. 変数およびホスト変数の宣言 /
DCL W_DEPT CHAR(3) INIT(‘DEV’); / 検索条件:開発部コード /
DCL H_EMP_ID CHAR(6); / 社員ID(ホスト変数) /
DCL H_EMP_NAME CHAR(20); / 社員名(ホスト変数) /
DCL H_SALARY FIXED DEC(9,2); / 給与 (ホスト変数) /
/ SQLの戻り値を受け取るステータス変数 /
DCL SQLCODE FIXED BIN(31);
DCL W_EOF_FLG CHAR(1) INIT(‘OFF’); / データの終わりを示す自前フラグ /
/ 2. 組み込みSQLの宣言(埋め込みSQL) /
/ EXEC SQL と END-EXEC で囲むのがお作法です /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT EMP_ID, EMP_NAME, SALARY
FROM EMPLOYEE
WHERE DEPT_CD = :W_DEPT
ORDER BY EMP_ID;
/ 3. カーソルのオープン(レストランの開店) /
EXEC SQL OPEN C1;
IF SQLCODE ^= 0 THEN DO;
DISPLAY(‘ERROR: カーソルのオープンに失敗しました。SQLCODE = ‘ || SQLCODE);
GOTO ERROR_RTN;
END;
/ 4. ループ処理によるデータ取得(FETCH) /
DO WHILE (W_EOF_FLG = ‘OFF’);
EXEC SQL FETCH C1 INTO :H_EMP_ID, :H_EMP_NAME, :H_SALARY;
/ SQLCODEによるエラーおよび終了判定 /
SELECT (SQLCODE);
WHEN (0) / 正常に1行取得できた場合 /
CALL PROCESS_RECORD; / 社員ごとの詳細処理(給与計算など)を呼び出す /
WHEN (100) / データがもう存在しない場合(EOF) /
W_EOF_FLG = ‘ON’;
DISPLAY(‘INFO: すべてのデータの読み込みが完了しました。’);
OTHERWISE / 想定外のエラーが発生した場合 /
DISPLAY(‘ERROR: FETCH中にエラーが発生しました。SQLCODE = ‘ || SQLCODE);
W_EOF_FLG = ‘ON’;
GOTO ERROR_RTN;
END;
END;
/ 5. カーソルのクローズ(お会計と退店) /
EXEC SQL CLOSE C1;
IF SQLCODE ^= 0 THEN DO;
DISPLAY(‘ERROR: カーソルのクローズに失敗しました。SQLCODE = ‘ || SQLCODE);
GOTO ERROR_RTN;
END;
RETURN;
ERROR_RTN:
/ 異常終了時のクローズ処理(念のため) /
EXEC SQL CLOSE C1;
DISPLAY(‘PROGRAM ABENDED.’);
SIGNAL ERROR;
END EMP_REPORT;
—
4. コードのポイントと、つまずきやすい罠の回避術
先ほどのコードを見て、「あれ?」と思ったところはありませんでしたか? Javaや他のモダン言語とは少し違う、メインフレーム・DB2ならではのポイントをいくつか解説しますね。
① ホスト変数にはコロン(`:`)をつける
SQL文の中でPL/Iの変数(メモリ上の領域)を参照するとき、変数名の頭にコロン `:W_DEPT` や `:H_EMP_ID` がついていますよね。これはDB2プリコンパイラ(SQLを翻訳するプログラム)に対する「おい、これはDB2の列名じゃなくて、PL/I側の変数だからな!」という大切な目印です。これを忘れるとコンパイルエラーや予期せぬ誤動作の元になりますので、しっかり覚えておきましょう。
② `SQLCODE` によるエラーハンドリング
DB2の操作を行うと、必ずシステムが `SQLCODE`(および `SQLSTATE`)という結果コードを返してくれます。
- `0`:大成功!正常終了です。
- `100`:これ以上データがありません(End of File)。エラーではなく正常な終了サインなので慌てずにループを抜けましょう。
- マイナスの値:データベース側で何かしらのエラー(一意制約違反やデッドロックなど)が発生しています。異常終了ルートへ飛ばしましょう。
Javaの例外処理(`try-catch`)に慣れていると、`SQLCODE`の値を `SELECT`文や `IF`文でいちいち判定するスタイルは少しレトロに感じるかもしれません。しかし、メインフレームのバッチ処理では、この「戻り値のステータスコードを愚直に判定して制御する」というフローが非常に確実で、トラブルシューティングのしやすさにも繋がっています。
③ クローズは「やり忘れ厳禁」の儀式
レストランを出るときにお会計を忘れないのと同じで、`OPEN`したカーソルは、正常終了時であっても異常終了時であっても、必ず最終的に `CLOSE` する必要があります。開けっぱなしにしておくと、データベースのリソース(ロックやメモリ)を圧迫し、オンライン画面や他のバッチに大迷惑をかけてしまう原因になります。上記のコードのように、エラー時でも必ずクローズを通るような「後始末の動線」を作っておくのがプロの技です。
—
さいごに
いかがでしたでしょうか?
「PL/I」や「メインフレームのDB2」という言葉の響きに気圧されていた方も、レストランの予約に例えたライフサイクルや、コードの裏側にある意図をひとつずつ紐解いていけば、決して恐ろしいものではないことが伝わったのではないでしょうか。
他の言語で培ったあなたのプログラミングの基礎力があれば、PL/Iの構文や作法などすぐに自分のものにできます。ぜひ、自信を持って日々のバッチ改修やマイグレーション調査に臨んでみてくださいね。あなたのメインフレーム開発ライフを、心から応援しています!
