Power Apps キャンバスアプリで SharePoint リストをページング表示しようとして Skip(SharePointList, N) を書いてみたものの、「なぜか全件返ってきてしまう」「ページ送りにならない」と悩んでいる方向けに、原因の整理と実務的な回避策をまとめます。
SharePoint リストに対する Skip が効かず全件が返る問題とは?
よくある症状
キャンバスアプリで SharePoint リストをギャラリーに表示し、次のようなコードを書いたとします。
// 2ページ目を取りたいつもりの例
Skip(
SharePointList,
50 // 先頭 50 件を飛ばす想定
)
ところが実際には、
- ギャラリーに 全レコード が表示されてしまう(1ページ目と同じ)
- ページ番号を変えても、表示内容が変わらない
- データが多いリストほど、挙動が怪しくなる(テスト用の小さいリストではうまく見える)
といった現象が起こります。さらに悪いことに、アプリによっては 一部のデータが silently に欠落 しているにもかかわらず、ユーザー側からは気付きにくいケースもあります。
| 書いたつもりの式 | 期待する動作 | 実際に起こりがちな動作 |
|---|---|---|
Skip(SharePointList, 50) | 51 件目以降だけを返す | 最初の 500 / 2,000 件の中で 50 件だけ飛ばし、残りは全部返す |
Skip(FilteredList, 100) | 条件に合うレコードから 100 件分だけ飛ばす | フィルター結果の一部しか取得されておらず、実質ページングになっていない |
なぜこのようなことが起きるのかの鍵が、「委任(Delegation)」と SharePoint コネクタの制約です。
原因:委任と SharePoint コネクタの制約
Power Apps の委任とは何か
Power Apps は、大量データに対してすべての処理をクライアント側(ブラウザやモバイル端末)で行うと性能が出ません。そこで、
- フィルターや並び替えなど、データソース側でできる処理はなるべくサーバー側で実行してもらう
- Power Apps 自身は「結果だけ」を受け取る
という仕組みを持っています。これを 委任(Delegation) と呼びます。
ところが、すべての関数・演算子が委任できるわけではありません。委任できない関数が式のどこか 1 か所でも混ざると、その式全体が非委任となり、Power Apps は次のような動作に切り替わります。
- SharePoint などのデータソースから、先頭 500 件(設定で最大 2,000 件)だけを取得
- その取得済みレコードに対して、残りの処理(Skip, FirstN, LastN など)をクライアント側で実行
つまり、データソースに 10 万件あっても、Power Apps が見ているのは「先頭 500~2,000 件だけ」です。それ以外のレコードは そもそも式の対象になっていません。
Skip / FirstN / LastN は非委任
公式ドキュメントでは First, FirstN, Last, LastN がデータソースに対して委任不可であることが明示されています。
また、Microsoft Q&A や GitHub Issue でも、Skip() を SharePoint リストに対して使うと正しくスキップされず、非委任の制限により意図した結果にならないことが報告されています。
| 関数 | SharePoint に対する委任可否 | 典型的な用途 |
|---|---|---|
Filter | 多くの条件で委任可(列や演算子による) | 行の絞り込み |
SortByColumns | 一部列で委任可 | 並び替え |
FirstN, LastN | 委任不可 | 先頭・末尾から N 件取得 |
Skip | SharePoint コネクタでは委任不可(実質ローカル処理) | 先頭 N 件を飛ばすページングなど |
そのため、Skip(SharePointList, N) のような式は、
- SharePoint から 先頭 500 / 2,000 件だけを取得
- その「一部だけのデータ」に対して Skip を適用する
という動きになります。これが、「大規模リストでは Skip が効いていないように見える」理由です。
ID 列を使った「しきい値ページング」が機能しない理由
「Skip がダメなら ID でページングしよう」と考えて、次のような式を書きたくなることがあります。
// 「ID が 1~50 のレコード」「51~100 のレコード」…とページ分割したい
Filter(SharePointList, ID > 50 && ID <= 100)
しかし SharePoint コネクタの仕様上、ID 列は =(等号)による比較のみ委任可能であり、>, >=, <, <=, <> などの不等号を含む比較は委任されません。
| 式の例 | 委任可否(SharePoint) | 備考 |
|---|---|---|
Filter(SharePointList, ID = 123) | 委任可 | 1 件を特定する用途なら安全 |
Filter(SharePointList, ID > 123) | 委任不可 | 大規模リストで使用すると結果が欠落する |
Filter(SharePointList, ID <> 123) | 委任不可 | <>(≠)も非委任 |
このため、ID を使って「ID > 50 のレコードを取得→Next ボタンでしきい値をずらす」というページングパターンは、大規模リストでは破綻します。
SharePoint REST API 側でも $skip が効かない
もう一つ混乱の元になっているのが、SharePoint REST API の挙動です。リスト項目の取得において、OData の $skip パラメータは リストに対してはサポートされず、代わりに $skiptoken を使う必要があります。
// NG 例($skip はリスト項目には効かない)
.../_api/web/lists/getbytitle('List')/items?$top=50&$skip=50
// OK 例($skiptoken を使用)
.../_api/web/lists/getbytitle('List')/items?$top=50&$skiptoken=Paged=TRUE%26p_ID=50
つまり、
- Power Apps 側の Skip 関数も非委任
- SharePoint REST 側の $skipもリスト項目には効かない
という「ダブルパンチ」によって、素直な Skip ベースのページングはほぼ使いものになりません。
現場で使える 3 つの回避策
では、実務ではどうするのが良いのでしょうか。ここでは、工数と堅牢性のバランスを考えた 3 つのパターンを示します。
| 回避策 | 概要 | 向いているケース |
|---|---|---|
| 回避策 A | 委任可能な列(作成日時など)をキーにして「しきい値ページング」する | 数万件程度までのリスト、Power Automate 不要で済ませたい場合 |
| 回避策 B | Power Automate + REST / Graph でサーバー側ページング | 数十万件規模のリスト、本番業務で確実性が必須な場合 |
| 回避策 C | 最大 2,000 件までと割り切り、非委任関数で簡易実装 | データ件数が小さいケース、試作・社内限定アプリなど |
回避策 A:委任できる列で「しきい値ページング」する
最も工数が小さく、既存 UI をあまり崩さずに済むのがこの方法です。
使用する列の考え方
次のような列を「ページングキー」として使うのが典型です。
- 作成日時(Created)
- 更新日時(Modified)
- 自前で用意した 連番やソート用の数値列(SortKey など)
これらの列に対しては、>, < を含む比較が委任されます(ただし計算列や特殊な型は例外もあるため注意)。
基本パターンの式
たとえば、「作成日時の昇順で 50 件ずつページングする」イメージは、概念的には次のような Power Fx になります。
// 1 ページ 50 件
Set(PageSize, 50);
// 直前ページの最後のレコードの Created を保持
// 1 ページ目を表示するときは Blank() にしておく
// varLastCreated : DateTime 型
// ギャラリーの Items プロパティ
FirstN(
SortByColumns(
Filter(
SharePointList,
IsBlank(varLastCreated) || Created > varLastCreated
),
"Created",
Ascending
),
PageSize
)
ポイントは、
- Filter の部分(Created > varLastCreated)が委任可能であること
- Filter で候補レコードをサーバー側で絞り込んでから、最後に FirstN(非委任)で行数を切ること
です。Filter の結果が 2,000 件未満であれば、その範囲に対して FirstN をかけるだけなので実用上問題になりにくくなります。
次ページ・前ページボタンの例
単純な「次へ」ボタンは、次のように書けます。
// ギャラリーの Items を先ほどの式にした場合
// 「次へ」ボタンの OnSelect
If(
CountRows(Gallery1.AllItems) > 0,
Set(
varLastCreated,
Last(Gallery1.AllItems).Created
)
);
「前へ」ボタンを完全に実装しようとすると少し複雑になるため、
- 「次へ」だけ用意して無限スクロール風にする
- ページ番号ではなく、「もっと見る」ボタンにする
といった UI に寄せてしまうのも現場ではよくある落としどころです。
注意点:ID 列を使わないこと
同じことを ID 列でやりたくなりますが、前述の通り、ID > x は SharePoint では委任不可です。
やるのであれば、
- 数値型の列
SortKeyを新たに作り、レコード作成時に連番を振る - または、Created+ID の複合キーで「Created が同一のときに ID でブレイク」する
といった工夫が必要です。
SharePoint 側のインデックス設定
Created / Modified / SortKey をページングキーに使う場合、SharePoint リスト側でその列に インデックス を付けておくと、以下のメリットがあります。
- フィルターの応答が速くなる
- リストビューのしきい値制限(5,000 件)を超える場合でも、クエリが通りやすくなる
多数のユーザーが同じリストを参照する業務アプリでは、インデックス設定をほぼ必須と考えておくと安心です。
回避策 B:Power Automate + REST / Graph でサーバー側ページング
データ件数が数十万件以上になる、あるいは「絶対に抜け漏れがあっては困る本番システム」であれば、Power Automate を使ったサーバー側ページングが本命になります。
SharePoint REST + $skiptoken の基本
SharePoint REST API では、$top と $skiptoken を組み合わせることでページングを行います。
// 例:作成日時+ID 昇順で 50 件ずつ取得
/_api/web/lists/getbytitle('List')/items
?$select=Id,Title,Created
&$orderby=Created,Id
&$top=50
&$skiptoken=Paged=TRUE%26p_ID=50
レスポンスには次ページを示す URL(odata.nextLink や __next などのプロパティ)が含まれ、そこに次の $skiptoken が含まれます。これをたどることで、サーバー側で確実にページを進めていけます。
Power Automate でフローを構成するイメージ
- トリガーに「Power Apps」を選択
- (任意)Power Apps から
skiptokenを受け取る入力パラメータを定義 - 「HTTP 要求を送信(SharePoint)」 アクションで REST API を呼び出す
- レスポンスから
value配列と次ページ URL(odata.nextLinkなど)を取り出す - Power Apps に返す JSON に
valueとnextLinkを含める
Power Apps 側では次のようなコードになります。
// 初回読み込み
Set(
res,
GetPageFromSP.Run("") // skiptoken なし
);
ClearCollect(colItems, res.value);
Set(varNextLink, res.nextLink);
// 「次へ」ボタン
If(
!IsBlank(varNextLink),
Set(res, GetPageFromSP.Run(varNextLink));
Collect(colItems, res.value);
Set(varNextLink, res.nextLink)
);
この方式では、レコードのスキップ・絞り込み・並び替えといった重い処理はすべて SharePoint サーバー側で完結するため、Power Apps の委任制限の影響を受けません。
Microsoft Graph を使ったページング
複数サイトをまたいでリストを扱う、Microsoft 365 全体の情報と一緒に扱う、といった高度な要件がある場合は、Microsoft Graph を使う選択肢もあります。
Graph では、一定件数を超えるとレスポンスに @odata.nextLink プロパティが含まれ、そこに次ページの URL が返されます。次ページを取得したいときは、この URL にそのまま GET を投げるだけでよい、という設計になっています。
// 例:Graph での次ページ URL
{
"value": [ ... ],
"@odata.nextLink": "https://graph.microsoft.com/v1.0/...?$top=50&$skiptoken=..."
}
Power Automate から Graph を呼び出す場合も、基本構造は SharePoint REST とほぼ同じです。
回避策 C:2,000 件以内と割り切る場合の簡易策
「このアプリで扱うレコードはどう頑張っても 2,000 件を超えない」ことが仕様で保証できる場合は、あえて非委任関数を気にせず使ってしまう方法もあります。
LastN を使った擬似 Skip
たとえば、Power Fx の中には Skip の代わりに LastN を使って先頭 N 件を飛ばすテクニックがよく紹介されています。
// テーブルの先頭 N 件を除外する(N <= CountRows(Table) を仮定)
LastN(
SharePointList,
Max(0, CountRows(SharePointList) - N)
)
この式は完全に非委任なので、SharePoint から最初の 500 / 2,000 件だけ取得した上でその中で LastN が実行されます。言い換えると、
- リスト全体が 2,000 件以内なら、実質すべてのレコードを見ているので問題にならない
- 2,001 件以上になった瞬間から、テーブルの「末尾」だと思っているものが実は全体の一部になる
という挙動になります。そのため、
- テスト環境・社内利用限定・データ件数が厳密に制限されている
- 多少の制約は飲み込める代わりに、実装を極力シンプルにしたい
といったケースに限って採用するのが良いでしょう。
「データ行数制限」を 2,000 にしても根本解決にはならない
Power Apps のアプリ設定の「データ行数制限」を 500 → 2,000 に増やすと、
- 非委任クエリで取得できる サンプル件数が増える
- その分だけ「見えている世界」が広がる
という意味では有効です。しかし、非委任であること自体は何も変わりません。サンプルが 500 件から 2,000 件に増えるだけで、2,001 件目以降はやはり切り捨てられます。
すぐ試せる設定・実装チェックリスト
実際にアプリを見直す際に確認しておきたいポイントをリストにまとめます。
ギャラリーの Items プロパティ
- Filter → SortByColumns → FirstN(必要なら) の順で処理しているか?
Skip(SharePointList, N)のように、いきなり全件に Skip をかけていないか?- Filter に使っている列・演算子は委任可能か?(ID の不等号比較を使っていないか)
SharePoint リストの設計
- ページングキーに使う列(Created / Modified / SortKey など)に インデックスを付けたか?
- 「Offline client availability(オフライン クライアント)」など、余計なオプションで動作が重くなっていないか?
- リストビューのフィルター・ソート条件と、アプリ側の Filter / Sort 条件が矛盾していないか?
アプリの設定
- 「データ行数制限」は必要に応じて 2,000 に上げているか?(ただし根本解決ではないことを理解した上で)
- 委任警告(青い下線)が表示されている式を放置していないか?
Power Automate / REST を使う場合
- SharePoint REST のクエリで
$skipではなく$skiptokenを使用しているか? - レスポンスから次ページ URL(
odata.nextLinkなど)をきちんと返しているか? - Power Apps 側で
nextLinkをグローバル変数に保持し、「次へ」押下でそれをフローに渡すようにしているか?
よくある疑問と補足
なぜ Skip を使っているのに委任警告が出ないことがあるの?
Power Apps の委任警告は「代表的な非委任パターン」については教えてくれますが、すべてのケースを完全にはカバーしていません。また、式の書き方やデータソースの種類によっては、警告が出ないにもかかわらず実際には非委任になっているケースもあります。
そのため、
- 「警告が出ていないから委任されているだろう」と楽観視しない
- 仕様として「Skip / FirstN / LastN などは基本的に非委任」と覚えておく
- 特にリストが 2,000 件を超える場合は、テストで抜け漏れがないか必ず確認する
といった運用が重要になります。
「ページ番号」で直接ジャンプさせる UI は難しい?
回避策 A のような「しきい値ページング」は、「1ページ目」「2ページ目」といった概念を 厳密に ID などで定義することには向きません。なぜなら、
- 途中でレコードが追加・削除されると、ページ境界が動いてしまう
- 「ページ番号」でのジャンプを実装しようとすると、結局 Skip 的な発想が必要になる
からです。
実務的には、
- 「最新 50 件を表示」「もっと見る(さらに 50 件)」のような 時間軸ベースの UI に寄せる
- 詳細検索は別画面に分離し、条件で絞り込んだ結果に対してのみページングする
といった割り切りをした方が、ユーザーにも理解しやすく、実装もシンプルにまとまります。
どうしても完全なページングが必要なら?
「絶対に全件を漏れなく、かつ任意のページにジャンプできる UI が必要」という要件であれば、
- Power Apps + SharePoint ではなく、Dataverse や Azure SQL のようなデータソースに寄せる
- あるいは、本格的な Web アプリ(.NET / React など)側で REST を叩いてページング処理を実装する
といった選択肢も検討に値します。
Power Apps は「業務ユーザーでもローコードでサクッと業務アプリを作る」ことに特化したプラットフォームなので、ページング 1 つ取っても「できること」と「向いていないこと」が存在します。その特性を踏まえた上で、要件に応じてプラットフォームやデータソースを選ぶことが重要です。
まとめ:Skip ではなく「委任できる条件 + 少量取得」か「サーバー側ページング」へ
本記事で整理したポイントを改めてまとめると、次のようになります。
Skip()やFirstN(),LastN()は、SharePoint リストに対しては 非委任 であり、大規模リストのページングには向かない- ID 列に対する
>,<などの比較も 非委任 であり、ID を使ったしきい値ページングは危険 - SharePoint REST では
$skipではなく$skiptokenでページングする必要がある - 実務的な回避策としては、
- 委任できる列(Created など)で絞り込み → FirstN で行数制限
- Power Automate + REST / Graph でサーバー側ページング
- 2,000 件以内と割り切り、非委任関数で簡易実装
のいずれかを選ぶのが現実解です。
「Skip を使ったのにページングできない」という問題に直面したら、まずは対象リストの件数と要件を洗い出し、上記 3 パターンからどれを採用するのが妥当かを検討してみてください。設計段階で委任を意識しておけば、あとから大規模リストに成長したときにも、最小限の手戻りで対応できるはずです。

コメント