GeneXusのコントロールブレイク処理について

先日GeneXusで作成した処理の速度が遅く、パフォーマンス改善をして欲しいと言う依頼がありました。

そこでコントロールブレイク処理を使用して速度改善ができましたので、今回はコントロールブレイクを記事にしようと思います。

コントロールブレイク処理とは?

他の言語でブレイクと言うと、繰り返しの処理を強制的に終了させると言う意味ですが、GeneXusではそういった意味とは異なります。

GeneXusにおけるコントロールブレイク処理は、SQLでいうグルーピングのようなものです。(SQL分からない人でも大丈夫です)

イメージとしては特定のカラムをもとに集計したり、まとめたりすることです。

例として、下記のように売上データを売上日や得意先コードごとに金額を集計できたりします。

一覧や帳票で集計して出力することは多いと思いますので、こういった場合は是非コントロールブレイク処理を使って実装しましょう。

概要説明

コントロールブレイク処理を実装するにあたって、GeneXusではFor Eachコマンドを使用します。

そしてそのFor Eachでまとめたい項目をOrderに記述して並び替えをします。
まず売上日ごとの集計で説明します。
(分かりやすいように項目は日本語表記とします)

For Each 売上
    Order 売上日

EndFor

これで売上テーブルから売上日順でデータを取得できました。

さらにこのFor Eachの中に、入れ子で売上を見るFor Eachもう一つ書くことによりコントロールブレイク処理となります。

For Each 売上
    Order 売上日
    For Each 売上
    EndFor
EndFor

外側のFor Eachでまず1データ読んで、中の青いFor Eachでは外側で読んでいるデータと売上日が同一のデータで回ります。

その後、外側で読んでいるデータと売上日が同一のデータがなくなったら、また外側のFor Eachで次のデータを読みます。

つまり、売上日ごとに売上金額を合計したい場合は、下記のような記述になります。

For Each 売上
    Order 売上日
    &売上合計金額 = 0
    For Each 売上
        &売上合計金額 += 売上金額
    EndFor
EndFor

まず集計をする前に、集計する変数を初期化します(赤文字の部分)
その後内側のFor Eachの中で、売上合計金額の変数に売上金額を足します。

こうすることによって、売上日ごとの売上金額を算出することが可能です。

売上日ごとではなく、得意先コードごとでしたら下記の記述でOKです。

For Each 売上
    Order 得意先コード
    &売上合計金額 = 0
    For Each 売上
        &売上合計金額 += 売上金額
    EndFor
EndFor

このようにブレイクしたい項目を外側のFor EachのOrderで記述し、さらに入れ子でFor Eachを書けばブレイク処理が可能です。
また外側のOrderに複数項目を指定することも可能で、その場合は複数指定している項目がすべて同一のデータで内側のFor Eachが回ります。

ちなみにブレイクしたい条件が2段階ある場合は、For Eachを3つ書けば可能です。

例えば、売上日ごとの集計をして、さらに売上日、得意先コードごとの集計もしたい場合は下記のようになります。

For Each 売上
    Order 売上日
    &売上合計金額 = 0
    For Each 売上
        Order 得意先コード
        &売上日売上合計金額 += 売上金額
        &得意先売上合計金額 = 0
        For Each 売上
            &得意先売上合計金額 += 売上金額
        EndFor
    EndFor
EndFor

売上日が同一のデータは青いFor Eachが回り、さらに得意先コードも同一のデータは緑のFor Eachで回ります。

トランザクション作成&データ準備

ではGeneXusで実装してみる前に、トランザクションとデータを用意します。

今回ベースとなるトランザクションは上記でも説明しました通り、売上トランザクションです。

データはこんな感じで入れました。

コントロールブレイク実装

今回は画面で集計区分(なし/売上日/得意先コード)を選択したら、その集計区分で売上金額を集計して一覧表示する画面を作ろうと思います。
(集計区分がなしの場合は、集計せず全件表示します)

まず画面に置くSDTを作成します。
(売上トランザクションと同一項目)

次に一覧画面を作成します。

WebLayoutは下記のような感じです。
上に集計区分があって、下にSDTがあります。

ソースは下記をRefreshEventで呼べばOK。

あまり役に立たないと思いますが、一応コピペ用にソースを貼っておきます。

&UriTotalSDTs = New()
Do Case
	Case &SyuKbn = 0
		//集計区分「なし」の場合
		For Each Uri
			&UriTotalSDT = New()
			&UriTotalSDT.UriNo = UriNo
			&UriTotalSDT.UriDate = UriDate
			&UriTotalSDT.TokCode = TokCode
			&UriTotalSDT.TokName = TokName
			&UriTotalSDT.UriAmt = UriAmt
			&UriTotalSDTs.Add(&UriTotalSDT)
		EndFor
	Case &SyuKbn = 1
		//集計区分「売上日」の場合
		For Each Uri
			Order UriDate
			&UriTotalSDT = New()
			&UriTotalSDT.UriDate = UriDate
			For Each
				&UriTotalSDT.UriAmt += UriAmt
			EndFor
			&UriTotalSDTs.Add(&UriTotalSDT)
		EndFor
	Case &SyuKbn = 2
		//集計区分「得意先コード」の場合
		For Each Uri
			Order TokCode
			&UriTotalSDT = New()
			&UriTotalSDT.TokCode = TokCode
			&UriTotalSDT.TokName = TokName
			For Each
				&UriTotalSDT.UriAmt += UriAmt
			EndFor
			&UriTotalSDTs.Add(&UriTotalSDT)
		EndFor
EndCase

ちなみに実装してビルドするとナビゲーションが出ると思いますが、そのナビゲーションでもブレイク処理が実装されていることが確認できます。
LoopWhileと記載されているところが、売上日や得意先コードごとにLoopしている箇所になります。

もしブレイク処理を実装しているつもりでも、ナビゲーションにBreakの記載がない場合は、正しく実装できておりません。
またブレイク処理を記載したつもりがないのに、ナビゲーションにBreakが記載されている場合も正しくないのでプログラムを見直しましょう。

動作確認

集計区分が「なし」の場合

問題なく全件出力されてますね。

集計区分が「売上日」の場合

不要な項目も出てますが、売上日と売上金額は問題ありません。

集計区分が「得意先コード」の場合

こちらも不要な項目が出てますが、得意先と売上金額は問題ありません。

まとめ

今回は画面での実装としましたが、帳票の出力も同様にFor Eachを入れ子にして記載すれば可能です。

コントロールブレイク処理は大変便利でよく使う機能ですので、使いこなせるようになるべき機能の一つです。

GeneXusはデータを取ってくるFor EachでUniqueが使えて、For Eachの中で集計しようと思えばSum関数も使えます。
ただこの2つを合わせて集計しようとすると、内部的に複雑なSQLになっているのか、かなり速度が遅くなります。

なかなかFor Eachの中に同一テーブルを参照するFor Eachを書くという書き方に慣れないかもしれませんが、是非色々なパターンを記述して慣れていただければと思います。

おまけ

集計区分が売上日と得意先コードの場合に、不要な項目が出ていたのでそれぞれ非表示にしました。

集計区分が「売上日」の場合

集計区分が「得意先コード」の場合

Gridの項目を条件によって非表示にしたい場合は、下記のようにLoadEventで記述すればOKです。

以上となります。

Blog一覧へ