TripPin Part 5-ページングTripPin Part 5 - Paging

このマルチパートチュートリアルでは、Power Query 用の新しいデータソース拡張機能の作成について説明します。This multi-part tutorial covers the creation of a new data source extension for Power Query. このチュートリアルは、 — 前のレッスンで作成したコネクタに対して各レッスンがビルドされ、コネクタに新しい機能が追加されていくことを目的としています。The tutorial is meant to be done sequentially—each lesson builds on the connector created in previous lessons, incrementally adding new capabilities to your connector.

このレッスンでは、次のことを行います。In this lesson, you will:

  • コネクタへのページングサポートの追加Add paging support to the connector

多くの Rest Api は "ページ" でデータを返します。これにより、クライアントは複数の要求を行って結果を結合する必要があります。Many Rest APIs will return data in "pages", requiring clients to make multiple requests to stitch the results together. ページ割り当てには一般的な規則 ( RFC 5988など) がいくつかありますが、通常は api によって異なります。Although there are some common conventions for pagination (such as RFC 5988), it generally varies from API to API. ありがたいと、TripPin は OData サービスであり、 odata 標準 は、応答の本文で返される odata のリンク 値を使用して改ページ位置の自動修正を実行する方法を定義します。Thankfully, TripPin is an OData service, and the OData standard defines a way of doing pagination using odata.nextLink values returned in the body of the response.

コネクタの 以前のイテレーション を簡略化するために、 TripPin.Feed 関数は _ページ対応_ではありませんでした。To simplify previous iterations of the connector, the TripPin.Feed function was not page aware. 単に、要求から返された JSON を解析し、それをテーブルとして書式設定しました。It simply parsed whatever JSON was returned from the request and formatted it as a table. OData プロトコルに慣れている方は、 応答の形式 ( value レコードの配列を含むフィールドがあると仮定するなど) に対して、いくつかの誤った仮定が行われていることに気付いたかもしれません。Those familiar with the OData protocol might have noticed that a number of incorrect assumptions were made on the format of the response (such as assuming there is a value field containing an array of records).

このレッスンでは、ページを認識させることで、応答処理ロジックを改善します。In this lesson you'll improve your response handling logic by making it page aware. 今後のチュートリアルでは、ページ処理ロジックがより堅牢になり、複数の応答形式 (サービスからのエラーを含む) を処理できるようになります。Future tutorials will make the page handling logic more robust and able to handle multiple response formats (including errors from the service).

注意

ODataに基づくコネクタを使用して独自のページングロジックを実装する必要はありません。これは、すべて自動的に処理されるためです。You do not need to implement your own paging logic with connectors based on OData.Feed, as it handles it all for you automatically.

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

ページングサポートを実装する場合は、API に関して次の点を把握しておく必要があります。When implementing paging support, you'll need to know the following things about your API:

  • データの次のページを要求するにはどうすればよいですか。How do you request the next page of data?
  • ページングメカニズムに値の計算が含まれているか、応答から次のページの URL を抽出していますか。Does the paging mechanism involve calculating values, or do you extract the URL for the next page from the response?
  • ページングを停止するタイミングを確認するにはどうすればよいですか。How do you know when to stop paging?
  • 認識しておく必要があるページングに関連するパラメーターはありますか。Are there parameters related to paging that you should be aware of? ("ページサイズ" など)(such as "page size")

これらの質問に対する答えは、ページングロジックの実装方法に影響します。The answer to these questions will impact the way you implement your paging logic. ページングの実装間では、 GenerateByPageの使用など、いくつかのコードを再利用できますが、ほとんどのコネクタではカスタムロジックが必要になります。While there is some amount of code reuse across paging implementations (such as the use of Table.GenerateByPage, most connectors will end up requiring custom logic.

注意

このレッスンでは、特定の形式に従った OData サービスのページングロジックについて説明します。This lesson contains paging logic for an OData service, which follows a specific format. API のドキュメントを参照して、コネクタでページング形式をサポートするために必要な変更を判断してください。Check the documentation for your API to determine the changes you'll need to make in your connector to support its paging format.

OData ページングの概要Overview of OData Paging

OData ページングは、応答ペイロード内に含まれる Nextlink 注釈 によって駆動されます。OData paging is driven by nextLink annotations contained within the response payload. NextLink 値には、データの次のページへの URL が含まれています。The nextLink value contains the URL to the next page of data. 応答で最も外側のオブジェクトのフィールドを検索することによって、別のデータページがあるかどうかを確認でき odata.nextLink ます。You'll know if there is another page of data by looking for an odata.nextLink field in outermost object in the response. フィールドがない場合は、 odata.nextLink すべてのデータが読み取られます。If there's no odata.nextLink field, you've read all of your data.

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

OData サービスによっては、クライアントが 最大ページサイズの設定を提供できる場合がありますが、それを受け入れるかどうかにかかわらず、サービスが必要になります。Some OData services allow clients to supply a max page size preference, but it is up to the service whether or not to honor it. Power Query は任意のサイズの応答を処理できる必要があるため、ページサイズの設定を指定する必要はありません — 。サービスによってスローされるあらゆることをサポートできます。Power Query should be able to handle responses of any size, so you don't need to worry about specifying a page size preference—you can support whatever the service throws at you.

サーバードリブンページングの詳細については、「OData 仕様」を参照してください。More information about Server-Driven Paging can be found in the OData specification.

テスト (TripPin を)Testing TripPin

ページングの実装を修正する前に、 前のチュートリアルの拡張機能の現在の動作を確認します。Before fixing your paging implementation, confirm the current behavior of the extension from the previous tutorial. 次のテストクエリは、People テーブルを取得し、現在の行数を表示するためのインデックス列を追加します。The following test query will retrieve the People table and add an index column to show your current row count.

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

Fiddler を有効にし、Visual Studio でクエリを実行します。Turn on fiddler, and run the query in Visual Studio. このクエリでは、8行 (インデックス 0 ~ 7) のテーブルが返されることがわかります。You'll notice that the query returns a table with 8 rows (index 0 to 7).

ページングを行いません

Fiddler からの応答の本文を確認すると、実際には、 @odata.nextLink 使用可能なデータのページが増えることを示すフィールドが含まれていることがわかります。If you look at the body of the response from fiddler, you'll see that it does in fact contain an @odata.nextLink field, indicating that there are more pages of data available.

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

TripPin のページングの実装Implementing Paging for TripPin

これで、拡張機能に次の変更を加えることになります。You're now going to make the following changes to your extension:

  1. 共通関数をインポートする Table.GenerateByPageImport the common Table.GenerateByPage function
  2. GetAllPagesByNextLink Table.GenerateByPage を使用してすべてのページを連結する関数を追加します。Add a GetAllPagesByNextLink function that uses Table.GenerateByPage to glue all pages together
  3. GetPage1 ページのデータを読み取ることができる関数を追加するAdd a GetPage function that can read a single page of data
  4. GetNextLink応答から次の URL を抽出する関数を追加しますAdd a GetNextLink function to extract the next URL from the response
  5. TripPin.Feed新しいページリーダー関数を使用するように更新するUpdate TripPin.Feed to use the new page reader functions

注意

このチュートリアルで既に説明したように、ページングロジックはデータソースによって異なります。As stated earlier in this tutorial, paging logic will vary between data sources. ここでの実装では、応答で返された 次のリンク を使用するソースに対して再利用できるように、ロジックを関数に分割しようとします。The implementation here tries to break up the logic into functions that should be reusable for sources that use next links returned in the response.

GenerateByPageTable.GenerateByPage

関数を使用すると、 Table.GenerateByPage 複数の "ページ" データを1つのテーブルに効率的に結合できます。The Table.GenerateByPage function can be used to efficiently combine multiple 'pages' of data into a single table. これを行うには、を受け取るまで、パラメーターとして渡された関数を繰り返し呼び出し getNextPage null ます。It does this by repeatedly calling the function passed in as the getNextPage parameter, until it receives a null. 関数のパラメーターは、1つの引数を受け取り、を返す必要があり nullable table ます。The function parameter must take a single argument, and return a nullable table.

getNextPage = (lastPage) as nullable table => ...

への各呼び出しは、 getNextPage 前の呼び出しの出力を受け取ります。Each call to getNextPage receives the output from the previous call.

// The getNextPage function takes a single argument and is expected to return a nullable table
Table.GenerateByPage = (getNextPage as function) as table =>
    let        
        listOfPages = List.Generate(
            () => getNextPage(null),            // get the first page of data
            (lastPage) => lastPage <> null,     // stop when the function returns null
            (lastPage) => getNextPage(lastPage) // pass the previous page to the next function call
        ),
        // concatenate the pages together
        tableOfPages = Table.FromList(listOfPages, Splitter.SplitByNothing(), {"Column1"}),
        firstRow = tableOfPages{0}?
    in
        // if we didn't get back any pages of data, return an empty table
        // otherwise set the table type based on the columns of the first page
        if (firstRow = null) then
            Table.FromRows({})
        else        
            Value.ReplaceType(
                Table.ExpandTableColumn(tableOfPages, "Column1", Table.ColumnNames(firstRow[Column1])),
                Value.Type(firstRow[Column1])
            );

次の点に注意して Table.GenerateByPage ください。Some notes about Table.GenerateByPage:

  • 関数は、 getNextPage 次のページの URL (またはページ番号、またはページングロジックの実装に使用されるその他の値) を取得する必要があります。The getNextPage function will need to retrieve the next page URL (or page number, or whatever other values are used to implement the paging logic). 通常、この操作を行う meta には、ページに値を追加してから返します。This is generally done by adding meta values to the page before returning it.
  • 結合されたテーブルの列とテーブル型 (つまり、すべてのページ) は、データの最初のページから派生します。The columns and table type of the combined table (i.e. all pages together) are derived from the first page of data. 関数は、 getNextPage データの各ページを正規化する必要があります。The getNextPage function should normalize each page of data.
  • の最初の呼び出しでは、 getNextPage null パラメーターを受け取ります。The first call to getNextPage receives a null parameter.
  • getNextPage 残りのページがない場合は、null を返す必要があります。getNextPage must return null when there are no pages left.

関数の本体は、 GetAllPagesByNextLink getNextPage の関数引数を実装し Table.GenerateByPage ます。The body of your GetAllPagesByNextLink function implements the getNextPage function argument for Table.GenerateByPage. 関数が呼び出され、 GetPage 前の呼び出しのレコードのフィールドから、次のデータページの URL が取得され NextLink meta ます。It will call the GetPage function, and retrieve the URL for the next page of data from the NextLink field of the meta record from the previous call.

// 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 の実装Implementing GetPage

GetPage関数は、 Web コンテンツを使用して trippin サービスから1ページのデータを取得し、応答をテーブルに変換します。Your GetPage function will use Web.Contents to retrieve a single page of data from the TripPin service, and convert the response into a table. Web.Contents GetNextLink 次のページの URL を抽出し、 meta 返されたテーブル (データのページ) のレコードに設定するために、Web からの応答を関数に渡します。It passes the response from Web.Contents to the GetNextLink function to extract the URL of the next page, and sets it on the meta record of the returned table (page of data).

この実装は、前のチュートリアルからの呼び出しの少し変更されたバージョンです TripPin.FeedThis implementation is a slightly modified version of the TripPin.Feed call from the previous tutorials.

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 し、その値を返します。Your GetNextLink function simply checks the body of the response for an @odata.nextLink field, and returns its value.

// In this implementation, 'response' will be 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");

まとめPutting it all Together

ページングロジックを実装する最後の手順は、 TripPin.Feed 新しい関数を使用するようにを更新することです。The final step to implement your paging logic is to update TripPin.Feed to use the new functions. ここでは、単にを使用してを呼び出し GetAllPagesByNextLink ますが、以降のチュートリアルでは、新しい機能 (スキーマの適用やパラメーターロジックのクエリなど) を追加します。For now, you're simply calling through to GetAllPagesByNextLink, but in subsequent tutorials, you'll be adding new capabilities (such as enforcing a schema, and query parameter logic).

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

チュートリアルの前の手順と同じ テストクエリ を再実行すると、ページリーダーが動作していることがわかります。If you re-run the same test query from earlier in the tutorial, you should now see the page reader in action. 8ではなく、応答に20行があることもわかります。You should also see that you have 20 rows in the response rather than 8.

QueryWithPaging

Fiddler の要求を確認すると、データの各ページに対して個別の要求が表示されるようになります。If you look at the requests in fiddler, you should now see separate requests for each page of data.

Fiddler

注意

サービスからのデータの最初のページに対して重複する要求があることがわかりますが、これは理想的ではありません。You'll notice duplicate requests for the first page of data from the service, which is not ideal. 余分な要求は、M エンジンのスキーマチェック動作の結果です。The extra request is a result of the M engine's schema checking behavior. ここではこの問題を無視して、明示的なスキーマを適用する 次のチュートリアルで解決してください。Ignore this issue for now and resolve it in the next tutorial, where you'll apply an explict schema.

結論Conclusion

このレッスンでは、Rest API の改ページサポートを実装する方法を説明しました。This lesson showed you how to implement pagination support for a Rest API. ロジックは Api によって異なる可能性がありますが、ここで確立されたパターンは、軽微な変更で再利用できます。While the logic will likely vary between APIs, the pattern established here should be reusable with minor modifications.

次のレッスンでは、明示的なスキーマをデータに適用する方法について説明します。これは、 text number 取得する単純型とデータ型を超えて Json.Document います。In the next lesson, you'll look at how to apply an explicit schema to your data, going beyond the simple text and number data types you get from Json.Document.

次のステップNext steps

パート 6-スキーマの TripPinTripPin Part 6 - Schema