Forum Discussion
how to create a query that paginates?
I'm working with the Hubspot CRM API and when you query for a list of all deals, you only get 100 records at a time, and they want you to send subsequent queries with an "offset" to paginate the results.
For instance, if you send:
https://api.hubapi.com/deals/v1/deal/all?hapikey=demo
at the very end of the query, you see the following JSON:
"hasMore":false "offset":27939158
so, if hasMore is true, the NEXT query should look like this:
https://api.hubapi.com/deals/v1/deal/all?hapikey=demo&offset=27939158
and then, we would want to repeat the process until hasMore comes back with false.
i'm completely new to power bi, so would love to know how to handle this type of query process.
in another language, this would just be do { } while (hasMore == false);
or something like that...
214 Replies
- LarsSchreiberResponsive Resident
Hi gotmike,
there is no concept of loops in M (Power Query), but you can use recursive functions, to reach your goals. The following article by Chris Webb uses this concept to flat a parent/child hierarchy. I am not sure how to use this in combination with your API call, but maybe this is one more step into the right direction for you :)
Regards,
Lars
- tristanstcyrHelper I
You may also want to avoid recursion as well since we don't do tail recursion optimization. It depends how many results you need to iterate over. If the result is large, look into using functions such as List.Accumulate or List.Generate.
- ImkeFCommunity Champion
This article contains of very good explanation of how List.Generate works: http://blog.crossjoin.co.uk/2014/06/25/using-list-generate-to-make-multiple-replacements-of-words-in-text-in-power-query/
- shad0wca7Frequent Visitor
I spent a while on this recently and finally cracked it. Here's some code that will retrieve all companies from Hubspot - some entries may be duplicated (I never figured that out) but a simple next query step of 'remove duplicates' does the trick. It can be used as the basis for all Hubspot API calls and probably similar pagination APIs:
let Pagination = List.Skip(List.Generate( () => [IsMore = null, Last_Key = 0, Counter = 0], // Start Value each [IsMore] <> false,// Whilst this is true, keep going each [WebCall = Json.Document(Web.Contents("https://api.hubapi.com/companies/v2/companies/paged?hapikey=" & #"Hubspot API Key" & "&properties=name&properties=website&limit=250&offset=" & Text.From([Last_Key]) & "")), // retrieve results per call Last_Key = try [WebCall][offset] otherwise 0, IsMore = if [Counter] < 1 then null else [WebCall][#"has-more"], Counter = [Counter]+1, Table = Table.FromRecords(WebCall[companies]) ] ,each [Table] // selector ) ,1) // in // Pagination , Custom1 = Table.Combine(Pagination) in Custom1- shad0wca7Frequent Visitor
You're welcome! I spent a fair bit of time trying to get this to work reliably and it would be a shame not to share it!
- JackSelmanHelper I
Thanks for sharing shad0wca7 ! I don't suppose you've updated this with the changes to HubSpot APIs? I'm using a PAT and I'm struggling to figure out hot to get it to work (as opposed to the APIKEY
- moritz1Regular Visitor
Hello!
After reading most of the Thread and a couple of hours of try and error later, frustration and mind block starts to kick in for me.
I am trying to adapt the code samples to request data from Airtable.com, which uses the same Pagination with an offset-Parameter like in the last example. Maybe someone can help me?? :smileyfrustrated:
To make the reproduction easier, I created a sample database with five entries.
Example how the API works, I am requesting only two records (with the pageSize=2 Parameter) to allow testing with minimal data...
// 20170424221134 // https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2 { "records": [ { "id": "rec9xyewCK97T01kP", "fields": { "Room": "Garage", "Condition": "Poor", "Priority": "Low" }, "createdTime": "2017-04-24T19:53:01.511Z" }, { "id": "recZmk4a5kmxdfK7O", "fields": { "Condition": "Poor", "Priority": "Medium", "Room": "Kitchen" }, "createdTime": "2015-11-16T22:48:35.000Z" } ], "offset": "itrANA53fD5J9bdLa/recZmk4a5kmxdfK7O" }The offset-Parameter can be inserted in the next call, to get the next two records:
https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2&offset=itrANA53fD5J9bdLa/recZmk4a5kmxdfK7O
To request the whole dataset (without pagination), I would perform the following in Power BI
let Source = Json.Document(Web.Contents("https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO")), records = Source[records], #"Converted to Table" = Table.FromList(records, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "fields", "createdTime"}, {"Column1.id", "Column1.fields", "Column1.createdTime"}), #"Expanded Column1.fields" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1.fields", {"Condition", "Priority", "Notes", "Room", "Started?", "Projects"}, {"Column1.fields.Condition", "Column1.fields.Priority", "Column1.fields.Notes", "Column1.fields.Room", "Column1.fields.Started?", "Column1.fields.Projects"}) in #"Expanded Column1.fields"Now I tried to bring together this coding and the example coding from earlier:
let Pagination = List.Skip(List.Generate( () => [Last_Key = "", Counter=0], // Start Value each [Last_Key] <> null and [Last_Key] <> "", // Condition under which the next execution will happen each [ Last_Key = try if [Counter]<=1 then "" else [WebCall][offset] otherwise null,// determine the LastKey for the next execution WebCall = Json.Document(Web.Contents("https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2&offset="&Last_Key&"")), // retrieve results per call Counter = [Counter]+1// internal counter ], each [WebCall] ),1) , #"Converted to Table" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error) , #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "fields", "createdTime"}, {"Column1.id", "Column1.fields", "Column1.createdTime"}) , #"Expanded Column1.fields" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1.fields", {"Condition", "Priority", "Notes", "Room", "Started?", "Projects"}, {"Column1.fields.Condition", "Column1.fields.Priority", "Column1.fields.Notes", "Column1.fields.Room", "Column1.fields.Started?", "Column1.fields.Projects"}) in #"Expanded Column1.fields"But I get an error the column "Column1" of the table could not be found. It seems that my "Pagination" is already empty.
Edit: It seems that the errors comes up because the offset parameter cannot be empty on the first call and can only be filled with valid values, otherwise the API call will fail. I removed the and [Last_Key] <> "" from the third row and now I finally get data (hooray!!) but two records are fetched twice, so instead of five entries it returns seven. Is there any way I can initialize the "Last_Key" and only pass it to the offset-Parameter when it is not initial?
Can anyone help me? Thank you very much!! :smileyhappy:
Best regards
Moritz
- moritz1Regular Visitor
Hello!
After reading most of the Thread and a couple of hours of try and error later, frustration and mind block starts to kick in for me.
I am trying to adapt the code samples to request data from Airtable.com, which uses the same Pagination with an offset-Parameter like in the last example. Maybe someone can help me?? :smileyfrustrated:
To make the reproduction easier, I created a sample database with five entries.
Example how the API works, I am requesting only two records (with the pageSize=2 Parameter) to allow testing with minimal data...
// 20170424221134 // https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2 { "records": [ { "id": "rec9xyewCK97T01kP", "fields": { "Room": "Garage", "Condition": "Poor", "Priority": "Low" }, "createdTime": "2017-04-24T19:53:01.511Z" }, { "id": "recZmk4a5kmxdfK7O", "fields": { "Condition": "Poor", "Priority": "Medium", "Room": "Kitchen" }, "createdTime": "2015-11-16T22:48:35.000Z" } ], "offset": "itrANA53fD5J9bdLa/recZmk4a5kmxdfK7O" }The offset-Parameter can be inserted in the next call, to get the next two records:
https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2&offset=itrANA53fD5J9bdLa/recZmk4a5kmxdfK7O
To request the whole dataset (without pagination), I would perform the following in Power BI
let Source = Json.Document(Web.Contents("https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO")), records = Source[records], #"Converted to Table" = Table.FromList(records, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "fields", "createdTime"}, {"Column1.id", "Column1.fields", "Column1.createdTime"}), #"Expanded Column1.fields" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1.fields", {"Condition", "Priority", "Notes", "Room", "Started?", "Projects"}, {"Column1.fields.Condition", "Column1.fields.Priority", "Column1.fields.Notes", "Column1.fields.Room", "Column1.fields.Started?", "Column1.fields.Projects"}) in #"Expanded Column1.fields"The thing is, the offset parameter must be valid and cannot be left empty. For the first API-Call, it must not be sent at all.
I tried to bring together this coding and the example coding from earlier:
let Pagination = List.Skip(List.Generate( () => [Last_Key = "", Counter=0], // Start Value each [Last_Key] <> null, // Condition under which the next execution will happen each [ Last_Key = try if [Counter]<=1 then "" else [WebCall][offset] otherwise null,// determine the LastKey for the next execution WebCall = Json.Document(Web.Contents("https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2&offset="&Last_Key&"")), // retrieve results per call Counter = [Counter]+1// internal counter ], each [WebCall] ),1), #"In Tabelle konvertiert" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Erweiterte Column1" = Table.ExpandRecordColumn(#"In Tabelle konvertiert", "Column1", {"records", "offset"}, {"Column1.records", "Column1.offset"}), #"Erweiterte Column1.records" = Table.ExpandListColumn(#"Erweiterte Column1", "Column1.records"), #"Erweiterte Column1.records1" = Table.ExpandRecordColumn(#"Erweiterte Column1.records", "Column1.records", {"id", "fields", "createdTime"}, {"Column1.records.id", "Column1.records.fields", "Column1.records.createdTime"}) in #"Erweiterte Column1.records1"The thing is now, that the first API call seems to be executed twice, at least I get the first two values doubled in in the result set.
Is there any way to "initialize" the Last_Key and only pass the offset-Parameter if the Last_Key is not initialized?
I tried this:
let Pagination = List.Skip(List.Generate( () => [Last_Key = "&init", Counter=0], // Start Value each [Last_Key] <> null, // Condition under which the next execution will happen each [ Last_Key = try if [Counter]<=1 then "" else "&offset="[WebCall][offset] otherwise null,// determine the LastKey for the next execution WebCall = Json.Document(Web.Contents("https://api.airtable.com/v0/appiXnlh3PAZ46394/Room%20Assessment?api_key=keyi0umQebbJneGDO&pageSize=2"&Last_Key&"")), // retrieve results per call Counter = [Counter]+1// internal counter ], each [WebCall] ),1),Which would send an empty "init"-parameter on the first call (the API doesn't mind that), and concatenate the offset-Parameter later. But this only returns the first two entries twice.
Can anyone help me? Thank you very much!! :smileyhappy:
Best regards
Moritz
- ImkeFCommunity Champion
It's late, so just a quick idea now & maybe more tomorrow :-)
You have to put a condition into the step "WebCall", like :
if counter= 0 then ...CallWithoutOffset/LastKey else YourCurrentString
- moritz1Regular Visitor
Hello Imke,
thanks for your reply. That did the trick! And the Counter<=1 had to be changed to Counter<1... Thanks a lot!
let Pagination = List.Skip(List.Generate( () => [Last_Key = "init", Counter=0], // Start Value each [Last_Key] <> null, // Condition under which the next execution will happen each [ Last_Key = try if [Counter]<1 then "" else [WebCall][Value][offset] otherwise null,// determine the LastKey for the next execution WebCall = try if [Counter]<1 then Json.Document(Web.Contents("https://api.airtable.com/v0/<api>/Room%20Assessment?api_key=<apikey>&pageSize=2")) else Json.Document(Web.Contents("https://api.airtable.com/v0/<api>/Room%20Assessment?api_key=<apikey>pageSize=2&offset="&Last_Key&"")), // retrieve results per call Counter = [Counter]+1// internal counter ], each [WebCall] ),1), #"In Tabelle konvertiert" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Erweiterte Column1" = Table.ExpandRecordColumn(#"In Tabelle konvertiert", "Column1", {"HasError", "Value"}, {"Column1.HasError", "Column1.Value"}), #"Erweiterte Column1.Value" = Table.ExpandRecordColumn(#"Erweiterte Column1", "Column1.Value", {"records", "offset"}, {"Column1.Value.records", "Column1.Value.offset"}), #"Erweiterte Column1.Value.records" = Table.ExpandListColumn(#"Erweiterte Column1.Value", "Column1.Value.records"), #"Erweiterte Column1.Value.records1" = Table.ExpandRecordColumn(#"Erweiterte Column1.Value.records", "Column1.Value.records", {"id", "fields", "createdTime"}, {"Column1.Value.records.id", "Column1.Value.records.fields", "Column1.Value.records.createdTime"}), #"Erweiterte Column1.Value.records.fields" = Table.ExpandRecordColumn(#"Erweiterte Column1.Value.records1", "Column1.Value.records.fields", {"Condition", "Priority", "Room"}, {"Column1.Value.records.fields.Condition", "Column1.Value.records.fields.Priority", "Column1.Value.records.fields.Room"}) in #"Erweiterte Column1.Value.records.fields"
- GrantFrequent Visitor
Hi
I've been watching this thread, in particular the conversation between Imke & kroll, in the hope that I may get the help I need. It seems that solution provided by Imke to kroll might do the trick for me, I want to return all records. I've attempted to reverse engineer the code but it returns an error. I've provided the code with the credentials to a test data source in the hope that someone might be able to assist.
let Pagination = List.Skip(List.Generate( () => [Last_Key = 1, Counter=0], // Start Value each [Last_Key] <> null and [Last_Key] <> "", // Condition under which the next execution will happen each [ WebCall = Json.Document(Web.Contents("https://api.capsulecrm.com/api/v2/parties?page='"&[Last_Key]&"'",[Headers=[Authorization="Bearer WAWjGWU6Kbl4o9TeGMRw5i52+kvSiz9xfQe+vNTxlcjw61R7RZYa4HxvdT8TSlDG"]])), // retrieve results per call Last_Key = if [Counter]<=1 then 1 else WebCall[lastKey] ,// determine the LastKey for the next execution Counter = [Counter]+1,// internal counter #"Converted to Table" = Record.ToTable(WebCall), // steps of your further query Value = #"Converted to Table"{1}[Value] // last step of your further queries ], each [Value]),1), Pagination1 = Pagination{0} in Pagination1Kind Regards - Grant
- ImkeFCommunity Champion
Hi Grant,
please check out this code:
let Pagination = List.Skip(List.Generate( () => [Last_Key = 0, Counter=0], // Start Value each [Counter]<4, // Condition under which the next execution will happen each [ WebCall = Json.Document(Web.Contents("https://api.capsulecrm.com/api/v2/parties?page="&Text.From([Last_Key])&"",[Headers=[Authorization="Bearer WAWjGWU6Kbl4o9TeGMRw5i52+kvSiz9xfQe+vNTxlcjw61R7RZYa4HxvdT8TSlDG"]])), // retrieve results per call Last_Key = [Last_Key]+1, Counter = [Counter]+1,// internal counter Table = Table.FromRecords(WebCall[parties]) // steps of your further query //Value = #"Converted to Table"{0}[Value] // last step of your further queries ] ,each [Table] ) ,1), Custom1 = Table.Combine(Pagination) in Custom1It returns results, but I couldn't spot any NextKey, so you might have to limit the number of pages manually.
- GrantFrequent Visitor
Hi Imke
Thank you for your rapid response. As you will probably gather from the questions to follow, I'm a Power Query/BI novice. the code you provided has pointed me in the right direction, and with some minor modifications, I get what I want - see code below.
let Pagination = List.Skip(List.Generate( () => [Page = 1, Counter=0], // Start Value each [Counter]<50, // Condition under which the next execution will happen each [ WebCall = Json.Document(Web.Contents("https://api.capsulecrm.com/api/v2/parties?perPage=100&page="&Text.From([Page])&"",[Headers=[Authorization="Bearer WAWjGWU6Kbl4o9TeGMRw5i52+kvSiz9xfQe+vNTxlcjw61R7RZYa4HxvdT8TSlDG"]])), // retrieve results per call Page = [Page]+1, Counter = [Counter]+1,// internal counter Table = Table.FromRecords(WebCall[parties]) // steps of your further query //Value = #"Converted to Table"{0}[Value] // last step of your further queries ] ,each [Table] ) ,1), #"Converted to Table" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandTableColumn(#"Converted to Table", "Column1", {"id", "firstName", "lastName", "createdAt"}, {"id", "firstName", "lastName", "createdAt"}), #"Removed Duplicates" = Table.Distinct(#"Expanded Column1", {"id"}), #"Filtered Rows" = Table.SelectRows(#"Removed Duplicates", each [id] <> null) in #"Filtered Rows"What I'm actually trying to accomplish is to code so that I dont have to limit the number of pages manually. I want to stop the execution when the number of results returned are zero.
You will also note that I have had to modify the code remove duplicates and null values in order to arrive at the expected result i.e. 241 records.
Hope you can assist.
Kind Regards - Grant
- GrantFrequent Visitor
Hi
Apologies if this is construed as a cross post. if I have a working example of a query that paginates that Imke helped me with - see below.
let Pagination = List.Skip(List.Generate( () => [Table = #table({}, {{}}) ,Page = 1, Counter=0], each Table.RowCount([Table])>0 or [Counter]=0, each [ WebCall = Json.Document(Web.Contents("https://api.capsulecrm.com/api/v2/opportunities?perPage=100&embed=tags&page="&Text.From([Page])&"",[Headers=[Authorization="Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]])), Page = [Page]+1, Counter = [Counter]+1, Table = Table.FromRecords(WebCall[opportunities]) ] ,each [Table] ) ,1), #"Converted to Table" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error) in #"Converted to Table"So what I'm trying to acomplish now, is create a query that paginates based on the query above but Posts a filter in the body of the request. To put it bluntly, it's doing my head in, so I'm hoping that someone can assist? It uses a different URL however, https://api.capsulecrm.com/api/v2/opportunities/filters/results and I need to post the following in the body of the request;
{ "filter" : { "conditions": [ { "field": "isClosed", "operator": "is", "value": false } ] }}
This is what I have coded thus far.
let
obj = "{""filter"":{""conditions"":[{""field"":""isClosed"",""operator"":""is"",""value"":false }]}}",
authKey = "Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
url = "https://api.capsulecrm.com/api/v2/opportunities/filters/results?perPage=100&page=",
Pagination = List.Skip(List.Generate( () => [Table = #table({}, {{}}) ,Page = 1, Counter=0],
each Table.RowCount([Table])>0 or [Counter]=0, // Condition under which the next execution will happen
each [ WebCall = Json.Document(Web.Contents(url&Text.From([Page]) & obj,[Headers=[#"Authorization"=authKey, #"Content-Type"="application/json"],Content = Text.ToBinary(obj)])),
Page = [Page]+1,
Counter = [Counter]+1,// internal counter
Table = Table.FromRecords(WebCall[opportunities])
]
,each [Table]
) ,1)
in
PaginationObviously the code above does not work, otherwise I wouldnt be posting here :smileywink:. I do however think I'm on the right track. See error produced below;
DataSource.Error: Web.Contents failed to get contents from 'https://api.capsulecrm.com/api/v2/opportunities/filters/results?perPage=100&page=1%7B%22filter%22:%7B%22conditions%22:%5B%7B%22field%22:%22isClosed%22,%22operator%22:%22is%22,%22value%22:false%20%7D%5D%7D%7D' (400): Bad Request
Details:
DataSourceKind=Web
DataSourcePath=https://api.capsulecrm.com/api/v2/opportunities/filters/results
Url=https://api.capsulecrm.com/api/v2/opportunities/filters/results?perPage=100&page=1%7B%22filter%22:%7B%22conditions%22:%5B%7B%22field%22:%22isClosed%22,%22operator%22:%22is%22,%22value%22:false%20%7D%5D%7D%7DHope someone can help
Kind Regards - Grant
- ImkeFCommunity Champion
Please check out this post and see if you can get it working: https://eriksvensen.wordpress.com/2014/09/15/specifying-json-query-in-power-query-example-statistics-sweden/
- spocxHelper I
Hi ImkeF
I am as well a total novice joining this great thread.
I am trying to use the code you helped Grant to build earlier in this thread to accomplish a looped rest api call.
I have basically just changed the url in the webcall and the Pagination values that is defined for the page i am trying to get data from. The Pagination values for the system can be found here: https://developer.itrp.com/v1/general/pagination/
let Pagination = List.Skip(List.Generate( () => [Table = #table({}, {{}}) ,Page = 1, Counter=0], // Start Value each Table.RowCount([Table])>0 or [Counter]=0, // Condition under which the next execution will happen each [ WebCall = Json.Document(Web.Contents("https://api.itrp.qa/requests?per_page=100&page="&Text.From([Page])&"")), // retrieve results per call Page = [Page]+1, Counter = [Counter]+1,// internal counter Table = Table.FromRecords(WebCall[requests]) // steps of your further query ] ,each [Table] ) ,1), #"Converted to Table" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error) in #"Converted to Table"But i recieve the following error when trying to convert to table:
Any kind of help is much appreciated.
- mwoody1983Regular Visitor
Hi ImkeF,
How would I go about doing this with a JIRA call, where I have a startAt and total number of records? I've included them below as an example, I can't get my head around how I would call them? Any help pointing me in the right direction would be great thanks.
let Pagination = startAt": 0 "total": 161 each [Last_Key] <> null and [Last_Key] <> "", // Condition under which the next execution will happen each [WebCall = Json.Document(Web.Contents("https://companyName.atlassian.net/rest/agile/1.0/board?startAt"&??,[Headers=[ContentType="application/json", Authorization="Auth="]])), // retrieve results per call Last_Key = if [Counter]<=1 then 1 else WebCall[lastKey] ,// determine the LastKey for the next execution Counter = [Counter]+1,// internal counter #"Converted to Table" = Record.ToTable(WebCall), // steps of your further query Value = #"Converted to Table"{1}[Value] // last step of your further queries ], each [Value]),1), Pagination1 = Pagination{0} in Pagination1- DBaHelper I
Not as neat as the previous query, but you could try something along these lines:
Source1 = {0..161}, #"Converted to Table" = Table.FromList(Source1, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Add Pagination Number" = Table.AddColumn(#"Converted to Table", "startAt", each [Column1]+1), #"Added Custom" = Table.AddColumn(#"Add Pagination Number", "Custom", each Json.Document(Web.Contents("yourlink?startAt=0", [Headers=[your details],Query=[startAt=Text.From([startAt])]]))),
in
#"Added Custom"I'm not sure whether you actually need 'startAt' in the api link, but you can play around with removing it to get it to work?
- ImkeFCommunity Champion
Agree with DBa: No need to use the complicated List.Generate here. "Just" create a table with one row per api-call needed that holds all the variables/parameters that are needed to create the distinct URLs.
If one call can fetch 50 records, then your table might just need four rows and you could start with a list like this:
{1..Number.RoundUp(161/50)}
- AracelliFrequent Visitor
Hello, I need help to load in Power BI more than 100 records from Airtable.
I do not understand this programming language, so even though I have reviewed all the examples I do not understand what I should do.
Here is my code, I did it using the power BI interface to import data and convert into a table.
I need help!!! Thank youletOrigen = Json.Document(Web.Contents("https://api.airtable.com/v0/appWNGNQnupCwVItO/Proyectos?api_key=key")),#"Converted to Table1" = Record.ToTable(Origen),#"Removed Bottom Rows" = Table.RemoveLastN(#"Converted to Table1",1),#"Filtered Rows" = Table.SelectRows(#"Removed Bottom Rows", each [Value] <> "itrn4qcW1C96uVpbe/reczS133aB2AtJA3t"),#"Expanded Value" = Table.ExpandListColumn(#"Filtered Rows", "Value"),#"Expanded Value1" = Table.ExpandRecordColumn(#"Expanded Value", "Value", {"id", "fields", "createdTime"}, {"Value.id", "Value.fields", "Value.createdTime"}),#"Se expandió Value.fields" = Table.ExpandRecordColumn(#"Expanded Value1", "Value.fields", {"Id Jira", "Nombre", "Estado", "Horas Op", "Pasos a produccion", "HH est", "Fechas", "Resp", "Tipo plan", "Compañia", "Resolutor", "Tipo Proy", "Responsable", "Programacion des", "Trimestre", "Trabajo semanal", "Facturacion", "ExcluirPBI"}, {"Value.fields.Id Jira", "Value.fields.Nombre", "Value.fields.Estado", "Value.fields.Horas Op", "Value.fields.Pasos a produccion", "Value.fields.HH est", "Value.fields.Fechas", "Value.fields.Resp", "Value.fields.Tipo plan", "Value.fields.Compañia", "Value.fields.Resolutor", "Value.fields.Tipo Proy", "Value.fields.Responsable", "Value.fields.Programacion des", "Value.fields.Trimestre", "Value.fields.Trabajo semanal", "Value.fields.Facturacion", "Value.fields.ExcluirPBI"}),#"Reordered Columns" = Table.ReorderColumns(#"Se expandió Value.fields",{"Value.id", "Name", "Value.fields.Id Jira", "Value.fields.Nombre", "Value.fields.Estado", "Value.fields.Horas Op", "Value.fields.Pasos a produccion", "Value.fields.HH est", "Value.fields.Fechas", "Value.fields.Resp", "Value.fields.Tipo plan", "Value.fields.Compañia", "Value.fields.Resolutor", "Value.fields.Tipo Proy", "Value.fields.Responsable", "Value.fields.Programacion des", "Value.fields.Trimestre", "Value.fields.Trabajo semanal", "Value.createdTime"}),Personalizado1 = #"Reordered Columns"inPersonalizado1- ImkeFCommunity Champion
This video might be a good start to get and understanding of what's required here: https://www.youtube.com/watch?v=vhr4w5G8bRA
You will have to transform your query into a function - this video might also help: https://www.youtube.com/watch?v=GgwXt4LVmsU)
- AracelliFrequent Visitor
Hi thanks! I already managed to paginate the load of my input data, but now I have an error when I want to perform an automatic update in PowerBI WEB.
Apparently I have a very long sentence, but I do not know how I can solve the problem
I would really appreciate if you can help me.
Thank you.
- snamuthFrequent Visitor
gotmike or ImkeF I am using the HubSpot Contacts API to get all contacts from the CRM. I am running into the same issue with paging through the contact list. I have been reading through the comments and trying different approaches, but unfortunately without any luck. The parameter for &vidOffset can be used in the url to page through contacts, but needs to be sent in the second call and is based on the the vid-offset value in the first call. Any help on this would be greatly appreciated.
- ImkeFCommunity Champion
I'm too busy currently to look deeper into this, but please check this link: http://blog.datainspirations.com/2018/02/17/dynamic-web-contents-and-power-bi-refresh-errors/
- snamuthFrequent Visitor
Thanks ImkeF. I reviewed the link you posted and tried that code as part of my testing, but unfortunatly it doesn't work for my use case. I don't get a number of pages back in the response, instead I get a vid-offset number and a has-more value of true if there are additional contacts to page through. The vid-offset number that I get back on the first call is the max vid (hubspot contact id) in the list. This number needs to then be passed into the parameter &vidOffset in the url to get additional contacts. Each time the vid-offset will come back with another number until the has-more value no longer equals true. I will keep at it and see if I can figure it out. Thanks again.
- gotmikeFrequent Visitor
Okay, so I'm the OP on this and unfortunately I lost track of this post for a couple YEARS... whoops.
But now I got back to it and in looking through all the responses, it seems to be a very hot topic.
My original question was about HubSpot CRM specifically and pagination in general.
I have finally found a solution, which I will post here for everyone else's benefit.
But first, a couple notes:
- My goal here was to get DEAL info from HubSpot CRM. Presumably, the same process would work for any other info from HubSpot like CONTACTS or COMPANIES. Also would probably work for many others who have a similar use case.
- Like many APIs out there, they PAGINATE the results, by default they will give you 100 per API call, but you can request up to 250 per call.
- Like many APIs that paginate, they also give you an OFFSET, so you provide that offset on subsequent calls to get additional pages.
- Like many APIs that paginate, they will give you a boolean indicator if there are more or not.
- Like many APIs out there, the HubSpot API breaks data up into separate API calls, so the first one gets you a list of IDs, which you then must use to make a subsequent call for each individual item.
- Like many APIs out there, the times sent are in unix timestamps. My query converts these to regular time, albeit hard-coded to Eastern Daylight Time.
- I believe all the fields I'm using in this are standard HubSpot fields, but there is a possibility there are some custom ones. If so, then you may get an error and have to remove those.
- I use two HubSpot API endpoints here, the first one that gives me a list of ALL DEALS with their dealId is https://api.hubapi.com/deals/v1/deal/paged. The second one that gives me individual deal properties is https://api.hubapi.com/deals/v1/deal/[dealId].
- Be aware that depending on your number of DEALS, this can take a really long time to execute. It is definitely not "efficient" and is not optimized in any way. I have over 4,000 deals and it takes maybe a half-hour or so.
- Also, be aware that HubSpot has a daily limit on API calls of about 40,000. So running this query with 4,000 deals will eat up a lot of your API calls.
- I believe I can solve #7 and #8 above by using some combination of two things to optimize this query:
- Specifying specific PROPERTIES in the initial API call may prevent me from having to make a separate call for each individual deal, if I know the exact properties I want
- Instead of using the above API endpoint for ALL DEALS, I can use the one for RECENTLY CREATED DEALS and specify a start date. This endpoint is https://api.hubapi.com/deals/v1/recent/created.
At any rate, with all the above caveats, here is the query which works for me and includes all the expanded tables/fields to get to the individual values.
None of the other solutions worked for me, but I used several of them to get to this point. Thanks to everyone for your help.
If I am able to speed this up using the items in #10 above, I will post that solution as well.
let // Start Values Pagination = List.Skip(List.Generate( () => [hsOffset = 0, Counter = 0, isMore = true], // Condition under which the next execution will happen each [isMore] <> false, // retrieve results per call each [WebCall = Json.Document(Web.Contents("https://api.hubapi.com/deals/v1/deal/paged?hapikey=[ENTER-YOUR-API-KEY-HERE]&limit=250&offset=" & Text.From(hsOffset))), hsOffset = try [WebCall][offset] otherwise 0, isMore = if [Counter] <1 then null else [WebCall][#"hasMore"], // internal counter Counter = [Counter] + 1 // ,Table = Table.FromRecords(WebCall[deals]) ] // ,each [Table] ),1), #"Converted to Table1" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table1", "Column1", {"WebCall", "hsOffset", "isMore", "Counter"}, {"Column1.WebCall", "Column1.hsOffset", "Column1.isMore", "Column1.Counter"}), #"Expanded Column1.WebCall" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1.WebCall", {"deals", "hasMore", "offset"}, {"Column1.WebCall.deals", "Column1.WebCall.hasMore", "Column1.WebCall.offset"}), #"Expanded Column1.WebCall.deals" = Table.ExpandListColumn(#"Expanded Column1.WebCall", "Column1.WebCall.deals"), #"Expanded Column1.WebCall.deals1" = Table.ExpandRecordColumn(#"Expanded Column1.WebCall.deals", "Column1.WebCall.deals", {"dealId", "isDeleted"}, {"Column1.WebCall.deals.dealId", "Column1.WebCall.deals.isDeleted"}), // THIS IS THE SECOND API CALL FOR DEAL DETAILS #"Added Custom" = Table.AddColumn(#"Expanded Column1.WebCall.deals1", "DealInfo", each Json.Document(Web.Contents("https://api.hubapi.com/deals/v1/deal/" & Text.From([Column1.WebCall.deals.dealId]) & "?hapikey=[ENTER-YOUR-API-KEY-HERE]"))), #"Changed Type" = Table.TransformColumnTypes(#"Added Custom",{{"Column1.WebCall.offset", Int64.Type}, {"Column1.WebCall.hasMore", type logical}}), #"Expanded DealInfo" = Table.ExpandRecordColumn(#"Changed Type", "DealInfo", {"properties"}, {"DealInfo.properties"}), #"Expanded DealInfo.properties" = Table.ExpandRecordColumn(#"Expanded DealInfo", "DealInfo.properties", {"dealname", "createdate", "hubspot_owner_id", "hs_analytics_source", "deal_temperature", "hs_createdate", "dealtype", "closed_lost_reason", "closedate", "pipeline", "notes_last_contacted", "dealstage", "hs_analytics_source_data_2", "hs_analytics_source_data_1", "amount"}, {"DealInfo.properties.dealname", "DealInfo.properties.createdate", "DealInfo.properties.hubspot_owner_id", "DealInfo.properties.hs_analytics_source", "DealInfo.properties.deal_temperature", "DealInfo.properties.hs_createdate", "DealInfo.properties.dealtype", "DealInfo.properties.closed_lost_reason", "DealInfo.properties.closedate", "DealInfo.properties.pipeline", "DealInfo.properties.notes_last_contacted", "DealInfo.properties.dealstage", "DealInfo.properties.hs_analytics_source_data_2", "DealInfo.properties.hs_analytics_source_data_1", "DealInfo.properties.amount"}), #"Expanded DealInfo.properties.dealstage" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties", "DealInfo.properties.dealstage", {"value"}, {"DealInfo.properties.dealstage.value"}), #"Expanded DealInfo.properties.hs_analytics_source_data_2" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.dealstage", "DealInfo.properties.hs_analytics_source_data_2", {"value"}, {"DealInfo.properties.hs_analytics_source_data_2.value"}), #"Expanded DealInfo.properties.hs_analytics_source_data_1" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.hs_analytics_source_data_2", "DealInfo.properties.hs_analytics_source_data_1", {"value"}, {"DealInfo.properties.hs_analytics_source_data_1.value"}), #"Expanded DealInfo.properties.amount" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.hs_analytics_source_data_1", "DealInfo.properties.amount", {"value"}, {"DealInfo.properties.amount.value"}), #"Expanded DealInfo.properties.notes_last_contacted" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.amount", "DealInfo.properties.notes_last_contacted", {"value"}, {"DealInfo.properties.notes_last_contacted.value"}), #"Expanded DealInfo.properties.pipeline" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.notes_last_contacted", "DealInfo.properties.pipeline", {"value"}, {"DealInfo.properties.pipeline.value"}), #"Expanded DealInfo.properties.closedate" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.pipeline", "DealInfo.properties.closedate", {"value"}, {"DealInfo.properties.closedate.value"}), #"Expanded DealInfo.properties.closed_lost_reason" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.closedate", "DealInfo.properties.closed_lost_reason", {"value"}, {"DealInfo.properties.closed_lost_reason.value"}), #"Expanded DealInfo.properties.dealtype" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.closed_lost_reason", "DealInfo.properties.dealtype", {"value"}, {"DealInfo.properties.dealtype.value"}), #"Expanded DealInfo.properties.hs_createdate" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.dealtype", "DealInfo.properties.hs_createdate", {"value"}, {"DealInfo.properties.hs_createdate.value"}), #"Expanded DealInfo.properties.deal_temperature" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.hs_createdate", "DealInfo.properties.deal_temperature", {"value"}, {"DealInfo.properties.deal_temperature.value"}), #"Expanded DealInfo.properties.hs_analytics_source" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.deal_temperature", "DealInfo.properties.hs_analytics_source", {"value"}, {"DealInfo.properties.hs_analytics_source.value"}), #"Expanded DealInfo.properties.hubspot_owner_id" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.hs_analytics_source", "DealInfo.properties.hubspot_owner_id", {"value"}, {"DealInfo.properties.hubspot_owner_id.value"}), #"Expanded DealInfo.properties.createdate" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.hubspot_owner_id", "DealInfo.properties.createdate", {"value"}, {"DealInfo.properties.createdate.value"}), #"Expanded DealInfo.properties.dealname" = Table.ExpandRecordColumn(#"Expanded DealInfo.properties.createdate", "DealInfo.properties.dealname", {"value"}, {"DealInfo.properties.dealname.value"}), #"Changed Type1" = Table.TransformColumnTypes(#"Expanded DealInfo.properties.dealname",{{"DealInfo.properties.createdate.value", Int64.Type}}), #"Changed Type2" = Table.TransformColumnTypes(#"Changed Type1",{{"DealInfo.properties.closedate.value", Int64.Type}, {"DealInfo.properties.notes_last_contacted.value", Int64.Type}, {"DealInfo.properties.hs_createdate.value", Int64.Type}}), #"Added Custom1" = Table.AddColumn(#"Changed Type2", "Create.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, -4, 0, [DealInfo.properties.createdate.value]/1000)), #"Changed Type3" = Table.TransformColumnTypes(#"Added Custom1",{{"Create.DateTime", type datetime}}) in #"Changed Type3"- AnonymousNot applicable
I am looking for a pagination solution too, and I am almost there but not quite....!
here is my code (and it feels very close):
let baseURL = "https://api.vworkapp.com/v4/jobs.xml", apiKey = "?api_key=xxxxxxxx", params = "&start_at=2018-10-15&end_at=2018-10-16", perPage = "&per_page=200", queryURL = baseURL & apiKey & params & perPage, PreFetchData = Xml.Tables(Web.Contents(queryURL)), PageCount = Number.FromText(PreFetchData{0}[#"Attribute:total_pages"]), GetPageData = (Index) => let XMLData = Xml.Tables(Web.Contents(queryURL & "&page=" & Index)) in XMLData, Data = List.Generate ( () => [i=1], each [i] < PageCount, each [ i=[i]+1, ResList = GetPageData( Number.ToText([i]) ) ], each [ResList] ) in DataWhat this gets me is:
The Table records are spot on, I can expand those and they are exactly what I want, but there is an error in the first list item and I can't figure out why:
I can't seem to get past this error, can anyone put me on the right track to solving why i=1 would be a problem? The api accepts page=1 so I don't see what would be wrong....
EDIT: I have skipped over that row and everything else after that works as expected, so if I solve why i=1 is an issue then I am done!
- AnonymousNot applicable
Anonymous looks to me that you are making it too complex...i think , in your case, u can paginate simply by calling a function. See this page
http://sqlcodespace.blogspot.com/2017/09/power-bipower-query-api-response.html
- gotmikeFrequent Visitor
Actually, I was able to clean this up and use a single API call to get all the needed properties.
The code below makes one single API call that executes quickly and returns most if not all of the standard HubSpot properties.
It also converts all the Unix Epoch timestamps to regular DateTime values.
And, it assigns the correct data type to each column, so it will sort/filter and play nice in reports.
I think this is ultimately what I was after.
I hope this helps someone else looking to use Power BI for HubSpot and/or anyone running into this pagination issue.
let // ENTER API KEY HERE apiKey = "[API-KEY-GOES-HERE]", // GO THROUGH PAGINATION Pagination = List.Skip(List.Generate( () => [hsOffset = 0, Counter = 0, isMore = true], // Condition under which the next execution will happen each [isMore] <> false, // retrieve results per call each [WebCall = Json.Document(Web.Contents("https://api.hubapi.com/deals/v1/deal/paged" & "?hapikey=" & myApiKey & "&limit=250" & "&properties=notes_last_updated" & "&properties=dealname" & "&properties=amount" & "&properties=closedate" & "&properties=num_associated_contacts" & "&properties=createdate" & "&properties=pipeline" & "&properties=hubspot_owner_id" & "&properties=num_contacted_notes" & "&properties=hs_lastmodifieddate" & "&properties=hs_analytics_source" & "&properties=notes_last_contacted" & "&properties=hubspot_owner_assigneddate" & "&properties=deal_temperature" & "&properties=dealstage" & "&properties=hs_createdate" & "&properties=hs_object_id" & "&properties=hs_analytics_source_data_2" & "&properties=hs_analytics_source_data_1" & "&properties=num_notes" & "&properties=dealtype" & "&offset=" & Text.From(hsOffset))), hsOffset = try [WebCall][offset] otherwise 0, isMore = if [Counter] <1 then null else [WebCall][#"hasMore"], // internal counter Counter = [Counter] + 1
] ),1),
#"Converted to Table" = Table.FromList(Pagination, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"WebCall"}, {"Column1.WebCall"}), #"Expanded Column1.WebCall" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1.WebCall", {"deals"}, {"Column1.WebCall.deals"}), #"Expanded Column1.WebCall.deals" = Table.ExpandListColumn(#"Expanded Column1.WebCall", "Column1.WebCall.deals"), #"Expanded Column1.WebCall.deals1" = Table.ExpandRecordColumn(#"Expanded Column1.WebCall.deals", "Column1.WebCall.deals", {"dealId", "isDeleted", "properties"}, {"Column1.WebCall.deals.dealId", "Column1.WebCall.deals.isDeleted", "Column1.WebCall.deals.properties"}), #"Expanded Column1.WebCall.deals.properties" = Table.ExpandRecordColumn(#"Expanded Column1.WebCall.deals1", "Column1.WebCall.deals.properties", {"notes_last_updated", "dealname", "closedate", "num_associated_contacts", "createdate", "pipeline", "hubspot_owner_id", "num_contacted_notes", "hs_lastmodifieddate", "hs_analytics_source", "notes_last_contacted", "hubspot_owner_assigneddate", "deal_temperature", "dealstage", "hs_createdate", "hs_object_id", "hs_analytics_source_data_2", "hs_analytics_source_data_1", "num_notes", "dealtype", "amount"}, {"notes_last_updated", "dealname", "closedate", "num_associated_contacts", "createdate", "pipeline", "hubspot_owner_id", "num_contacted_notes", "hs_lastmodifieddate", "hs_analytics_source", "notes_last_contacted", "hubspot_owner_assigneddate", "deal_temperature", "dealstage", "hs_createdate", "hs_object_id", "hs_analytics_source_data_2", "hs_analytics_source_data_1", "num_notes", "dealtype", "amount"}), #"Expanded notes_last_updated1" = Table.ExpandRecordColumn(#"Expanded Column1.WebCall.deals.properties", "notes_last_updated", {"value"}, {"notes_last_updated.value"}), #"Expanded dealname" = Table.ExpandRecordColumn(#"Expanded notes_last_updated1", "dealname", {"value"}, {"dealname.value"}), #"Expanded closedate" = Table.ExpandRecordColumn(#"Expanded dealname", "closedate", {"value"}, {"closedate.value"}), #"Changed Type" = Table.TransformColumnTypes(#"Expanded closedate",{{"closedate.value", Int64.Type}, {"notes_last_updated.value", Int64.Type}}), #"Expanded num_associated_contacts" = Table.ExpandRecordColumn(#"Changed Type", "num_associated_contacts", {"value"}, {"num_associated_contacts.value"}), #"Changed Type1" = Table.TransformColumnTypes(#"Expanded num_associated_contacts",{{"num_associated_contacts.value", Int64.Type}}), #"Expanded createdate" = Table.ExpandRecordColumn(#"Changed Type1", "createdate", {"value"}, {"createdate.value"}), #"Expanded pipeline" = Table.ExpandRecordColumn(#"Expanded createdate", "pipeline", {"value"}, {"pipeline.value"}), #"Changed Type2" = Table.TransformColumnTypes(#"Expanded pipeline",{{"createdate.value", Int64.Type}}), #"Expanded hubspot_owner_id" = Table.ExpandRecordColumn(#"Changed Type2", "hubspot_owner_id", {"value"}, {"hubspot_owner_id.value"}), #"Changed Type3" = Table.TransformColumnTypes(#"Expanded hubspot_owner_id",{{"hubspot_owner_id.value", Int64.Type}}), #"Expanded num_contacted_notes" = Table.ExpandRecordColumn(#"Changed Type3", "num_contacted_notes", {"value"}, {"num_contacted_notes.value"}), #"Changed Type4" = Table.TransformColumnTypes(#"Expanded num_contacted_notes",{{"num_contacted_notes.value", Int64.Type}}), #"Expanded hs_lastmodifieddate" = Table.ExpandRecordColumn(#"Changed Type4", "hs_lastmodifieddate", {"value"}, {"hs_lastmodifieddate.value"}), #"Changed Type5" = Table.TransformColumnTypes(#"Expanded hs_lastmodifieddate",{{"hs_lastmodifieddate.value", Int64.Type}}), #"Expanded hs_analytics_source" = Table.ExpandRecordColumn(#"Changed Type5", "hs_analytics_source", {"value"}, {"hs_analytics_source.value"}), #"Expanded notes_last_contacted" = Table.ExpandRecordColumn(#"Expanded hs_analytics_source", "notes_last_contacted", {"value"}, {"notes_last_contacted.value"}), #"Changed Type6" = Table.TransformColumnTypes(#"Expanded notes_last_contacted",{{"notes_last_contacted.value", Int64.Type}}), #"Expanded hubspot_owner_assigneddate" = Table.ExpandRecordColumn(#"Changed Type6", "hubspot_owner_assigneddate", {"value"}, {"hubspot_owner_assigneddate.value"}), #"Changed Type7" = Table.TransformColumnTypes(#"Expanded hubspot_owner_assigneddate",{{"hubspot_owner_assigneddate.value", Int64.Type}}), #"Expanded deal_temperature" = Table.ExpandRecordColumn(#"Changed Type7", "deal_temperature", {"value"}, {"deal_temperature.value"}), #"Expanded dealstage" = Table.ExpandRecordColumn(#"Expanded deal_temperature", "dealstage", {"value"}, {"dealstage.value"}), #"Expanded hs_createdate" = Table.ExpandRecordColumn(#"Expanded dealstage", "hs_createdate", {"value"}, {"hs_createdate.value"}), #"Changed Type8" = Table.TransformColumnTypes(#"Expanded hs_createdate",{{"hs_createdate.value", Int64.Type}}), #"Expanded hs_object_id" = Table.ExpandRecordColumn(#"Changed Type8", "hs_object_id", {"value"}, {"hs_object_id.value"}), #"Changed Type9" = Table.TransformColumnTypes(#"Expanded hs_object_id",{{"hs_object_id.value", Int64.Type}}), #"Expanded hs_analytics_source_data_2" = Table.ExpandRecordColumn(#"Changed Type9", "hs_analytics_source_data_2", {"value"}, {"hs_analytics_source_data_2.value"}), #"Expanded hs_analytics_source_data_1" = Table.ExpandRecordColumn(#"Expanded hs_analytics_source_data_2", "hs_analytics_source_data_1", {"value"}, {"hs_analytics_source_data_1.value"}), #"Expanded num_notes" = Table.ExpandRecordColumn(#"Expanded hs_analytics_source_data_1", "num_notes", {"value"}, {"num_notes.value"}), #"Changed Type10" = Table.TransformColumnTypes(#"Expanded num_notes",{{"num_notes.value", Int64.Type}}), #"Expanded dealtype" = Table.ExpandRecordColumn(#"Changed Type10", "dealtype", {"value"}, {"dealtype.value"}), #"Expanded amount" = Table.ExpandRecordColumn(#"Expanded dealtype", "amount", {"value"}, {"amount.value"}), #"Changed Type11" = Table.TransformColumnTypes(#"Expanded amount",{{"amount.value", type number}}), #"Added Custom" = Table.AddColumn(#"Changed Type11", "CloseDate.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [closedate.value]/1000)), #"Changed Type12" = Table.TransformColumnTypes(#"Added Custom",{{"CloseDate.DateTime", type datetime}}), #"Added Custom1" = Table.AddColumn(#"Changed Type12", "CreateDate.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [createdate.value]/1000)), #"Added Custom2" = Table.AddColumn(#"Added Custom1", "LastModified.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [hs_lastmodifieddate.value]/1000)), #"Added Custom3" = Table.AddColumn(#"Added Custom2", "LastContacted.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [notes_last_contacted.value]/1000)), #"Replaced Errors" = Table.ReplaceErrorValues(#"Added Custom3", {{"LastContacted.DateTime", null}}), #"Added Custom4" = Table.AddColumn(#"Replaced Errors", "OwnerAssigned.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [hubspot_owner_assigneddate.value]/1000)), #"Added Custom5" = Table.AddColumn(#"Added Custom4", "HsCreateDate.DateTime", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [hs_createdate.value]/1000)), #"Changed Type13" = Table.TransformColumnTypes(#"Added Custom5",{{"CreateDate.DateTime", type datetime}, {"LastModified.DateTime", type datetime}, {"LastContacted.DateTime", type datetime}, {"OwnerAssigned.DateTime", type datetime}, {"HsCreateDate.DateTime", type datetime}}) in #"Changed Type13"