Vai al contenuto principale

Funzioni Power Query e query di esempio

Combinare le funzioni esposte da Cognite Data Fusion (REST API) connector for Power BI con Power Query per recuperare e trasformare i dati con Cognite API per creare report e dashboard con Microsoft Power BI e Microsoft Excel.

Beta

Le funzionalità descritte in questa sezione sono attualmente in beta testing presso alcuni clienti, pertanto sono soggette a modifiche.

Copiare e adattare le query e le funzioni Power Query in questa pagina in base alle esigenze aziendali.

Funzioni di utilità

Conversione di timestamp da/all'epoca Unix

I tipi di risorse CDF prevedono e restituiscono timestamp utilizzando millisecondi dall'epoca Unix. Power Query non dispone di metodi per analizzare automaticamente questo formato in un tipo datetimezone per rappresentare timestamp che includono informazioni sul fuso orario. I modelli di dati CDF rappresentano timestamp utilizzando il formato ISO 8601 per campi primitivi.

Utilizzare le funzioni riportate di seguito per eseguire conversioni tra una variabile datetimezone e millisecondi dall'epoca Unix e tra una variabile datetimezone in testo nel formato ISO 8601.

ConvertDateTimeZoneToMs

Convert datetimezone to milliseconds since epoch
//
(dtz as datetimezone) as number =>
let
// Convert the input DateTimeZone to UTC
UtcDateTime = DateTimeZone.RemoveZone(DateTimeZone.SwitchZone(dtz, 0)),
// Define the Unix epoch start
UnixEpochStart = #datetime(1970, 1, 1, 0, 0, 0),
// Calculate the duration between the input date and Unix epoch start
Delta = UtcDateTime - UnixEpochStart,
// Convert duration to total milliseconds
TotalMilliseconds = Duration.TotalSeconds(Delta) * 1000
in
TotalMilliseconds

ConvertMsToDateTimeZone

Convert milliseconds since epoch to datetimezone
(ms as number) as datetimezone =>
let
// Convert ms to seconds
SecondsSinceEpoch = ms / 1000,

// Create a duration
DurationSinceEpoch = #duration(0, 0, 0, SecondsSinceEpoch),

// Add duration to Unix epoch start to get UTC datetime
UnixEpochStart = #datetime(1970, 1, 1, 0, 0, 0),
UtcDateTime = UnixEpochStart + DurationSinceEpoch,

// Convert UTC datetime to local time zone
LocalDateTimeZone = DateTimeZone.From(UtcDateTime)
in
LocalDateTimeZone

ConvertDateTimeZoneToIso

Convert DateTimeZone to ISO 8601 text representation
(dtz as datetimezone) as text =>
let
// Use DateTimeZone.ToText with ISO 8601 format
Result = DateTimeZone.ToText(dtz, [Format="yyyy-MM-ddTHH:mm:sszzz", Culture="en-US"])
in
Result

Aggiunta di una funzione

Per aggiungere una nuova funzione in Power Query, selezionare Get Data > Blank Query e scrivere la funzione oppure copiare una delle funzioni riportate di seguito.

Delta tempo

È comune definire timestamp di inizio e fine in base a delta tempo. I valori verranno aggiornati all'aggiornamento di un set di dati. Nell'esempio seguente, EndTime è l'ora corrente e StartTime è 7 giorni prima di EndTime. È possibile adattare questo esempio per utilizzare delta tempo diversi.

Time deltas
CurrentTime = DateTimeZone.LocalNow(),
EndTime = CurrentTime,
StartTime = CurrentTime - #duration(7, 0, 0, 0)

Richiesta GET comune

Quando si recuperano dati da CDF utilizzando la funzione GetCDF da CDF REST API connector for Power BI, è necessario utilizzare i parametri di query per passare i filtri per selezionare quali dati recuperare.

L'esempio riportato di seguito mostra come aggiungere i parametri di query externalIdPrefix e limit dall'endpoint /timeseries all'URL per filtrare dati sul lato server.

List all time series instances with an externalId starting with a specific string
let
Source = GetCDF("/timeseries?externalIdPrefix=EVE&limit=1000")
in
Source

Richiesta POST comune

Quando si recuperano dati da CDF con la funzione PostCDF, è necessario scrivere il corpo di una richiesta per selezionare quali dati recuperare. La funzione accetta una rappresentazione di testo del corpo JSON, ma è anche possibile scrivere il corpo utilizzando un tipo di dati di record Power Query, quindi convertirlo in un tipo di dati di testo JSON prima di passare il valore alla funzione PostCDF.

List all data modeling instances for a view using the DMS API
let
SpaceExternalId = "Geography",
ViewExternalId = "City",
ViewVersion = "1",
Body = [
sources = {
[
source = [
type = "view",
space = SpaceExternalId,
externalId = ViewExternalId,
version = ViewVersion
]
]
},
limit = 1000
],
BodyText = Text.FromBinary(Json.FromValue(Body)),
Source = PostCDF("/models/instances/list", BodyText)
in
Source

In alternativa, è possibile scrivere manualmente il corpo POST come testo, ma è necessario aggiungere caratteri di escape davanti alle virgolette doppie (") con un altro set di virgolette doppie:

let
BodyText = "{""sources"": [{""source"": {""type"": ""view"", ""space"": ""Geography"", ""externalId"": ""City"", ""version"": ""1""}}], ""limit"": 1000}",
Source = PostCDF("/models/instances/list", BodyText)
in
Source

Se è necessario riutilizzare una richiesta POST, è possibile trasformarla in una funzione Power Query. Ad esempio:

(SpaceExternalId as text, ViewExternalId as text, ViewVersion as text) as table =>
let
Body = [
sources = {
[
source = [
type = "view",
space = SpaceExternalId,
externalId = ViewExternalId,
version = ViewVersion
]
]
},
limit = 1000
],
BodyText = Text.FromBinary(Json.FromValue(Body)),
Source = PostCDF("/models/instances/list", BodyText)
in
Source

È possibile definire il nome della funzione facendo clic con il pulsante destro del mouse sulla voce nell'elenco di query nell'editor di Power Query e selezionando Rename.

Se la funzione precedente ha il nome ListInstancesDMS, è possibile utilizzarla in una nuova query immettendo i valori dei campi in Power Query o scrivendo una nuova query:

let
Source = ListInstancesDMS("Geography", "City", "1")
in
Source

Richieste GraphQL

Quando si recuperano dati da CDF con la funzione GraphQL, è necessario scrivere una richiesta GraphQL per selezionare quali dati recuperare da un modello di dati specifico. La funzione si aspetta che l'utente specifichi l'ID esterno dello space, l'ID esterno della view, la versione della view, la query GraphQL da eseguire e, facoltativamente, un set di variabili da utilizzare nella query.

La query seguente utilizza la sintassi GraphQL e passa le variabili come testi JSON. L'utilizzo delle variabili nella query agevola la parametrizzazione e l'utilizzo con valori esterni.

List all work orders with an end date greater than a specific date
let
Source = GraphQL(
"cdf_idm",
"CogniteProcessIndustries",
"v1",
"query MyQuery($cursor: String, $endTime: Timestamp) {#(lf) listCogniteMaintenanceOrder(#(lf) first: 1000#(lf) after: $cursor#(lf) filter: {endTime: {gte: $endTime}}#(lf) ) {#(lf) items {#(lf) name#(lf) type#(lf) startTime#(lf) endTime#(lf) priority#(lf) }#(lf) pageInfo {#(lf) endCursor#(lf) hasNextPage#(lf) }#(lf) }#(lf)}",
"{""endTime"": ""2024-10-01T00:00:00+02:00""}"
)
in
Source

Il linguaggio M utilizzato Power Query non supporta attualmente stringhe multiriga, pertanto la query deve trovarsi su una sola riga. #(lf) rappresenta il carattere di interruzione riga. Nel precedente esempio, la query è stata incollata nel campo dell'area di testo in Power Query e le variabili sono state passate come testo JSON. Si noti come Power BI ha aggiunto le interruzioni di riga alla query originale e l'ha espressa come variabile di testo su una sola riga.

In alternativa, è possibile scrivere la query come più testi a riga singola e utilizzare la funzione Text.Combine per aggiungere le interruzioni di riga alla query. È possibile definire le variabili come record Power Query e convertirle in testo JSON prima di passarle alla funzione GraphQL. Ad esempio, vedere come la funzione ConvertDateTimeZoneToIso converte una variabile datetimezone in una rappresentazione di testo in formato ISO 8601 e la passa come variabile alla query.

let
// This could be a parameter or referenced from another query
EndTime = #datetimezone(2024, 10, 1, 0, 0, 0, 2, 0),
VariablesRecord = [
endTime = ConvertDateTimeZoneToIso(EndTime)
],
VariablesText = Text.FromBinary(Json.FromValue(VariablesRecord)),
Query = Text.Combine({
"query MyQuery($cursor: String, $endTime: Timestamp) {",
" listCogniteMaintenanceOrder(",
" first: 1000",
" after: $cursor",
" filter: {endTime: {gte: $endTime}}",
" ) {",
" items {",
" name",
" type",
" startTime",
" endTime",
" priority",
" }",
" pageInfo {",
" endCursor",
" hasNextPage",
" }",
" }",
"}"
}, "#(lf)"),
Data = GraphQL(
"cdf_idm",
"CogniteProcessIndustries",
"v1",
Query,
VariablesText
)
in
Data

Esempi avanzati

A seconda della forma della risposta dell'API di Cognite, potrebbero essere necessarie trasformazioni Power Query aggiuntive per recuperare e trasformare dati. Copiare e adattare gli esempi seguenti in base alle esigenze aziendali.

Recupero di righe in sequenza con la funzione PostCDF

La funzione Power Query seguente recupera ed elabora i dati di righe in sequenza per un ID esterno in sequenza da CDF. Invia una richiesta POST, estrae informazioni di colonne, espande i dati di risposta annidati e li riorganizza in formato tabulare. La funzione gestisce la conversione dei tipi di dati, rimuove i campi non necessari e raggruppa i dati in base alla riga. L'output finale è una tabella ben strutturata con colonne correttamente tipizzate.

Fetch sequence rows with PostCDF
(externalId as text) as table =>
let
RequestBody = "{""externalId"": """ & externalId & """, ""limit"": 10000}",
Response = PostCDF("/sequences/data/list", RequestBody),
// Extract columns information from the first page
FirstPage = Response{0},
Columns = FirstPage[columns],
ColumnNames = List.Transform(Columns, each [externalId]),
ColumnTypes = List.Transform(Columns, each
if [valueType] = "STRING" then type text else
if [valueType] = "DOUBLE" then type number else
if [valueType] = "LONG" then Int64.Type
else type any
),
// Extract the 'values' from each row
Rows = Table.ExpandListColumn(Response, "rows"),
ValuesTable = Table.ExpandRecordColumn(Rows, "rows", {"rowNumber", "values"}, {"rows.rowNumber", "rows.values"}),
RemoveColumns = Table.RemoveColumns(ValuesTable,{"id", "externalId", "columns", "nextCursor"}),
ExpandValues = Table.ExpandListColumn(RemoveColumns, "rows.values"),
// Group by rowNumber and create a record for each row
GroupedRows = Table.Group(ExpandValues, {"rows.rowNumber"}, {
{"RowData", (t) => Record.FromList(t[rows.values], ColumnNames)}
}),
// Expand the RowData column
ExpandRows = Table.ExpandRecordColumn(GroupedRows, "RowData", ColumnNames),
// Set column data types
FinalTable = Table.TransformColumnTypes(ExpandRows, List.Zip({ColumnNames, ColumnTypes}))
in
FinalTable

Per utilizzare la funzione:

let
Source = RetrieveSequenceRows("sequence-externalId")
in
Source

Recupero di istanze dall'endpoint delle query DMS con la funzione PostCDF

La funzione Power Query recupera ed elabora le istanze di modellazione dei dati per una query DMS. Esegue l'impaginazione attraverso la risposta, estrae le istanze ed espande i dati annidati.

Fetch instances from the DMS query endpoint with PostCDF
(query as text) as table =>
let
FetchPage = (query as text, optional cursors as nullable record) as table =>
let
Query = Json.Document(query),
UpdatedQuery =
if cursors <> null then
let
// Get all field names of both records
QueryWithFields = Record.FieldNames(Query[with]),
QUerySelectFields = Record.FieldNames(Query[select]),
CursorsFields = Record.FieldNames(cursors),
// Find the intersection of field names
CommonFields = List.Intersect({QueryWithFields, QUerySelectFields, CursorsFields}),
// Create new records containing only the common fields
UpdatedQueryWithAndSelect = Record.TransformFields(
Query,
{
{"with", each Record.SelectFields(_, CommonFields)},
{"select", each Record.SelectFields(_, CommonFields)}
}
)
in
UpdatedQueryWithAndSelect
else
Query,
// Add cursors if they are provided
UpdatedQueryWithCursors =
if cursors <> null then
Record.AddField(UpdatedQuery, "cursors", cursors)
else
UpdatedQuery,
FinalBody = Text.FromBinary(Json.FromValue(UpdatedQueryWithCursors)),
Response = PostCDF("/models/instances/query", FinalBody)
in
Response,
// Helper function to create next cursor record from result table
CreateNextCursorRecordFromTable = (inputTable as table) as record =>
let
RecordsList = List.Transform(
Table.ToRecords(inputTable), each Record.FromList({[nextCursor]}, {[resultExpression]})
),
CombinedRecord = Record.Combine(RecordsList)
in
CombinedRecord,
// Helper function to check if all cursors are null
AllCursorsNull = (cursorsRecord as record) as logical =>
let
CursorValues = Record.ToList(cursorsRecord),
NullCount = List.Count(List.Select(CursorValues, each _ = null))
in
NullCount = List.Count(CursorValues),
// Helper function to aggregate items from all pages and convert to tables
AggregateResults = (results as list) as table =>
let
// Combine all tables
CombinedTable = Table.Combine(results),
// Group by resultExpression and convert items to tables
GroupedTable = Table.Group(
CombinedTable,
{"resultExpression"},
{
{
"items",
each
Table.FromRecords(
List.Combine(List.Transform([items], each if Value.Is(_, type list) then _ else {
_
}))
),
type table
}
}
)
in
GroupedTable,
// Main pagination logic
FetchAllPages = () as list =>
let
// Initialize accumulator
InitialAcc = [
results = {},
currentCursors = null,
hasMore = true
],
// Pagination function
PaginationFunction = (acc as record) =>
let
CurrentPage = FetchPage(query, acc[currentCursors]),
NextCursors = CreateNextCursorRecordFromTable(CurrentPage),
HasMoreResults = not AllCursorsNull(NextCursors) and Table.RowCount(CurrentPage) > 0,
UpdatedResults = List.Combine({acc[results], {CurrentPage}})
in
[
results = UpdatedResults,
currentCursors = NextCursors,
hasMore = HasMoreResults
],
// Keep fetching until no more results
AllResults = List.Generate(
() => InitialAcc, each _[hasMore], each PaginationFunction(_), each _[results]
),
// Get the final list of results
FinalResults = List.Last(AllResults)
in
FinalResults,
// Execute pagination and combine results
AllPages = FetchAllPages(),
FinalTable = AggregateResults(AllPages)
in
FinalTable

Per utilizzare la funzione:

let
Query = [
with = [
cities = [
nodes = [
filter = [
hasData = {
[
space = "Geography",
externalId = "City",
version = "1",
#"type" = "view"
]
}
],
chainTo = "destination",
direction = "outwards"
]
],
countries = [
nodes = [
filter = [
hasData = {
[
space = "Geography",
externalId = "Country",
version = "1",
#"type" = "view"
]
}
],
chainTo = "destination",
direction = "outwards"
]
]
],
select = [
cities = [
sources = {
[
source = [
space = "Geography",
externalId = "City",
version = "1",
#"type" = "view"
],
properties = {
"name"
}
]
}
],
countries = [
sources = {
[
source = [
space = "Geography",
externalId = "Country",
version = "1",
#"type" = "view"
],
properties = {
"name"
}
]
}
]
]
],
QueryText = Text.FromBinary(Json.FromValue(Query)),
Source = QueryDMS(QueryText)
in
Source
suggerimento

Aggiungere filtri come il filtro hasData nel precedente esempio per evitare di recuperare tutte le istanze da CDF.

Recupero di punti dati delle serie temporali con la funzione PostCDF

La funzione Power Query seguente recupera ed elabora i punti dati delle serie temporali aggregate per una serie temporale in un intervallo di tempo. Converte gli input del fuso orario locale in UTC per le richieste API CDF, supporta più aggregati e la granularità personalizzata, quindi gestisce l'impaginazione dei dati. La funzione converte quindi i timestamp UTC restituiti nel fuso orario locale, espande la risposta API annidata e genera una tabella formattata correttamente con colonne tipizzate in modo appropriato. Include anche timestamp locali e valori aggregati decimali.

L'esempio utilizza le funzioni ConvertDateTimeZoneToMs e ConvertMsToDateTimeZone per convertire i timestamp.

(
item as record,
start as datetimezone,
optional end as nullable datetimezone,
optional aggregates as nullable text,
optional granularity as nullable text,
optional targetUnit as nullable text,
optional targetUnitSystem as nullable text,
optional timeZone as nullable text
) =>
let
// Function to detect query type based on item record structure
DetectQueryType = (item as record) =>
let
Fields = Record.FieldNames(item),
HasId = List.Contains(Fields, "id"),
HasExternalId = List.Contains(Fields, "externalId"),
HasSpace = List.Contains(Fields, "space"),
FieldCount = List.Count(Fields),
QueryType =
if HasId and not HasExternalId and not HasSpace and FieldCount = 1 then
"id"
else if HasExternalId and not HasId and not HasSpace and FieldCount = 1 then
"externalId"
else if HasExternalId and HasSpace and not HasId and FieldCount = 2 then
"instanceId"
else
Error.Record(
"Invalid item content", "The item record does not match any supported query type", item
)
in
QueryType,
// Detect query type
queryType = DetectQueryType(item),
// Determine limit based on presence of aggregates
limit = if aggregates <> null then 10000 else 100000,
// Convert aggregates from comma-separated string to list format accepted by the API
AggregatesList = Text.Split(aggregates, ","),
AggregatesTrimmedList = List.Transform(AggregatesList, each Text.Trim(_)),
StartMs = Number.Round(ConvertDateTimeZoneToMs(start)),
EndMs = Number.Round(ConvertDateTimeZoneToMs(end)),
// Function to fetch a single page of data
FetchPage = (cursor as nullable text) =>
let
// Build body item
bodyItem =
if queryType = "id" then
[id = Record.Field(item, "id")]
& (if targetUnit <> null then [targetUnit = targetUnit] else [])
& (if targetUnitSystem <> null then [targetUnitSystem = targetUnitSystem] else [])
& (if cursor <> null then [cursor = cursor] else [])
else if queryType = "externalId" then
[externalId = Record.Field(item, "externalId")]
& (if targetUnit <> null then [targetUnit = targetUnit] else [])
& (if targetUnitSystem <> null then [targetUnitSystem = targetUnitSystem] else [])
& (if cursor <> null then [cursor = cursor] else [])
else if queryType = "instanceId" then
[ instanceId = [ space = Record.Field(item, "space"), externalId = Record.Field(item, "externalId") ] ]
& (if targetUnit <> null then [targetUnit = targetUnit] else [])
& (if targetUnitSystem <> null then [targetUnitSystem = targetUnitSystem] else [])
& (if cursor <> null then [cursor = cursor] else [])
else
error "Invalid query type",
// Build request body
body = [
items = {bodyItem},
limit = limit,
ignoreUnknownIds = true,
start = Text.From(StartMs)
]
& (if end <> null then [end = Text.From(EndMs)] else [])
& (if aggregates <> null then [aggregates = AggregatesTrimmedList] else [])
& (if granularity <> null then [granularity = granularity] else [])
& (if timeZone <> null then [timeZone = timeZone] else []),
Response = PostCDF("/timeseries/data/list", Text.FromBinary(Json.FromValue(body))),
// Try to fetch the cursor from the first item in the response
FirstItem =
if Type.Is(Value.Type(Response), type table) and Table.RowCount(Response) > 0 then
Table.First(Response)
else
null,
NextCursor = if FirstItem <> null then Record.FieldOrDefault(FirstItem, "nextCursor", null) else null,
// Handles empty response and extracts data points when present
FinalItemsList =
if Table.HasColumns(Response, "datapoints") then
let
// Clean up the response table
ColumnsToRemove = {"nextCursor", "isStep", "unit"},
ColumnsPresent = List.Intersect({Table.ColumnNames(Response), ColumnsToRemove}),
CleanedTable = Table.RemoveColumns(Response, ColumnsPresent),
// Expand the "datapoints" column
ExpandedDatapointsList = Table.ExpandListColumn(CleanedTable, "datapoints"),
// Handles the case where the list of "datapoints" is empty
FinalDataPointsList =
if List.NonNullCount(ExpandedDatapointsList[datapoints]) > 0 then
let
// Extract a sample record to determine available fields dynamically
SampleRecord = ExpandedDatapointsList[datapoints]{0},
AvailableFields = Record.FieldNames(SampleRecord),
// Expand the "datapoints" records using the available fields
ExpandedDatapointsRecords = Table.ExpandRecordColumn(
ExpandedDatapointsList, "datapoints", AvailableFields, AvailableFields
),
DataPointsList = Table.ToRecords(ExpandedDatapointsRecords)
in
DataPointsList
else
{}
in
FinalDataPointsList
else
Table.ToRecords(Response)
in
{FinalItemsList, NextCursor},
// Recursive function to accumulate all pages of data
AccumulateData = (cursor as nullable text, accumulatedItems as list) =>
let
CurrentPage = FetchPage(cursor),
NewItems = CurrentPage{0},
NextCursor = CurrentPage{1},
UpdatedAccumulatedItems = accumulatedItems & NewItems,
Result =
if NextCursor <> null then
@AccumulateData(NextCursor, UpdatedAccumulatedItems)
else
UpdatedAccumulatedItems
in
Result,
// Fetch all data
AllItems = AccumulateData(null, {}),
// Convert the accumulated items to a table
ConvertToTable =
if List.IsEmpty(AllItems) then
Table.FromList({}, Splitter.SplitByNothing(), null, null, ExtraValues.Error)
else
Table.FromList(AllItems, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
// Expand the table column and convert timestamps
ExpandedTable =
if not Table.IsEmpty(ConvertToTable) and Table.HasColumns(ConvertToTable, "Column1") then
let
TmpTable = Table.ExpandRecordColumn(
ConvertToTable, "Column1", Record.FieldNames(ConvertToTable{0}[Column1])
),
// timestamp should be always present when there are datapoints
FixType = Table.TransformColumnTypes(TmpTable, {{"timestamp", Int64.Type}}),
ParseTimestamp = Table.TransformColumns(FixType, {"timestamp", each ConvertMsToDateTimeZone(_)}),
ParsedWithType = Table.TransformColumnTypes(ParseTimestamp, {{"timestamp", type datetimezone}}),
// check if the timeseries is of type string
FirstEntry = ParsedWithType{0},
IsString = FirstEntry[isString],
CleanedTable = Table.RemoveColumns(ParsedWithType, {"isString"}),
// Convert aggregate/value columns to decimal number
ValuesAsDecimal =
if aggregates <> null then
Table.TransformColumnTypes(
CleanedTable, List.Transform(AggregatesTrimmedList, each {_, type number})
)
else if IsString then
CleanedTable
else
Table.TransformColumnTypes(
CleanedTable, List.Transform({"value"}, each {_, type number})
),
// Check if "id" column is present and convert to integer
IdAsInteger =
if Table.HasColumns(ValuesAsDecimal, "id") then
Table.TransformColumnTypes(ValuesAsDecimal, {{"id", Int64.Type}})
else
ValuesAsDecimal
in
IdAsInteger
else
ConvertToTable
in
ExpandedTable

La funzione è più complessa degli esempi precedenti e gestisce molti scenari diversi, con impaginazione, conversione del tipo di dati ed espansione dei dati nidificati. Per utilizzare la funzione:

let
Source = RetrieveDataPoints(
[ externalId = "EVE-TI-FORNEBU-01-3" ],
#datetimezone(2024, 10, 1, 0, 0, 0, 2, 0),
#datetimezone(2024, 10, 13, 10, 0, 0, 2, 0),
"average,max,min",
"1d",
null,
"SI",
"Europe/Oslo"
)
in
Source

In base a questa funzione, è possibile creare un'altra funzione per eseguire l'iterazione su un elenco di ID esterni di serie temporali, quindi combinare i risultati in una tabella di grandi dimensioni. L'elenco può essere una colonna in un'altra tabella, dove, ad esempio, si filtrano le serie temporali. È anche possibile adattare la funzione per eseguire l'iterazione su un elenco di ID interni o ID di istanza.

(
externalIds as list,
start as datetimezone,
end as datetimezone,
aggregates as text,
granularity as text,
optional targetUnitSystem as nullable text,
optional timeZone as nullable text
) =>
let
// Iterate over each externalId and get corresponding table
TablesList = List.Transform(
externalIds,
each RetrieveDataPoints(
[ externalId = _ ],
start,
end,
aggregates,
granularity,
null,
targetUnitSystem,
timeZone
)
),
// Combine all tables into one
CombinedTable = Table.Combine(TablesList)
in
CombinedTable

Per utilizzare la funzione:

let
Source = RetrieveDataPointsMultipleTs(
{"EVE-TI-FORNEBU-01-2", "EVE-TI-FORNEBU-01-3"},
#datetimezone(2024, 10, 1, 0, 0, 0, 2, 0),
#datetimezone(2024, 10, 13, 10, 0, 0, 2, 0),
"average,max,min",
"1d",
"SI",
"Europe/Oslo"
)
in
Source
Per ulteriori informazioni