カーソルとともにSELECT *を使用する
時折、RPGプログラムにおいて、SQLカーソルとともにSELECT *を使用することについて尋ねられることがあります。具体的に言えば、それはより良いアイデアなのか、悪いアイデアなのかということです。その問いに対する答えは、「場合による」であると私は思っています。カーソル宣言でSELECT*を使用することは、トラブルの元になることもあれば、ならないこともある、ということです。
土台を整える意味で、まず、簡単な例から始めましょう。1つの表(物理ファイル)を読み取り、各行(レコード)をプリントするRPGプログラムです。ほとんどのプログラムは複数の表からのデータを使用しますが、1つの表だけを読み取るプログラムも珍しいものではありません。1つの表だけを読み取るプログラムは、カーソルでのSELECT*の使用のサンプル プログラムにうってつけだと言えます。
以下は、従業員データの表のDDLです。

カーソルでSELECT *を使用するプログラムの2つのバージョンについて考えてみましょう。1つ目は静的SQLバージョンです。

このプログラムについて、指摘しておきたい点がいくつかあります。 第1に、employees表(プログラムが読み取る表)が、EmployeeDataデータ構造の外部定義を提供していることに着目してください。これは安心です。データ構造内のフィールドが、取り出されたデータを受け取るのに十分だということが分かっているからです。
第2に、Employees表がnullデータを含むことができる場合は、null標識配列を定義する必要があるでしょう。ほとんどのIBM iのショップがデータベースでnullを使用していないと思われるため、null値を処理するためのコードを追加して必要以上に例を複雑にすることはしません。
第3に、プログラムを単純にするために、表全体を処理するループは省略しました。1つの行だけを取り出すプログラムは、カーソルを必要としませんが、INTO節付きのSELECTは必要とします。
最後に、アサーションの目的は、SQLステートメントが失敗したときに通知してもらうことです。私は本稼働プログラムでこのようにアサーションを使用することはないでしょう。
以下は、動的SQLでの同じプログラムです。

新たな列を追加したら、どうなるでしょうか。

修正もコンパイルもなしで、プログラムは引き続き正常に動作するでしょうか。これが、場合によるところです。
- SQLは静的か、動的か。
- 列をどこに追加したか。
このケースでは、行(すなわち、レコード フォーマット)の最後に新たな列を追加しました。 静的バージョンは以前の通りに動作します。これは、SQLプリコンパイラーが列リストを拡張するためです。したがって . . .、

は、以下と同じになります。

プログラム オブジェクトは3つの列を選択するだけなので、もうひとつの列を表へ追加することはプログラムに影響を及ぼしません。プログラムは、再コンパイルを必要としません。
また、動的SQLバージョンも、引き続き正常にデータを取得しますが、FETCH操作はSQL状態を01503(ホスト変数の数が結果の値より少ない)に設定します。つまり、データ構造にemail列用の場所がないということです。これは警告であり、エラーではありません。プログラムは動作し、正常に最初の3つの列を取得します。
列をもうひとつ追加しましょう。ただし今度は、その行内に追加します。

やはり、静的SQLは引き続き正常に動作します。プリコンパイラーが列リストを拡張したためです。現在、表に5つの列があるとしても、静的バージョンは引き続き同じ3つの列を選択します。
しかし、動的SQLについては、事はそれほどうまく運びません。同じようにSQL状態値01503を受け取りますが、そのデータは正確でありません。電話番号はすべての従業員でゼロです。これはプログラムが、ゼロに初期化されて正しい値がまだ入っていない部署番号を取得してしまうためです。
プログラムは動作するものの、データは正しくなく、混乱するだけです。
解決策はと言えばもちろん、このプログラムと、employees表で動的カーソルを使用する他のすべてを再コンパイルすることです。結局のところ、週末をこの作業に充てるのが良いでしょう。解析時には、何ひとつ見落としがあってはなりません。
SELECT *がカーソル内でどのような動作になるのか、お分かりいただけたのではないでしょうか。そうしたところで、もっと良いやり方だと私には思える手法について説明しようと思います。
データベースがある程度正規化されている場合、ほとんどすべてのプログラムは複数の表からのデータを必要とします。先程のクエリーに部署名を追加してみましょう。第3正規形を維持するためには、部署番号がキー化されている表に部署名を置く必要があります。

両方の表からのデータを使用するには、joinが必要となります。そのようなjoinを多くのプログラムに置くことができますが、1つの場所にjoinを置くためのより良いアイデアとして、私はビューを提案します。

left joinを使用しても、このビューはnull値を返さないことに注意してください。coalesce関数があり得るすべてのnullを処理し、それらを *Invalid*というダミーの部署名に置き換えるからです。
このデータを必要とするすべてのプログラムは、このビューを使用することができます。すると、どうなるでしょうか。それらはSELECT *を使用することができます。1つの表だけの例がそうしたのと同じです。以下は、その静的SQLバージョンです。

1つの表だけのクエリーでそうしたように、私はデータ構造を記述するためにビューを使用しました。SELECT *は、ビューでリストされる同じ順序で値を取得するため、うまく機能します。
ビューの使用は、ビューを変更しない場合はさらにいっそう堅牢になります。クエリーにもうひとつ列が必要だとしましょう。ビューを変更する場合は、プログラムを再コンパイルしなければならないかもしれません。しかし、そうする代わりに、別のビューを作成するとすれば、再コンパイルする必要があるプログラムは、新たな列を必要とするプログラムだけです。新たなビューを使用するためにそれらのプログラムを変更することができますし、また、コード凍結なしでそうすることができます。
したがって、結論としては、外部記述データ構造とともにカーソル定義でSELECT *を使用すれば失敗の心配がないということのように思われます。さて、まだまだ終わりではありません。表に対して照会を行う場合、暗黙的な隠し列があると、作業を混乱させる元になります。これは暗黙的な隠し列が、データ構造に含まれ、SELECT *フィールド リストには含まれないためです。ほとんどではないにしても多くのショップが暗黙的な隠し列を使用していないため、このようなことが起こる可能性はごくわずかです。列はビューで非表示にできないため、ビューに対してSELECT*を実行すれば、この問題に遭遇することはありません。
表に対して照会を行うのがショップで慣習的になっているのだとしたら、表ではなくビューに対して照会を行うことのメリットについて一度検討してみるとよいと思います。