2018.06.28
Jon Paris著

正規化されていないデータを処理する

時として、正規化されていない古い表を効率的な方法で処理する解決策を見つけることが必要になることがあります。そうしたデータベースを設計し直し、正規化する余裕があったらよいのですが、現実はそううまくは行きません。特に、問題の表が、ファイル レイアウトを制御できないアプリケーション パッケージの一部である場合はなおさら大変です。

ここで「効率的」というのは、必ずしも処理速度のことを意味しているわけではなく、必要な操作を実行するのに必要となるコードの行数といった点や、結果として生じるコードがどれくらい理解しやすいか(保守しやすいか)という点からのものだということを指摘しておきます。

このトピックについて説明するときによく引用する典型的な例は、何らかの形式の月間売上データが、12個の連続したフィールドとしてレコードに保存されているといったケースです。たとえば、そのファイルには、製品コードに続いて1月の売上、2月の売上、… 12月の売上が含まれているのかもしれません。

この記事で使用されているコードは、ここからダウンロードできます。

そのようなレコードから年間の総売上を算出することが要件であるとしたら、それについてはRPGプログラムでどのようにしたらよいでしょうか。まずは、もちろん、フィールド単位で処理を行うことができます。すなわち、totalSales = JANSALES + FEBSALES + … DECSALES;です。

しかし、月間売上の値を配列(結局のところ、実際にそのようなものですが)として処理できるようにレコードを定義し直すことができるとしたら、簡単に、totalSales = %XFoot(salesByMonth)とすることができます。

さてどうでしょう、その方がずっと手際よいのではないでしょうか。さらに、もちろん月番号をインデックスとして使用することによって、個々の月を参照することもできます。はるかに柔軟です。

それでは、それはどのようにして行うのでしょうか。次のコード例を見れば分かる通り、本当に簡単です。

技術情報10

このようにすれば、この問題についてはきちんとした解決策が得られるわけですが、そのようなレガシーなファイルは、これよりも複雑であることがよくあります。このことを思い出したのは、そのような要件に毎日直面しているという、あるRPGプログラマーとメールをやり取りしていたときでした。彼のケースでは、彼が直面していたのは単純な1つのフィールドの「配列」ではなく、繰り返される一連のフィールドでした。また、後で説明しますが、彼の要件には、1つ「ややこしい点」も含まれていました。

昔は、アパレル向けアプリケーションで、このようなレコード レイアウトによく出くわしたものでした。1つのレコードに数多くのサイズに対する(たとえば)価格の情報が入っているというようなものです。したがって、この例では、そのようなファイルの簡略化版を使用しようと思います。

ファイルの基本的なレイアウトは、まずは製品コード、それに続けてサイズ、コスト、および販売価格のフィールドというものです。このテスト ファイルの場合、これら3つのフィールドは計4回繰り返されることになります。実際には、8~10回以上、それらが繰り返されるのを目にするケースもあります。今日では、できるものなら、このような表を設計しようとはまず思わないものですが ... 。

最も簡単なアプローチは、上で説明した同じ手法を使用して、フィールドの種類ごとに1つずつ、3つの別々のデータ構造を作成するやり方かもしれません。しかし、結果はやや不恰好になり、3つのフィールドのグループの間の関係が一見して明らかというわけでもありません。その代わりにグループ フィールドを用いて、どのようにすれば、同じことをもう少しきれいに実現できるのか説明して行こうと思います。

基本的な原理は同じで、次のように並べたい順番に列をリストして行くだけです。

技術情報11

違うのは、グループ フィールドprodDetailを定義する(A)のところからです。「グループ フィールド」をそうならしめるものは何なのでしょうか。それは、自ら長さまたは型の定義を持っているのではなく、それをオーバーレイするフィールドの連結された長さによって定義されるということです。この例では、prodSize、prodCost、およびsellPrフィールドを「グループ化」します。グループ フィールドは長さが指定されていなくても配列として宣言でき、これは重要なところなのですが、そのことにより、同様に3つの個々のフィールドそれぞれが配列として扱われることが可能になります。つまり、グループのどれでも(たとえばprodDetail(1))、または個々のフィールドのどれでも(たとえばprodCost(1))参照できるということです。prodDetailはPos(1)(すなわちデータ構造の先頭)から始まるものとして定義されているため、prodSize(1)はSIZE1にマッピングされ、sellPr(4)はSELLPR4にマッピングされます。

先に、私のメール相手の要件には1つ「ややこしい点」があると述べました。簡単に言うと、彼はレコード内のフィールドを、対応するフィールドへコピーする必要があったのですが、コピー先となるディスプレイ、プリント、およびディスク ファイルで、フィールドのサイズ、型、名前がそれぞれ異なっているという状況だったということです。たとえば、パック5,2であったフィールドが、別の表ではパック7,2であったり、ディスプレイおよびプリント ファイルではゾーン9,2であったりするわけです。それらのフィールドをDSに入れたとしても、サイズや型が違うせいで、1つのDSを別のDSへ簡単にコピーするというわけには行きませんでした。また、名前が違うために、EVAL-CORRを使用してデータをコピーすることも、もちろんできないわけです。

私のアプローチは、上に示したようなグループ フィールド配列を、ソースおよびターゲットの両方のレコード用に作成してから、入力配列のそれぞれを、対応するターゲットの配列に割り当てるだけでした。ターゲットとなる配列の定義(B)は次のようになります。

技術情報12

配列が定義されたら、入力レコードからターゲットへのデータのコピーは、次のようにコーディングするだけです。

技術情報13

RPGが、どのようなサイズおよび型の違いも処理し、たった3行のコードだけで、ターゲットへ正確に1つずつ効率的にフィールドをコピーします。

面白いことに、このアプローチは、私のメール相手が抱えていた別の問題の処理にも役立ちました。その問題というのは、ソース レコードが(たとえば)3回繰り返しのみのファイルからのものであったが、5回または6回繰り返しのファイルと同じレポート/ディスプレイでの表示が必要となることが時々あるというものでした。コピーされるフィールドの数は、エントリーの数が少ない方の配列によって決まるため、やはり、この問題もRPGが処理してくれることになります。

利点はどのような点でしょうか。

このアプローチの数多くの利点についてはすでに述べてきたつもりでしたが、以下にまとめてみます。

  • レコードのフィールド名がDSで使用されるため、レコードが読み取られると同時に配列にデータが入れられる。個々のフィールドの移動をコーディングする必要がない。
  • 一連のフィールドをコピーするのに必要となるコードはたった1行のみ。
  • どのようなデータ型およびサイズの違いも、RPGによって処理される
  • 配列のソートは、それらのコンポーネント フィールドのいずれでも行える。たとえば、SORTA prodCostは、3つのフィールドのグループを生産コスト順に並べる。
  • 関連のあるフィールドのグループは、グループ フィールド配列を介して参照できる。たとえばitemDetail(4)は、ITEMSIZE4、ITEMCOST4、およびITEMSELL4フィールドを参照する。

この手法を「試して」みたい方は、サンプル プログラムおよびデータをこちらからダウンロードできます。「解決策」を探している同じような面倒な問題はありませんか。あるいは、質問やコメントなどでも構いません。何かありましたらご連絡をいただければ幸いです。

Jon Parisは、System iプラットフォームのプログラミングに関して世界で最も知識が豊富なエキスパートの1人です。ずいぶん前からParisはSystem/38の経験を積んでいましたが、1987年、IBMのTorontoソフトウェア ラボでSystem/38およびSystem/36のCOBOLコンパイラーに取り組みました。1988年には、オリジナルAS/400のCOBOL/400コンパイラーの作成にも取り組んでおり、RPG IVおよびCODE/400開発ツールの主要開発者の1人でした。1998年にIBMを去り、自身の教育訓練会社を立ち上げ、今日まで、同じくSystem iプログラミングのエキスパートである妻のSusan Gantnerとともに仕事をしています。ParisとGantnerはPaul TuohyおよびSkip MarchesaniとともにSystem i Developerの共同創立者であり、新しいRPG&DB2 Summitカンファレンスを主催しています。

ページトップ

ボタン