次の方法で共有


TripPin パート 5 - ページング

このマルチパート チュートリアルでは、Power Query 用の新しいデータ ソース拡張機能の作成について説明します。 このチュートリアルは順番に行う予定です。各レッスンは、前のレッスンで作成したコネクタに基づいて構築され、コネクタに新しい機能を段階的に追加します。

このレッスンでは、次の操作を行います。

  • コネクタにページングのサポートを追加する

多くの Rest API は "ページ" のデータを返します。クライアントは結果を結合するために複数の要求を行う必要があります。 改ページ調整にはいくつかの一般的な規則 ( RFC 5988 など) がありますが、一般的には API によって異なります。 さいことに、TripPin は OData サービスであり、 OData 標準 では、応答の本文で返される odata.nextLink 値を使用して改ページ処理を行う方法が定義されています。

コネクタの 以前のイテレーション を簡略化するために、 TripPin.Feed 関数は ページ対応ではありません。 要求から返された JSON を解析し、テーブルとして書式設定するだけです。 OData プロトコルに慣れている場合は、 応答の形式 に関して多くの正しくない想定が行われていることに気付くかもしれません (たとえば、レコードの配列を含む value フィールドがあると仮定するなど)。

このレッスンでは、ページに対応させることで、応答処理ロジックを改善します。 今後のチュートリアルでは、ページ処理ロジックをより堅牢にし、複数の応答形式 (サービスからのエラーを含む) を処理できるようにします。

OData.Feed に基づくコネクタを使用して独自のページング ロジックを実装する必要はありません。これは自動的に処理されるためです。

ページングのチェックリスト

ページングのサポートを実装する場合は、API について次のことを知っている必要があります。

  • データの次のページを要求するにはどうすればよいですか?
  • ページング メカニズムには値の計算が含まれますか、または応答から次のページの URL を抽出しますか?
  • ページングを停止するタイミングを知る方法
  • 注意する必要があるページングに関連するパラメーター ("ページ サイズ" など) はありますか?

これらの質問に対する回答は、ページング ロジックを実装する方法に影響します。 ページングの実装 ( Table.GenerateByPage の使用など) 間でコードを再利用する量はあるものの、ほとんどのコネクタではカスタム ロジックが必要になります。

このレッスンには、特定の形式に従う OData サービスのページング ロジックが含まれています。 API のドキュメントを確認して、コネクタでページング形式をサポートするために行う必要がある変更を確認します。

OData ページングの概要

OData ページングは、応答ペイロード内に含まれる nextLink 注釈 によって駆動されます。 nextLink 値には、データの次のページへの URL が含まれています。 別のデータ ページがあるかどうかを判断するには、応答の最も外側のオブジェクトで odata.nextLink フィールドを探します。 odata.nextLinkフィールドがない場合は、すべてのデータが読み取られます。

{
  "odata.context": "...",
  "odata.count": 37,
  "value": [
    { },
    { },
    { }
  ],
  "odata.nextLink": "...?$skiptoken=342r89"
}

一部の OData サービスでは、クライアントが 最大ページ サイズの優先設定を指定できますが、受け入れるかどうかはサービスにかかります。 Power Query では、任意のサイズの応答を処理できる必要があるため、ページ サイズの基本設定の指定について心配する必要はありません。サービスがスローするものは何でもサポートできます。

サーバ駆動のページングの詳細については、OData 仕様をお読みください。

TripPin のテスト

ページングの実装を修正する前に、 前のチュートリアルの拡張機能の現在の動作を確認します。 次のテスト クエリでは、People テーブルを取得し、現在の行数を表示するインデックス列を追加します。

let
    source = TripPin.Contents(),
    data = source{[Name="People"]}[Data],
    withRowCount = Table.AddIndexColumn(data, "Index")
in
    withRowCount

Fiddler を有効にして、Power Query SDK でクエリを実行します。 このクエリは、8 行 (インデックス 0 から 7) のテーブルを返します。

インデックス行 0 から 7 のテーブルを示す PQTest 結果の [出力] タブのスクリーンショット。

fiddler からの応答の本文を見ると、実際には @odata.nextLink フィールドが含まれており、使用可能なデータのページが増えていることを示します。

{
  "@odata.context": "https://services.odata.org/V4/TripPinService/$metadata#People",
  "@odata.nextLink": "https://services.odata.org/v4/TripPinService/People?%24skiptoken=8",
  "value": [
    { },
    { },
    { }
  ]
}

TripPin のページングの実装

拡張機能に次の変更を加えます。

  1. 共通の Table.GenerateByPage 関数をインポートします。
  2. GetAllPagesByNextLinkを使用してすべてのページを接着するTable.GenerateByPage関数を追加します。
  3. 1 ページのデータを読み取ることができる GetPage 関数を追加します。
  4. GetNextLink関数を追加して、応答から次の URL を抽出します。
  5. 新しいページ リーダー関数を使用するように TripPin.Feed を更新します。

このチュートリアルで前述したように、ページング ロジックはデータ ソースによって異なります。 ここでの実装では、応答で返される 次のリンク を使用するソースに対して再利用可能な関数にロジックを分割しようとします。

Table.GenerateByPage(ページごとにテーブルを生成)

ソースによって返される複数のページを 1 つのテーブルに結合するには、 Table.GenerateByPageを使用します。 この関数は、名前が示すとおりにデータの次のページをフェッチするための getNextPage 関数を引数として受け取ります。 Table.GenerateByPage getNextPage関数を繰り返し呼び出します。この関数を渡すたびに、結果は最後に呼び出された時刻を生成し、nullを返して、それ以上ページが使用できなくなることを通知します。

この関数は Power Query の標準ライブラリの一部ではないので、 ソース コード を .pq ファイルにコピーする必要があります。

GetAllPagesByNextLink関数の本体は、getNextPageTable.GenerateByPage関数引数を実装します。 GetPage関数を呼び出し、前の呼び出しからNextLink レコードのmeta フィールドからデータの次のページの URL を取得します。

// Read all pages of data.
// After every page, we check the "NextLink" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
GetAllPagesByNextLink = (url as text) as table =>
    Table.GenerateByPage((previous) => 
        let
            // if previous is null, then this is our first page of data
            nextLink = if (previous = null) then url else Value.Metadata(previous)[NextLink]?,
            // if NextLink was set to null by the previous call, we know we have no more data
            page = if (nextLink <> null) then GetPage(nextLink) else null
        in
            page
    );

GetPage の実装

GetPage関数は、Web.Contents を使用して TripPin サービスから 1 ページのデータを取得し、応答をテーブルに変換します。 Web.Contents からの応答をGetNextLink関数に渡して次のページの URL を抽出し、返されたテーブル (データのページ) のmeta レコードに設定します。

この実装は、前のチュートリアルからの TripPin.Feed 呼び出しの少し変更されたバージョンです。

GetPage = (url as text) as table =>
    let
        response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),        
        body = Json.Document(response),
        nextLink = GetNextLink(body),
        data = Table.FromRecords(body[value])
    in
        data meta [NextLink = nextLink];

GetNextLink関数は、単に応答の本文で@odata.nextLinkフィールドをチェックし、その値を返します。

// In this implementation, 'response' is the parsed body of the response after the call to Json.Document.
// Look for the '@odata.nextLink' field and simply return null if it doesn't exist.
GetNextLink = (response) as nullable text => Record.FieldOrDefault(response, "@odata.nextLink");

すべてをまとめる

ページング ロジックを実装する最後の手順は、新しい関数を使用するように TripPin.Feed を更新することです。 ここでは、 GetAllPagesByNextLinkを呼び出すだけですが、以降のチュートリアルでは、新しい機能 (スキーマの適用、クエリ パラメーター ロジックなど) を追加します。

TripPin.Feed = (url as text) as table => GetAllPagesByNextLink(url);

チュートリアルの前半から同じ テスト クエリ を再実行すると、ページ リーダーが動作していることがわかります。 また、応答には 8 行ではなく 24 行が含まれていることがわかります。

24 行のデータを示す PQTest 結果の [出力] タブのスクリーンショット。

fiddler で要求を確認すると、データのページごとに個別の要求が表示されます。

データのページごとに個別の要求を含む Fiddler 出力のスクリーンショット。

サービスからのデータの最初のページに対する要求が重複している場合がありますが、これは理想的ではありません。 追加の要求は、M エンジンのスキーマ チェック動作の結果です。 現時点ではこの問題を無視し、次の チュートリアルで明示的なスキーマを適用して解決します。

Conclusion

このレッスンでは、Rest API の改ページ位置のサポートを実装する方法について説明しました。 ロジックは API によって異なる可能性が高くなりますが、ここで確立されたパターンは、軽微な変更で再利用できる必要があります。

次のレッスンでは、textから取得する単純なnumberJson.Documentのデータ型を超えて、明示的なスキーマをデータに適用する方法について説明します。

次のステップ

TripPin パート 6 - スキーマ