2014年3月20日木曜日

データプロバイダーを使いこなそう - 応用編その1 ページング

データプロバイダーを使って一覧画面(Grid)を作成すると、オブジェクト分割もでき、シンプルなコーディングもでき、工数も削減できていい事づくめのように思えます。

しかし、GridをSDTコレクションで実装する場合、データプロバイダーを使う・使わないに関係無く、検索結果のデータ(レコード)が大量にある場合はパフォーマンスが悪化する事になります。
これはDBから取り出したデータをAPサーバー上のプログラムが一旦SDTコレクションとしてオブジェクトを組み立てるのにコストが掛かるためです。

一方、ベーステーブルありのGridやSDTコレクションではないシンプルな変数を使ったGrid実装の場合は、そのコストがないためパフォーマンスは良いですが、そもそもデータプロバイダーを利用できません。

データプロバイダーを使ってもパフォーマンスを維持できる実装はできるのでしょうか?

データプロバイダーの記述(データプロバイダー言語)には、SKIPとCOUNTというオプションがあります。いわゆるページング処理のためのオプションです。
・SKIP : 検索結果のレコードセットに対して読み飛ばす件数を指定
・COUNT : 取得する件数を指定

例)
SDT_Customer [SKIP=100] [COUNT=10]
{
CustomerId
CustomerName
CustomerAddress
...
この例では、最初の100件を読み飛ばして、101件目から10件を取得します。
もちろん値をハードコードするのではなく、変数を指定する事も可能です。

Evolution1ではクライアント側(APサーバー側)でページング処理を実装していましたが、Evolution2ではサーバー側(DB側=SQL文)で実装されるようになりましたので、パフォーマンスが大幅に向上しています。

それでは実際に見ていきましょう。今まで作成したものに修正を加えていきます。

1.データプロバイダーの修正


SKIP/COUNTオプション指定用の変数を追加します

&Skip/&Countを受け取るためにParmルールに追加します

SouceにSKIP/COUNTオプションを追加します

以上でデータプロバイダー側の修正は完了です。いたって簡単ですね。
一方、Webパネル側では独自にページングのロジックを実装する必要があるので一工夫必要です。

2.レコードカウント用データプロバイダーの作成

ページングロジックの実装にあたって必要なのは検索条件によって抽出された全体の件数です。しかし、データプロバイダー側でページング(SKIP/COUNT)指定を行うと、SDTコレクションとして取得するのはCOUNT指定での件数のみになってしまい、全体の件数が取得できません。

全体の件数を取得するには別途カウント処理が必要になります。そこでレコードカウントする為のデータプロバイダーを新規に作成します。(Webパネルのイベントでfor eachを使ってカウントを実装する事も可能ですが、今回はデータプロバイダーで実装します)

2-1.レコードカウント用SDTの作成

レコード数を保持するだけのSDTです


2-2.レコードカウント用データプロバイダーの作成

検索条件の変数を定義します

Parmルールも同様です


Sourceセクションについては若干説明が必要です。カウント処理としてはローカル式の「Count()」が思い浮かびますが、今回の様に「When not &変数.IsEmpty()」を含んだWhereはローカル式のコンディションには記述できません。

For eachで考えるとカウントは以下の様な記述になります。
For each
where CustomerName like '%'+&CustomerName
when not &CustomerName.IsEmpty()
where CustomerAddress like '%'+&CustomerAddress
when not &CustomerAddress.IsEmpty()

&Count += 1

Endfor
コード上ではカーソルをループして1件づつカウントしていますが、ビルド時にオプティマイズ(最適化)が働いてSQL文の「SELECT Count(*) 〜」に展開されます



データプロバイダー言語としては計算処理も記述できますが、OUTPUT用のSDTのメンバーは代入式の右辺には記述できません。(記述可能なのは項目属性と変数、リテラル)

例えば以下の様な記述は
SDT_RecordCount
where CustomerName like '%'+&CustomerName
when not &CustomerName.IsEmpty()
where CustomerAddress like '%'+&CustomerAddress
when not &CustomerAddress.IsEmpty()
{
Count = Count + 1
}
赤字の部分の「Count」は項目属性としてGeneXusが認識してしまうため実装不可能です。計算処理には変数を使わなければいけません。



次のケースはどうでしょうか?

SDT_RecordCount
where CustomerName like '%'+&CustomerName
when not &CustomerName.IsEmpty()
where CustomerAddress like '%'+&CustomerAddress
when not &CustomerAddress.IsEmpty()
{
&Count&Count + 1
Count = &Count
}
この例は計算処理には変数を使用し、SDTのメンバー「Count」に計算結果をセットしています。この記述でも結果は正しく取得できますが、ビルド時にオプティマイズされずロジック上でのループ+カウント処理が生成されてしまいます。

オプティマイズがされていない


そこで、OUTPUTフォーマットとカウントのオプティマイズの双方を満たす記述が以下になります。
SDT_RecordCount
{
DUMMY [NOOUTPUT]
where CustomerName like '%'+&CustomerName
when not &CustomerName.IsEmpty()
where CustomerAddress like '%'+&CustomerAddress
when not &CustomerAddress.IsEmpty()
{
&Count = &Count+1
}
Count = &Count
}

赤色がデータプロバイダーとしてのOUTPUTフォーマットの定義部分、青色がカウントの定義部分です。

書き方としては、ネスト(入れ子)For eachコマンドのイメージに近いです。データベースへのセレクト処理(本来のfor eachコマンド)は内側の{}(青色)の部分になります。

但し、データプロバイダー言語では { } をネスト(入れ子)すると、OUTPUT用のSDTもネストしたレベルの構造が必要となりますが、今回のSDT構造では1つのレベルが想定で2つのレベル構造をOUTPUTしたくありません。

こういったケースでは、[NOOUTPUT]オプションを指定する事で該当するレベルが出力されなくなります。(今回の例ではDUMMYという名前で記述している部分です)

オプティマイズされてCount(*)になっている


記事が長くなってしまったのでエントリーを分割します。次回はWebパネル側の修正です。


0 件のコメント:

コメントを投稿