こんにちは、あんこ先生です。
PowerAppsでもVBAのForLoop文のように繰り返し処理をさせたい場面がありますよね。
たとえば、データソースをまとめて更新したり、データを計算させたりといった処理です。
ForLoop文ほど万能ではありませんが、PowerAppsでもForAll関数を使うことで実現できます。
Twitterのお悩み相談でも、ForAll関数を使えば解決できるものをよく見かけます。
でもちょっとクセの強い関数なので簡単には扱えませんよね。
そこで、今回は「ForAll関数ってよくわからない」、「まとめてデータを処理させる方法はないの?」といった初心者の方を対象に、ForAll関数の概要と便利な使い方を紹介します。
事例をふんだんに取り入れているので、この記事を読めばForAll関数を使いこなせるようになりますよ。
繰り返し処理ならコレ!ForAll関数
ForAll関数とは
ForAll( データソース , 繰り返す処理)
ForAll関数は、テーブルに含まれるすべてのレコードに対して記述した処理を繰り返し行います。
繰り返す処理は1行目から最終行まで1つずつレコードの数だけ実行されます。
1周目は1行目を、2周目は2行目といった感じに、参照できるレコードが変化します。
図の例で説明すると、1周目の商品名はいちごで、2周目はりんごになります。
用途として、たとえば、時刻用の0-23までの数値テーブルを作成する、データソースをまとめて更新するなど、アイデア次第で便利な使い方がたくさんあります。
Sequence関数を使用すれば、意図した回数に基づいて繰り返し処理させることもできます。
また、戻り値として繰り返し処理の結果をまとめたテーブルを返します。
なお、ForAll関数は委任できないため、データソース欄はFilter関数などで絞り込まないと意図しない結果になることがあります。
レコードの数だけ参照と処理を繰り返す
ForAll関数は指定したテーブルに含まれるレコードをひとつずつ参照し、処理していきます。
図のように、9レコードあるテーブルを指定した場合、9回参照と処理を繰り返します。
Filter関数等との組み合わせ
指定したテーブルに対してフィルターをかけた場合、該当するレコードのみひとつずつ参照し、処理していきます。
図のように、9レコード中5レコードまで絞り込んだ場合、5回参照と処理を繰り返します。
除外された4レコードは参照も処理もされません。
ForAll関数の処理フロー
実際にForAll関数はどのような処理を行っているのか、ドロップダウンに向こう7日間の日付テーブルを割り当る例で解説します。
処理フローは次の通りです。
①データソース欄にはSequence(7)を指定します。
これで1-7までの連続した値を格納したテーブルが作成されます。
このテーブルは7レコード存在するため、以降7回処理を繰り返します。
②繰り返す処理欄にはDateAdd関数を設け、本日の日付からx日進めた値を返させます。
x日の部分はThisRecord.Value(Sequence(7)の中身)を指定します。
項目名の前にThisRecordをつけると、その数式内でもっとも深い入れ子のレコードがスコープされるよ。詳しくは次の項目で解説するよ。
今回のケースでは、1周目はSequence(7)の1レコード目に格納された値1となり、処理を繰り返すたびに7まで1ずつ増加します。
③最後まで繰り返すと、ForAll関数はその結果をテーブルとして返します。
ドロップダウンのItemsにこのForAll関数を記述することで、この戻り値が設定されます。
従って、本日の日付から向こう7日間の日付テーブルが戻り値として返されるわけです。
※戻り値はその数式内でのみ存在するため、コントロールのItemsプロパティなどに設定するか、コレクションに落とすのが一般的です。
データソースの値参照について
PowerAppsは異なるソースで同名の項目を持つ場合、項目名のみの指定ではどのソースか判別できません。
次のコードは、コレクションに同じ名前のValueを複写しようとしています。
ForAll(Sequence(7),
Collect(Collection,
{Value:Value}
)
)
この場合、指定されたValueがどちらのソースから持ってくればよいのか特定できず意図した処理が行えません。
この曖昧性を回避するため、ThisRecordを使用します。
次のコードでは、複写元の項目名にThisRecordがついています。
ForAll(Sequence(7),
Collect(Collection,
{Value:ThisRecord.Value}
)
)
ThisRecordを使用することで、データソース欄に指定したテーブル(Sequence(7))がスコープされます。
これでそれぞれのValueが別物だと認識され、正しく処理できます。
ForAll関数を使う上での注意事項
- ForAll関数は委任できません。
指定するテーブルが委任できないレコード数を超過する場合は、正しい結果が得られません。 - 最初に指定したテーブルに対して、必ず何かしないといけないわけではありません。
たとえば、社員数分の処理を繰り返す目的で、社員テーブルを指定することもできます。 - テーブルはFilter関数などで絞り込むことが可能です。
たとえば、年齢が30歳以下の社員レコードを抽出すれば、それらに対してなんらかの処理をさせることができます。 - もちろん、経過日数や文字数などテーブル以外の要素でも繰り返し処理させることも可能です。
そのままでは指定できないため、Sequence関数を経由してテーブル化させます。
ForAll関数の活用事例
ここからは、ForAll関数の使い方をケース別にみていきましょう。
カレンダーの作成
ForAll関数の戻り値を利用したカレンダーを作成します。
手順はかんたん、ドロップダウンのItemsプロパティに下記コードを記述するだけです。
//今日を基準に前後3日間の選択肢を作成
ForAll(Sequence(7,-3),
DateAdd(Today(),ThisRecord.Value)
)
//当月1日から当日までの選択肢を作成
ForAll(Sequence(DateDiff(Date(Year(Today()),Month(Today()),1),Today()+1)),
DateAdd(Date(Year(Today()),Month(Today()),1),ThisRecord.Value-1)
)
//明日から当月末までの選択肢を作成
ForAll(Sequence(DateDiff(Today(),Date(Year(DateAdd(Today(),1,Months)),Month(DateAdd(Today(),1,Months)),1)-1)),
DateAdd(Today(),ThisRecord.Value)
)
注意点として、Sequence関数の第1プロパティのみを指定した場合は1から開始します。
このままでは翌日からのカレンダーとなるため、本日の日付を含める場合は下記どちらかの対応が必要です。
- Sequence関数の第2プロパティ(開始値)に0を指定する。
- DateAdd関数のThisRecode.Valueから1を引く。
計算結果列の追加
レコードごとに計算した結果の列を追加して、ギャラリーに表示させます。
はじめに図のような社員テーブルと賞与テーブルを準備します。
①計算した列の追加
年齢に一律10000を乗じた値を支給するとします。
あからさまな年功序列ですが、気にせずその値を支給列に格納します。
なお、ギャラリーに値を表示させるだけなら、わざわざコレクションに落とし込む必要はありません。
ギャラリーのItemsプロパティに次のコードを記述します。
ForAll(社員テーブル,
{
名前:ThisRecord.名前,
支給:ThisRecord.年齢*10000
}
)
ちなみにAddColumns関数で同じ処理を行うと次の記述になります。
こちらの方がスマートですね。
AddColumns(社員テーブル,"支給",年齢*10000)
②外部テーブルを参照した列の追加
次に、年齢をキーに賞与テーブルから支給額を抜き取り、その値を支給列に格納します。
まず、ギャラリーのItemsプロパティに次のコードを記述します。
ForAll(社員テーブル,
{
名前:ThisRecord.名前,
支給:LookUp(賞与テーブル,年齢=ThisRecord.年齢,支給額)
}
)
図のように全員が同じ支給額になっています。
これは下図のように、LookUp関数内にあるThisRecodeの参照先がより深い入れ子の賞与テーブルに置き換わったためです。
なお、名前列の方は入れ子構造がかわっていないため、今まで通り社員テーブルから持ってこれます。
参照先の問題を解決するために、As演算子で社員テーブルに社員という名付けをします。
これで下図のとおり明示的にテーブルを参照できるようになります。
ただし、名付けしたテーブルはThisRecordで参照できなくなるといった弊害もあります。
そのため、ThisRecord.名前も社員.Valueに書き換える必要があります。
それらを踏まえて、ギャラリーのItemsプロパティに次のコードを記述します。
ForAll(社員テーブル As 社員,
{
名前:社員.名前,
支給:LookUp(賞与テーブル,年齢=社員.年齢,支給額)
}
)
これで参照問題が解決し、図のように正しく表示されたと思います。
ちなみにAddColumns関数で同じ処理を行うと次の記述になります。
こちらもAs演算子を使う必要がありますが、かなりスマートですね。
AddColumns(社員テーブル As 社員,
"支給",LookUp(賞与テーブル,年齢=社員.年齢,支給額)
)
連番付与(インデックス)
今度は、ForAll関数を使ってインデックスを付与します。
繰り返し処理によって、徐々にできあがるコレクションのレコード数を利用したものです。
CountRows(コレクション名)とすれば、0から始まり処理を繰り返すたびに1ずつ増加します。
それをインデックスとして連番列に格納します。
では、適当なボタンに次のコードを記述し、実行させます。
その後、できあがったコレクションをギャラリーに表示させます。
//ボタンのOnSelectでコレクションの作成
Clear(_ボーナス);
ForAll(社員テーブル As 社員,
Collect(_ボーナス,
{
連番:CountRows(_ボーナス)+1,
名前:社員.名前,
支給:LookUp(賞与テーブル,年齢=社員.年齢,支給額)
}
)
)
//ギャラリーのItems
_ボーナス
連番を付与することで、ギャラリーに表示したレコードを1行ずつ色付けすることもできます。
やり方は適当な図形に色をつけて最背面に設置します。
そして次のコードをVisibleプロパティに記述します。
Mod(ThisItem.連番,2)
Mod関数を使って連番を2で割ります。
その余りが1だったら表示、0だったら非表示となるので交互に色が変わります。
データの一括更新と条件分岐
更新用データを基に、データソースをまとめて更新することもできます。
また、ForAll関数の処理内で条件分岐もできます。
たとえば、コレクションレコードを追加する際、未登録商品なら追加、既存商品なら修正するといったことができます。
これは処理内にIf関数またはSwitch関数を使用すれば実現できます。
それをインデックスとして連番列に格納します。
では、適当なボタンに次のコードを記述し、実行させます。
下記例は、当日出荷データコレクションを基に、出荷データにまとめて登録します。
その際、出荷データに存在しない商品コードなら登録、存在するなら既存量に+1します。
ForAll(当日分出荷データ,
If(IsBlank(LookUp(出荷データ,Code=ThisItem.ProductCode)),
//この商品のレコードがなければ数量1で作成
Collect(出荷データ,
{
Code:ThisItem.ProductCode,
Value:1
}
),
//この商品のレコードがあれば+1
Patch(出荷データ,LookUp(出荷データ,Code=ThisItem.ProductCode),
{
Value:LookUp(出荷データ,Code=ThisItem.ProductCode).Value+1
}
)
)
)
ForAll関数のまとめ
今回は初心者の方を対象に、ForAll関数の概要と便利な使い方を紹介しました。
具体的なケースを画像とコード付きで解説したため、すぐに使えると思います。
紹介した事例以外にも、文字列を1文字ずつ分割したり、総当たりで該当データを探し出すといったこともできます。
使い方次第で大抵のことはできてしまう関数ですが、別関数を使った方がスマートな場合も多々あります。
うまく使い分けてみましょう。
あんこ先生にはいつも分かりやすく詳しく解説してくださってありがとうございます。
毎日とても助けていただいています。
お願いがあります。
「SharePointの参照列の値をPatchで登録する方法」を詳しく解説いただけると嬉しいです。
いろいろ検索しても情報が乏しくて、また私には難度が高くて困っています。
よろしくお願いいたします。
いつもお世話なっております。
参照列はレコード型なのでパッチするときもレコードで与えてやらないといけません。
その際、すべての列名を指定しないと怒られるので一度データテーブルに展開して何が含まれているか可視化することをオススメします。
あんこ先生、教えて下さって有難うございます!
試行錯誤してみました、私にはうまくできませんでした(T_T)
もう少しだけ質問よろしいでしょうか。
「すべての列名を指定しないと怒られるので」というのは、M_データの列名すべて ということですか?それとも、T_データの列名すべて ということでしょうか?
T_データの列は30列ほどあるのですが、その場合は参照する方法は合理的ではないのでしょうか。
他リストを参照しているならReferenceの中身であるIdとValueのみで行けると思います。
注意事項として、リストの設定で「複数の値を許可」にしていない場合はレコード、している場合はテーブルでパッチしないといけません。
ありがとうございます。試行錯誤やってみます!!
[…] 今回の記事では、Patch関数、First関数、そして最後にForAll関数…という手順を踏むようにしましょう(以前、理解の助けになった七草あんこ先生の記事もご参考ください:https://anko7793.com/2022/04/1182/) […]