Requête OData / pagination

  • Requête OData / pagination

    Posté par Xavier NOEL-BRIAND sur 16 février 2023 at 9h52

    Bonjour, nous utilisons des requêtes OData dans PowerQuery pour récupérer des données. Sur le flux API de notre éditeur, nous avons une nouvelle contrainte technique > la pagination qui limite le nombre d’enregistrement à 1000 par appel.

    Quelqu’un a-t-il déjà eu ce cas à traiter ?

    Comment l’implémenter dans un environnement PowerQuery ?

    PostID=IT9iFywp9PBDbYs

    Jeremy a répondu 11 months, 3 weeks ago 1 Membre · 3 Réponses
  • 3 Réponses
  • Jeremy

    Member
    27 février 2023 at 8h16

    Bonjour Xavier,

    J’ai déjà eu cela avec des limitation à 500 enr par appel. J’ai dû créer des fonctions en M pour gérer cela.

    Voici mes fonctions :

    importTablenletn    Source = (tableImport) =>n    letn        firstPageUrl = "https://<NomDuSite>/api/" & tableImport,nn        output = @GenerateByPage(n            (lastPage) =>n                letn                    next = if lastPage <> null then Value.Metadata(lastPage)[Next] else null,n                    urlToUse = if (next <> null) then next else firstPageUrl,n                    current = if (lastPage <> null and next = null) then null else getJson(urlToUse),n                    dataset = if (current <> null) then getDataset(current) else null,n                    link = if (current <> null) then current[next_page_url] else nulln                inn                    dataset meta [Next=link]n        ),nn        getDataset = (json) =>n            letn                ConvertirEnTable = Table.FromRecords({json}),n                Developper = Table.ExpandListColumn(ConvertirEnTable, "data"),n                GarderData = Table.SelectColumns(Developper,{"data"})n            in GarderData,n            n        getJson = (url) =>n            letn                waitForResult = WaitFor(n                    (iteration) =>n                        letn                            Source = Web.Contents(url, [Headers=[Authorization="Bearer " & ApiToken], ManualStatusHandling = {500}, IsRetry = iteration > 0]),n                            contentType = Value.Metadata(Source)[Content.Type],n                            actualResult = if contentType = "application/json" then Json.Document(Source) else nulln                        in actualResult,n                    (iteration)=> #duration(0, 0, 0, Number.Power(2, iteration)),n                    5n                )n            inn                if waitForResult = nulln                then error "Wait for gave up after too many retry"n                else waitForResultn    inn        outputninn    Source

    A noter que le Token est dans un paramètre que j’ai appelé “ApiToken”.

    Ensuite, j’ai une fonction qui permet de générer par page :

    GenerateByPagenletn    Source = (getNextPage as function) as table =>n    letn        listOfPages = List.Generate(n            () => getNextPage(null),            // get the first page of datan            (lastPage) => lastPage <> null,     // stop when the function returns nulln            (lastPage) => getNextPage(lastPage) // pass the previous page to the next function calln        ),n        // concatenate the pages togethern        tableOfPages = Table.FromList(listOfPages, Splitter.SplitByNothing(), {"Column1"}),n        firstRow = tableOfPages{0}?n    inn        // if we didn't get back any pages of data, return an empty tablen        // otherwise set the table type based on the columns of the first pagen        if (firstRow = null) thenn            Table.FromRows({})nt    // check for empty first tablen        else if (Table.IsEmpty(firstRow[Column1])) thenn            firstRow[Column1]n        elsen            Value.ReplaceType(n                Table.ExpandTableColumn(tableOfPages, "Column1", Table.ColumnNames(firstRow[Column1])),n                Value.Type(firstRow[Column1])n            )ninn    Source

    Enfin, parce qu’il y a une limite de nombre de transactions par minute, j’avais dû créer une fonction d’attente :

    WaitFornletn    Source = (producer as function, interval as function, optional count as number) as any =>n    letn        list = List.Generate(n            () => {0, null},n            (state) => state{0} <> null and (count = null or state{0} < count),n            (state) => if state{1} <> null then {null, state{1}} else {1 + state{0}, Function.InvokeAfter(() => producer(state{0}), interval(state{0}))},n            (state) => state{1})n    inn        List.Last(list)ninn    Source

    J’espère que cela pourra t’aider. Peut-être une idée d’article à faire dès que je pourrais car c’est une vraie problématique.

    ATTENTION : j’ai eu des problèmes avec ce genre de code car la source devient dynamique et elle ne peut pas être utilisée dans Power BI Services. En effet l’actualisation automatique des sources dynamiques ne fonctionne pas.

    CommentID=rPOfMdeNFioAZOK, PostID=IT9iFywp9PBDbYs

  • Xavier NOEL-BRIAND

    Member
    10 mars 2023 at 13h55

    Merci pour ton retour Jérémy Laplaine 🙂

    On a créé une fonction personnalisée dans PowerQuery getOdata avec le code suivant et ça fonctionne 🎉

    letn    Source = (url as text) => letn        GetPage = (url) => letn            Source = Json.Document(Web.Contents(url, [Headers=[#"Authorization"="Bearer " & getToken()]])),n            NextList = Source[value],n            Result = try @NextList & @GetPage(Source[#"@odata.nextLink"]) otherwise NextListn        inn            Result,n            Pages = GetPage(url),n            #"Converti en table" = Table.FromList(Pages, Splitter.SplitByNothing(), null, null, ExtraValues.Error)n    inn        #"Converti en table"ninn    Source

    CommentID=bGNGNm6U4U8zwNw, PostID=IT9iFywp9PBDbYs

    • Jeremy

      Member
      10 mars 2023 at 14h01

      Magnifique ! Attention, vérifie bien que l’actualisation fonctionne côté Power BI Service. Si jamais, il faut utiliser le RelativePath.

      SubCommentID=gHnYMLQM72obhqz, CommentID=bGNGNm6U4U8zwNw, PostID=IT9iFywp9PBDbYs

Connectez-vous pour répondre.