09/16/2019 スーザン・ガントナー
初めて可変長フィールドについて記事を書いたのは2002に遡ります。以下のような多くの理由でこのトピックを再考するのに良いタイミングだと確信しました。
- テーブル定義を行うのに増々多くの人がSQLを利用するようになったので、それにつれて可変長フィールドが使われことが多くなりました。それらのテーブルがネイティブのI/O操作で使われるならば、RPGプログラマーはそれをどのように扱うかを知る必要があります。
- 最近、多くのプログラマーが他のプラットフォームからIBM iに移って来ていますが、彼らにとって可変長フィールドはどちらかと言えば標準であり、IBM iでも同様にそれを使うのは自然なことです。
- 可変長フィールドはRPGプログラマーに多くの恩恵を与えてくれます。なぜなら、ロジックが簡潔かつ明瞭になり、効率が向上し、あらゆる点で物事を「良く」するからです。
- 最後になりますが、だからと言って決してこれが一番価値が無いというわけではありません。過去の元になる記事を見直したところ、 演算仕様書を含めすべて固定形式のRPGであることに気付きました!*身震いがします*
RPGで可変長フィールドを定義する
初歩から始めましょう。可変長フィールドはRPGの内部定義かデータベース上で定義できます。データベースについては後回しにして、今はRPG内で可変長フィールドを定義し、使用する方法について見ることにします。
プログラム内の定義は単純です。最初の例は、VARYINGキーワードを使った古いD仕様書による定義です。新しいフリーフォーマット(これ以降の例はすべてフリーフォーマットを使います)では、データ型として単にVaracharを指定します。これら2つの定義は基本的に同じことを行います。
D VaryingField1 S 200a Varying Inz (固定形式での定義例) Dcl-S VaryingField2 VarChar(200) Inz; (フリーフォーマットでの定義例)
可変長フィールドは、指定した長さが最大サイズ(上記の例では200文字)である従来の文字フィールドとは異なります。どの時点でも、最新のフィールド長は、最小0バイトから最大200バイトまで変化します。
RPGはどのようにして最新のフィールド長を知るのでしょうか?可変長フィールドは、実は2つの部分から成っているのです。つまり、長さを保持する整数フィールドと、その後に続く実データそのものから成っています。それ故、可変長フィールドは常にその最大長よりも多くのバイト数を占めています。最大65,535文字の長さのフィールドの場合、整数フィールド長は2バイトになります。それより長いフィールドの場合、整数フィールド長は4バイトになります。
通常このことに気を遣う必要は決してありません。唯一の例外はデバッグでそうしたフィールドを見るときだけです。デバッガーは、通常他の文字フィールドと同様に可変長フィールドを表示します。そして、そこには後続の空白が表示されます。それらの空白が本当に可変長フィールドの一部であるかどうかを知りたければ、5250のデバッガーでそのフィールドを16進数表示させる(つまり、EVAL変数名:x というデバッグ・コマンドを使う)必要があります。そうすれば、最初の2(または4)バイトの内容を調べ、そのフィールドの実際の長さを確定することができます。
どちらの例でも、可変長フィールドの長さの初期値を0に初期化するためにINZキーワードが指定されていたことに注意してください。これは省略時の初期値が0バイトである独立フィールドについては必須ではありません。しかし、データ構造内のすべてのフィールドの省略時の初期化は空白で行われます。そして可変長フィールドの長さ部がブランクで初期化されると問題になる可能性があります。結果、すべての可変長フィールドに対して、常に明示的に初期化を指定する習慣ができます。
RPGで可変長フィールドを使うにあたり、可変長フィールドの最大の長所はCSV、 XML、JSONのような複合した文字列を非常に容易に作ることができる点にあります。
たとえば、配列monthlySalesという配列内のすべての要素をコンマで区切られた文字列に書き換える必要があると仮定しましょう。固定長フィールドを使う場合、ロジックを次の様に記述するかもしれません。
For i = 1 to %Elem(monthlySales); fixedString = %TrimR(fixedString) + %Char(monthlySales(i)); If i < %Elem(monthlySales); fixedString = %TrimR(fixedString) + ','; EndIf; Endfor;
この例の場合、新しい要素を追加するプロセスでは、新しいデータを追加する前に、現在の内容から後続の空白を先ず切り取る必要があります。しかし、当然RPGはそのフィールドを定義された長さ通りにするためにそれらの空白を埋め戻します。最後の要素以外のすべての要素について、データに追加される各要素の後ろにコンマを付けるために、この空白の切り取りと埋め戻し操作が繰り返されます。
この動作は文字列に追加される各要素に対して起こります。もしこのすべてが、とても非効率的だと思われるなら、その考えは絶対的に正しいものです。それは恐ろしく非効率であり、目標の文字列が大きくなればなるほど効率が悪くなります。
しかし、可変長の結果フィールドを使うことで、このロジックはとても単純なものになり、かつより重要なことに、著しく効率良く動作します。
For i = 1 to %Elem(monthlySales); varyingString += %Char(monthlySales(i)); If i < %Elem(monthlySales); varyingString += ','; Endif; EndFor;
この第二版では、RPGが常にフィールドの終わりの位置を知っているので、何も切り取る必要はありません。ですから、データを追加するときに、RPGは単に最後の文字の後ろに配列要素を置き、空白で埋め戻すことなく、フィールドの長さを然るべき値に更新するだけです。
この例では組み込み関数%Charを使ったので、後続の空白が含まれません。しかし、後続の空白があるかもしれない文字列の内容を追加していると仮定しましょう。で、それがどうしたというのでしょう?答えは、追加されるときに文字列から後続の空白を単に切り取るだけです。以下に例を示します。
varyingString += %TrimR(charField);
もう一つの一般的な使用法は、個別の名フィールドと姓フィールドから人名を作るというものです。伝統的な固定長フィールドに名前が格納されている場合、コードは次のようなものになるでしょう。
fullName = %TrimR(firstName) + ' ' + lastName;
他方、名前を可変長フィールドに格納した場合、次のようなもっと簡単な(そして間違いなくもっと直感的な)コードを使うことができるでしょう。
fullName = firstNameV + ' ' + lastNameV;
固定長文字フィールドを使う場合、有効な値がなければそれらを空白で埋めることに私たちは慣れています。可変長フィールドを使う場合、そのフィールドが実際に値をもたないように設定できます。RPGでこれを行うには2つの方法があります。
そのフィールドをヌル(2つの連続する単一引用符で表されます)に設定することができます。これにより、RPGはその実際の長さを0に設定します。
代案として、RPGの組込み関数%LENを使うことができます。通常、%LENは現在のフィールドの長さを決定するために使用するものと考えられています。たとえば、印刷行に収められだけの十分な余裕があるかどうかを知る必要がある場合です。しかし、%LENを可変長フィールドの長さを制御するために使うこともできます。結果、次の2行のコードは同じこと、すなわちmyVaryingFieldというフィールドをヌルに設定する働きをきます。
myVaryingField = ''; %Len(myVaryingField) = 0;
パラメーターとしての可変長フィールド
最後になりますが、だからと言ってこれが一番価値が無いということではありませんが、可変長フィールドの使い方として多分一番無視されていることについて述べなければなりません。すなわち、呼び出し元が固定長フィールドを渡す必要がある場合でさえ、パラメーターとして可変長フィールドを使うことで、呼び出されるプログラムおよびプロシージャの多用途性が増すという能力についてです。
長さ1から200文字の文字列を受け入れる、ユーティリティー・ルーチンを書きたいと仮定しましょう。このルーチンの呼び出し元は固定長フィールドを渡したいと思うかもしれません。パラメーターが固定長フィールドだと定義された場合、この文字列を正しく処理するために、実際の文字列長を指定するための追加パラメーターを渡すよう、呼び出し元に対して要求する必要もあるでしょう。このアプローチはQCMDEXCが動作する方法だと恐らくお気付きだと思います。(QCMDEXCへの第1パラメーターはコマンド文字列で、第2パラメーターはその長さです。)QCMDEXCには何もできませんが、自分が作るプログラムは改善できます。
固定長の入力パラメーター・フィールドを可変長のそれに置き換えると、もはや別個のパラメーターとして長さを要求する必要はありません。その代わりに、呼び出されたルーチンは、単純に渡されたパラメーター・フィールドの現在の長さを調べることができます。単にパラメーター定義にCONSTまたはVALUEを追加する必要があるだけで、後はコンパイラーが面倒を見てくれます。実際、RPGは渡されたフィールドの長さを、渡した固定長フィールドの実際の長さにセットし、呼び出されたルーチンは元々のパラメーターの長さを規定するために%LENを使うことができます。
下記の短いプログラムはこの考えを例示しています。最初に20文字のフィールドを使ってルーチンを呼び出します。次に、40文字のフィールドを使い再度ルーチンを呼び出します。RPGは可変長パラメーターの長さを然るべき値に設定します。VaryParmのロジックは、最初の呼び出しではパラメーターは長さ20文字、次の呼び出しでは長さ40文字であると正しく報告することで、これを実証します。
Dcl-S firstName Char(20); Dcl-S fullName Char(40); VaryParm(firstName); VaryParm(fullName); Dcl-Proc VaryParm; Dcl-Pi *N; // Const は固定長文字または可変長文字を許す。 inputString Varchar(80) Const; End-Pi; Dcl-S parmLength Int(5); parmLength = %Len(inputString); Dsply ('Processing a ' + %char(parmLength) + ' char parameter'); End-Proc;
ここまでの説明で、可変長フィールドがあなたのRPG道具箱に追加する価値のあるものだと、その真価を認めてもらえることを願います。では、データベースでの可変長フィールドの定義はどうでしょう。
データベースで可変長フィールドを定義する
可変長フィールドはDDSまたはDDLを使って定義できます。まず、伝統的なDDSによる方法を見てみましょう。例として、次に示すようにVARLENキーワードを使ったDDSでPRDUCTLVというテーブルを定義しました。
R PRODUCTR PRODCODE 5A DESCRIPT 32A VARLEN(32) FULLDESCR 2000A VARLEN(200)
あなたが、最近増加中のDDLを使ってテーブルを定義するIBM i利用組織に属しているなら、VARCHARというデータ型を使って同じことができます。テーブル定義は最終的に次のようなものになります。
CREATE TABLE PARTNER400.PRODUCTVL ( PRODCODE CHAR(5) NOT NULL, DESCRIPT VARCHAR(32) ALLOCATE(32) NOT NULL. FULLDESCR VARCHAR(2000) ALLOCATE(200) NOT NULL ) RCDFMT PRODUCTR;
この例では、テーブルにDESCRIPTとFULLDESCRという2つの可変長フィールドが含まれています。それらは、それぞれ長さ32文字ならびに2,000文字です。
定義中のALLOCATE(nnn)というキーワードの目的が何であるか訝しく思う方がおられるかもしれません。可変長フィールドは、ある状況下でテーブル用のディスク容量を削減する可能性があります。
FULLDESCRフィールドは最大2,000文字である可能性があるものの、一般的な記述は100文字から200文字程度の長さに過ぎないと仮定しましょう。これは、1,000種の製品情報を格納しているデータベースで、ディスク容量を凡そ1,800,000バイト無駄にする可能性があることを意味します。(つまり、1レコード当たり約1,800バイトを無駄にするレコードが1,000レコードです。)
定義に長さを含めることで、データベースにその記述に対する各レコードの固定長部分に200文字だけを確保するよう指示しています。その限界を超える文字は、レコードの補助部として知られる特別な領域に格納されます。DDSのVARLENキーワードの後ろの括弧で括られた数字は、ALLOCATEと同じ目的を果たします。
割り当て長を指定しなかったらどうなるでしょう?その場合、これらのフィールドに対するすべてのデータは常に補助領域に置かれます。
FULLDESC内のほとんどの値が200文字またはそれ以下であるという仮定が正しければ、大半のレコードに対するFULLDESCの記述は僅か202バイトを使うだけです。(つまり、割り振られた200バイトに、実際の長さを格納するための2バイトを加えたもの。)200文字と言う限界を超えたものだけが追加の空き領域を使用します。このすべてはプログラマーには透過的に行われます。
大丈夫ですね?急いで長い文字フィールドをすべて可変長フィールドに再定義しようとする前に、他にいくつかの考慮事項があります。ディスク領域を節約するこの能力は完全にタダというわけではありません。
第1に、補助領域のデータを追跡するために、可変長フィールドを含むすべてのレコードに25バイトが追加されます。結果、可変長フィールドがあまり使用されない場合を除き、(たとえば)50文字フィールドを可変長として定義することで空き領域を本当に節約することはできません。
第2に、補助領域にデータを格納したすべてのレコードは、そのデータを読み込むために内部的に2回目の入出力操作が必要になります。(つまり、事実上、各レコードに対して2回の読み込み操作が行われます。)これは明らかに処理速度を低下させます。
これらの「コスト」が理由で、割り振り領域の大きさを決定するとき、私たちは通常85対15の規則を使います。これは、全レコードの85%が割り振られた長さに収まり、15%だけがそれを超えるに過ぎず、それ故補助領域を使用するのは全レコードの15%だけであることを確実にすることを狙うという意味です。あなたのテーブルに対する適切な割合を決定するのはあなた次第です。
この記事でいくばくかの考える材料が提供できたこと、そして可変長フィールドというこの多能なツールの使い方をもっと多く見つけてもらえることを望みます。