iCafe | IBM i / AS400等に関する技術情報ポータルサイト

IBM i / AS400等に関する技術情報ポータルサイト

コラム
【FFRPG】第六回 データベース – ネイティブ・アクセス(照会プログラム)


前回の復習

第五回 データベース – SQL(更新・削除・追加プログラム)」では、データベースに対する更新・削除および追加を SQL を使用して行う方法を解説しました。SQL をご存知の方は、RPG 言語を使用した IBM i のデータベース操作を違和感なく理解できたのではないかと思います。SQL を組み込んだ FFRPG を使用して、様々な技術者が IBM i のシステム開発にどんどん参加してくれることを願っています。

第四回で触れましたが、RPG 言語のデータベース・プログラミングは SQL の他にネイティブ・アクセスという方法があります。これは SQL が IBM i で使用できるようになる前から利用されてきた、IBM i における DB2 for i への基本的なアクセス方法です。SQL とかなり異なる書き方になるので、今回と次回の二回に渡ってじっくり解説していきたいと思います。この手法もぜひマスターしてください。

ネイティブ・アクセス

ではまず、SQL によるアクセスとネイティブ・アクセスの違いを説明します。SQL は対象となるレコードを抽出するために WHERE で条件を指定しました。この条件により処理対象のデータが1レコードに限定されることもあれば、複数レコードとなる場合もあります。この特徴を生かして、ある条件に該当する複数レコードを同時に更新したり削除したりする時には SQL を使用すると便利です。

これに対して、ネイティブ・アクセスは常にレコード単位のアクセスを行います。SQL のような複数レコードを同時に処理することはできません。レコードを更新および削除する時も、常に1レコード毎にステートメントを使用して行います。このため場合によっては SQL よりコードが複雑になることもあります。しかし、細かなレコード単位の処理を行うことが必須である SoR システム構築には最適な方法であり、IBM i の既存のプログラムは殆どがネイティブ・アクセスで実装されています。

DB2 for i のデータベース・ファイルは、このレコード単位でのアクセスを可能とするために、アクセス・パスと呼ばれる仕組みが実装されています。アクセス・パスは以下の2種類です。

  • 到着順アクセス・パス
    到着順アクセス・パスは、文字通りファイルにレコードが追加された順番を記録しているもので、その順番を相対レコード番号にシステムが自動的に採番して割当てていきます。データベースにアクセスする際にこの到着順アクセス・パスを使用することで、記録された順番でレコードを取り出すことができるようになります。
  • キー順アクセス・パス
    もうひとつのアクセス・パスはキー順アクセス・パスと呼ばれます。DB2 for i ではファイルを作成する際にキー・フィールドを指定することができますが、このキーの値と相対レコード番号の対応表がこのキー順アクセス・パスです。キー順アクセス・パスはキーの値によって並んだ表なので、これをプログラムで使用することにより、到着順ではなくキーの並び順でレコードを取り出すことができます。また、特定のキーの値のレコードを直接取り出すことも可能になります。

ファイルのレコードが追加、変更および削除されるたびにアクセス・パスは保守されなければなりませんが、これはファイルに定義されたタイミングで OS が自動的に行います。つまり、プログラムは常に最新の並び順でデータを操作することが保証されるのです。

ポインターについて

プログラムはどちらかのアクセス・パスを使用するように設定します。データベース・ファイルがオープンされると、アクセス・パスの先頭にポインターが設定されます。このポインターの役割は、直前にアクセスしたレコードの位置をアクセス・パス内で記憶することにあります。後ほど照会するレコード・アクセスのステートメントで詳細を解説します。ネイティブ・アクセスでは、条件にあうキー順アクセス・パスとポインターを使ってキーの順番通りにレコードを取り出すことも、ランダムにアクセスすることも自由に操作可能です。

ファイル定義

それではネイティブ・アクセスを行うのに必要なステートメントを紹介していきます。まずファイルをネイティブ・アクセス処理するには、プログラムで使用するファイルを DCL-F ステートメントで定義しなければなりません。

SQL の場合は、ファイルを定義する必要はなかったので不思議に思われるかもしれませんね。実は IBM i ではデータベース以外でもファイルとして処理できるものがあり、RPG ではそれらを同じステートメントで使用することができます。そのため、今どの種類のファイルを操作しているのかをシステムが判断できるように、プログラム内で使用するファイルの種類を指定する必要があるのです。今回はデータベースとしての定義の仕方を説明します。それ以外のファイルの定義方法と使用方法については第八回で解説します。

それでは DCL-F で指定する主なキーワードを解説しましょう。

DISK キーワードは、このファイルがデータベースであることを示しています。このキーワードはファイル名の直後に指定しなければなりません。

USAGE キーワードは、プログラム内でのファイルの使用方法を指定します。指定した方法以外の使用はできないので注意しましょう。たとえば、

  • USAGE(*INPUT)

と指定したファイルに対してレコードを追加、更新および削除することはできません。これは実行時にエラーになるのではなくコンパイル時に判定されます。

KEYED キーワードは、そのファイルのキー順アクセス・パスをプログラムで使用することを宣言するものです。これを指定することにより、そのファイルに対してキー付ファイル命令を使用することができるようになります。このキーワードを指定しないと、到着順アクセス・パスを使用することになり、キーによる処理ができなくなるので注意しましょう。

DCL-F ステートメントにファイル名のみを指定した場合、以下が省略値として使用されます。

  • データベース・ファイル
  • 入力専用

つまり、以下はすべて同じ意味になります。

どの書き方が良いのかはそれぞれで判断していただければと思いますが、個人的にはプログラムの可読性を考えて、省略値もすべて記述するほうが良いのではと思います。

DCL-F は、最初のプロシージャー定義(main サブ・プロシージャー)の前と、サブ・プロシージャー内の2箇所で記述することができます。前者で定義するとグローバル・ファイル、後者はローカル・ファイルと言います。

ローカル・ファイル

マニュアルには「ローカル・ファイルに対する入出力は、データ構造を使用することによってのみ実行できます。」と記述があります。この意味を説明します。

組込 SQL では、対象フィールドを SELECT の直後に指定し、取得したフィールドの値は事前に用意したホスト変数にセットされました。これに対しDCL-F で定義したファイルでは、レコードを読み取ると基本的に全てのフィールドの値が取得され、値は読み取り命令で指定したデータ構造にセットされます。つまり「データ構造を使用する」とは、このアクセスによって取得された全フィールドの値が、ホスト変数ではなく指定したデータ構造にのみセットされるということです。このデータ構造はプログラムで事前に定義しておく必要があります。

グローバル・ファイルの場合は、RPG コンパイラーがレコードの各フィールドの値を保存するための変数を自動的に用意します。データベース・ファイルはファイル自身にレコードを構成するフィールドの定義を持っています(これを外部記述という)ので、コンパイラーはその値を参照し、その定義を使って I 仕様書(入力仕様書)を自動で生成するのです。この I 仕様書は、読み取ったレコードのフィールド構成を記述するもので、定位置記入形式ではプログラマーが記述可能ですが、FFRPG では使用できません。しかし、コンパイラーは内部的に使用できるためこれが可能なのです。

それに対して、サブ・プロシージャー内では I 仕様書は使用することができないため、コンパイラーがローカル・ファイルの外部記述を使用して変数を自動的に用意することができません。そのため、プログラマーがデータ構造を事前に用意しておかなければならないのです。

スコープについて

グローバル・ファイルは、すべてのサブ・プロシージャーから参照することができます。取得した値を保存する変数には静的記憶域が使用されるため、プログラムが実行されている間存在しています。ファイルのオープンは基本的にプログラムの開始時点で自動的に行われます。

ローカル・ファイルは、それを定義したサブ・プロシージャー内でのみ参照可能です。サブ・プロシージャーが開始されるとファイルは自動的にオープンされ、RETURN 命令もしくは最後のステートメントが実行されたあとでサブ・プロシージャーが終了すると自動的にクローズされます。入出力で使用するデータ構造をサブ・プロシージャー内で定義すると自動記憶域が使用されるため、プロシージャーが終了するとそのデータ構造も破棄されます。

(参考)STATIC キーワードを使用することにより、静的記憶域に関連付けることも可能です。

それでは、実際のプログラムを通して、ネイティブ・アクセスの基本を理解していきましょう。

icafe013 プログラム(ランダム検索)

最初は icafe004.sqlrpgle と同様の処理をネイティブ・アクセスで作成します。まずはファイルの定義と使用するデータ構造の定義を見ていきましょう。

DCL-F ステートメントを使用して TECSMP ファイルを定義しています。TECSMP ファイルは得意先コード(CSCSCD)がプライマリー・キーとして定義されており(第四回参照)、そのキーを使ってアクセスするために KEYED キーワードを指定しています。また、プログラム内ではレコードの読み取りしか行わないので USAGE キーワードで *INPUT を指定しています。

読み取ったレコードのフィールドの値を保存するためのデータ構造を DCL-DS で定義します。今までと異なるのは、extname キーワードの2番目のパラメータに *INPUT を指定している点です。指定できるパラメータの値は以下の通りです。

「入力可能フィールド」と「出力可能フィールド」という表現に違和感を覚える方がいらっしゃるかもしれません。データベースのフィールドは入力も出力もできるのが当たり前ですよね。しかし、データベース以外のファイルには「入力しかできないフィールド」や「出力しかできないフィールド」を定義できるものがあります。データ構造内に含めるフィールドをそういった視点で限定したい場合のオプションと考えてください。今回は読み取るだけなので *INPUT を指定していますが、*ALL も指定可能です。

それでは実際のレコードを読み取るステートメントを解説しましょう。

指定したキーの値を持つレコードを直接検索することをランダム検索といい、CHAIN ステートメントを使用します。

検索引数はリテラルでも変数でも構いませんが、ファイルが持つキー・フィールドと同じ属性でなければなりません。サンプル・コードでは、得意先コードが ‘01010’ のレコードを直接読み取り、すべてのフィールドの値を customer データ構造にセットしています。

SQL では、fetch の直後で読めたかどうかを判定するために SQLSTATE フィールドを使用しました。CHAIN 命令でも同様に、読めたかどうかの判断をしなければなりません。CHAIN 命令ではそのファイルの直前の読み取りにより該当のレコードが見つかったかどうかを判断するために %found 関数を使用します。

%found 関数の引数はファイル名です。直前の CHAIN でレコードが見つかったら、この関数は true(’1’)を返すので、IF ステートメントで読めたかどうかを判断することができます。今回のサンプルでは、読めたら得意先名を、読めなければ ‘Not Found’ を dsply で表示するようにしてみましょう。では icafe013.rpgle のコード全体を確認しましょう。

それではこのソースを Orion で登録してコンパイルしてみましょう。今回は SQL を含んでいないので、ソースの拡張子は rpgle です。コンパイルも実習メニューのオプション 1 を使用してください。そのため、CTL-OPT で dftname キーワードを指定している点に注意してください。

実行するとジョブログに「荒川薬局」と表示されるはずです。うまくできましたか?

icafe014 プログラム(ランダム検索:LIKEREC)

icafe013 ではデータ構造の定義で extname キーワードを指定しましたが、別のキーワードを指定して定義することも可能なのでそれを紹介しましょう。

さきほどファイル自身がフィールドの定義を持っているという話をしました。ファイルにはこの各フィールドの定義の他に、フィールドで構成されるレコードの名称も定義されています。この名称のことをレコード様式名といいます。DCL-F ステートメントでファイルを定義すると、このレコード様式名も RPG プログラム内で使用することができます。

DCL-DS ステートメントでは、このレコード様式を参照するために likerec キーワードが使用可能です。今回参照している TECSMP のレコード様式名は TECSMPR なので、これを使用してデータ構造を定義するには以下のようなコーディングになります。

likerec キーワードを指定したデータ構造は QUALIFIED データ構造です。QUALIFIED キーワードを記述する必要はありません。サブフィールドの値を参照する際にはデータ構造名で修飾してください。

また、DCL-DS に対する END-DS も必要ないので注意しましょう。では、icafe013.rpgle のデータ構造定義部分を likerec を使用するように変更した icafe014.rpgle を作成して実行してみてください。

icafe015 プログラム(次のレコードの読み取り)

前回、SQL でカーソルを定義して fetch ステートメントで1件ずつレコードを取得するプログラムを作成しましたが、同様のプログラムをネイティブ・アクセスで作成してみましょう。

ネイティブ・アクセスの場合、サブ・プロシージャーが開始するとファイルが自動的にオープンされると説明しました。このオープンと SQL の「カーソル定義 + オープンステートメント」は同じと考えてください。つまり、ネイティブ・アクセスの場合は SQL のカーソル定義と同様の操作は必要ないということです。

SQL の fetch ステートメントに相当する読み取りステートメントは READ です。

READ も CHAIN と同様レコードを読んでデータ構造に値をセットするのは同じですが、CHAIN と異なり、検索引数が指定されていません。では、この命令によりどのレコードが読まれるのでしょうか?この処理で先程説明したポインターが重要な役割を果たします。以下の図で説明しましょう。

青い矢印がポインターだと仮定してください。まず、main プロシージャーが実行されると TECSMP がオープンされ、ポインターが最初のレコードの先頭に位置づけられます(①)。READ ステートメントは「次のレコードの読み取り」です。この「次」とは現在ポインターが位置づけられている「次」の意味です。最初の READ が実行されると①の次、つまり最初のレコードにポインターが移動して(②)そのレコードが読み取られます。その後また READ が実行されるとポインターが次のレコードに移動して(③)そのレコードが読み取られます。

ポインターが移動してレコードが読めたかどうかの判断は、%eof 関数を使用します。

%eof 関数の引数も %found 関数と同じファイル名です。ただし、この関数は「見つかったら」ではなく、「ファイルの終わりになったら(End Of File)」 true(’1’)を返すということに注意してください。ファイルの最後のレコードを読んだあとで、さらに READ を実行すると次のレコードが無いためポインターを移動することができずに true(’1’)を返すのです。read できた場合は %eof は false(’0’)を返すので、今回の if 文ではこの関数の戻り値が false の場合(not)に読めたときの処理を記述することになります。

ではコード全体を確認しましょう。

dou %eof(TECSMP) は、「TECSMP」の次のレコードが read できなくなるまで繰り返すという意味です。また、今回のサンプルでは likerec キーワードの2番目のパラメータは指定していませんが、この場合は省略値として *input が使用されます。

前回の icafe009.sqlrpgle と比べてみてください。ネイティブ・アクセスではカーソルの定義やオープン、クローズもなく読みやすいコードになっていると思います。このプログラムを icafe015.rpgle として登録し、コンパイルして実行してみましょう。

終わりに

いかがでしたか。SQL とは異なる DB2 for i へのネイティブ・アクセスのコーディングは少し難しいと感じられたかもしれません。しかし、このネイティブ・アクセスは、IBM i が AS/400 として発表された 30年前から(厳密にはその前から)、OS に組み込まれたデータベースに RPG からアクセスする方法として使われてきましたし、現在もほとんどのプログラムがこの方法で記述されています。この手法を理解することにより、既存のプログラム資産の理解も深まることでしょう。

今回は READ のところでポインターを紹介しましたが、次回はこのポインターの操作方法を使ったより具体的な読み取り操作を解説します。また、データの更新、削除および追加についても解説する予定です。

どうぞお楽しみに!

 

 

 

著者プロフィール


この記事のあとにはこちらの記事もおススメです。

関連キーワード

メールマガジン登録

注目キーワード

フリーワード検索

お探しの情報に関する単語を入力してください。

資料ダウンロード

iCafeではIBM iに関するさまざまな資料を掲載しています。お探しの資料をぜひさがしてみてくださいね。

アクセスランキング