EXECUTEおよびOPENでのSUBSET
IBM i 7.3テクノロジー リフレッシュ8 / IBM i 7.4テクノロジー リフレッシュ2でのDB2 for iの機能強化によって、動的SQL(RPGに組み込み)は大幅に使いやすくなりました。その中に、「USING SUBSET on EXECUTE and OPEN with Extended indicators(拡張標識を使用したEXECUTEおよびOPENでのUSING SUBSET)」というタイトルの機能強化があります。この機能強化は、最初に2015年9月の記事で取り上げたある問題に対する簡単かつ効果的な解決法をもたらします(「 初めて見るSQL記述子」を参照)。
対処しようとしている「問題」は、動的SQLステートメントで使用される可変数のホスト変数をどのように処理したらよいかということです。前述の記事では、例として以下のSQLステートメントを使用しました。
select workDept, empno, firstname, lastname from employee
where workDept = ? and
hireDate >= ? and
birthDate >= ?
order by workDept, empno
難しい点は、部門コード、雇用日、生年月日の比較値が提供されている場合と、されていない場合があるということでした。したがって、where節(組み込む必要がある場合)は3つの列の組み合わせと対照している可能性がありました。
解決策となり得るコーディングの手法は3通りありましたが、それぞれに注意すべき点もありました。
- 静的SQLを使用する。この手法では、(ほとんどの場合)照会エンジンは不要かつ非効率な作業を実行することになってしまいます。
- 比較値を含んだWHERE節を構築する。この手法の場合、結果として、入り組んだ、エラーが発生しやすい文字列処理となってしまい、SQLインジェクション攻撃の可能性も(少し)あります。
- SQL記述子を使用する。この手法は(前の2つの手法よりはマシですが)、直観的ではなく、特にホスト変数のデータ タイプの処理では、細部まで多大な注意を要することになります。
これら3つの解決法のコード例は、「 初めて見るSQL記述子 」の記事に記載されています。新たなSUBSETオプションは、これらに比べてはるかに巧妙な解決法をもたらします。
SUBSET
新たなSUBSETは、ホスト変数と関連付けられたSQL標識(RPGでは整数値)を利用します。列でNULL値を処理したことがある場合は、これらのSQL標識の1つを使用したことがあることでしょう。(ホスト変数に対する)SQL標識に-7の値を指定することは、変数にSQLIND_UNASSIGNEDの値を割り当てることと同じです。
以下は、動的SQLを使用して必要なSQLステートメントを生成し、新たなSUBSETオプションを使用してホスト変数から必要な値を割り当てるプログラムの例です。このプログラムは、生成されたステートメントにカーソルを定義し、複数行フェッチを使用して行を取得します。以下のコードのコールアウトを参照してください。
dcl-s getRows int(10) inz(%elem(data));
dcl-s gotRows int(10);
dcl-ds data qualified dim(500);
workDept char(3);
empno char(6);
firstName varchar(12);
lastName varchar(15);
end-ds;
(A) dcl-s getDept char(3);
dcl-s getHired date;
dcl-s getBirth date;
(B) dcl-s getDept_ind int(5);
dcl-s getHired_ind int(5);
dcl-s getBirth_ind int(5);
(C) dcl-s myStatement varchar(2000);
dcl-s myWhere varchar(100);
dcl-s pad_And varchar(5);
dcl-C ADD_AND ' and ';
dcl-C ADD_WHERE ' where ';
(D) dcl-C ADD_DEPT ' workDept = ? ';
dcl-C ADD_HIRE ' hiredate >= ? ';
dcl-C ADD_BIRTH ' birthdate >= ? ';
if (getDept <> *blanks);
(E) myWhere = ADD_DEPT;
pad_And = ADD_AND;
getDept_ind = 0;
else;
(F) getDept_ind = -7;
endIf;
if (getHired <> d'0001-01-01');
(G) myWhere += pad_And + ADD_HIRE;
pad_And = ADD_AND;
getHired_ind = 0;
else;
getHired_ind = -7;
endIf;
if (getBirth <> d'0001-01-01');
(G) myWhere += pad_And + ADD_BIRTH;
pad_And = ADD_AND;
getBirth_ind = 0;
else;
getBirth_ind = -7;
endIf;
if (myWhere <> '');
myWhere = ADD_WHERE + myWhere;
endIf;
myStatement = 'select workDept, empno, firstname, lastname ' +
' from employee ' +
myWhere +
' order by workDept, empno';
exec SQL
prepare D1 from :myStatement;
if SQLCode = 0;
exec SQL
declare C1 scroll cursor for D1;
exec SQL
(H) open C1 using SUBSET :getDept :getDept_ind,
:getHired :getHired_ind,
:getBirth :getBirth_ind;
exec SQL
fetch first from C1 for :getRows rows into :data;
gotRows = SQLErrd(3);
exec SQL
close C1;
endIf;
*inLR = *on;
(A) where節で必要な比較値を格納する変数。通常、パラメーターとして渡されます。
(B) OPENステートメントで使用されるホスト変数と関連付けられたSQL標識。
(C) 動的SQLステートメントの構築で使用される作業変数。
(D) 名前付き定数を使用して、WHERE節のコンポーネントを提供します。各コンポーネントには、必要なパラメーター マーカーがあります。
(E) 部門コードに対する比較がステートメントに含まれる場合は、WHERE節に比較を追加し、関連付けられた標識を0に設定します(値があることを示します)。
(F) 部門コードに対する比較がステートメントに含まれない場合は、関連付けられた標識を-7に設定します(変数が無視されることを示します)。
(G) 他の2つの比較値について同じプロセスを繰り返します。
(H) カーソルをオープンするときに、リストですべてのホスト変数を指定し、SUBSETをそのリストの前に置きます。ホスト変数には値があるか、あるいはSQLIND_UNASSIGNEDになります。SQLIND_UNASSIGNEDの変数は、割り当てで無視されます。注意すべき重要なことは、ホスト変数の名前の順序は、WHERE節で割り当てた順序と一致する必要があることです。
新たなSUBSET機能により、動的SQLステートメントの適切な構築(パラメーター マーカーを使用)が、はるかに簡単になります。その結果として、動的SQLの使いやすさは大きく向上します。