Opastus: Suorita useita RERERATE-lauseita PowerShellillä

Tässä opetusohjelmassa käytät PowerShelliä lähettääksesi yhden Execute DAX Queries REST API -pyynnön, joka sisältää useita EVALUATE lauseita, ja sitten jäsennät monitulossetin Apache Arrow -vastauksen. Tämän kaavan avulla voit hakea useita toisiinsa liittyviä tulosjoukkoja yhdellä kierroksella PowerShell-automaatioskriptistä.

Kaavio, jossa näkyy yksi HTTP POST -pyyntö, joka sisältää kolme RERERATE-lausetta kyselyrungossaan, sekä Arrow IPC -vastauksen, joka sisältää kolme tulosjoukkoa samassa järjestyksessä.

Miksi lähettää useita RERESE-lauseita yhdessä pyynnössä

Execute DAX Queries API hyväksyy yhden query merkkijonon, joka voi sisältää useita EVALUATE lauseita. Jokainen lause palauttaa oman tulosjoukkonsa, ja vastausrunko on yhden Arrow IPC -virran yhdistäminen per EVALUATE lause määrittelyjärjestyksessä. Liittyvien kyselyjen lähettäminen yhdessä välttää erillisten HTTP-kutsujen pyynnökohtaisen ylikuormituksen, mukaan lukien ylimääräinen Microsoft Entra -tokenin validointi ja DAX-moottorin alustus. Useiden EVALUATE lauseiden lähettäminen yhdessä pyynnössä voi myös auttaa vähentämään pyyntöjen rajoittamisen vaikutuksia. Power BI rajoittaa soittajien määrän 120 kyselypyyntöön minuutissa per käyttäjä semanttisessa mallikyselyoperaatiossa.

Mitä rakennat

Yhdessä PowerShell-skriptissä sinä:

  1. Hanki Microsoft Entra access token.
  2. Rakenna pyyntörunko, joka query sisältää kolme EVALUATE lausetta.
  3. Lähetä pyyntö ja tallenna raaka Arrow IPC -vastausvirta.
  4. Jäsennä vastaus yhdeksi tulosjoukoksi per EVALUATE lause.
  5. Näytä jokainen tulosjoukko PowerShell-objekteina.

Edellytykset

  • PowerShell 7.4 tai uudempi. Windows PowerShell 5.1 ei ole tuettu, koska tässä tutoriaalissa käytetty Apache.Arrow paketti on ristiriidassa System.Memory PowerShell 5.1:n kokoonpanon kanssa.
  • Power BI-työtila Premium- tai Fabric-kapasiteetilla, jossa on vähintään yksi semanttinen malli.
  • Rakennus - ja lukuoikeudet semanttiseen malliin.
  • MicrosoftPowerBIMgmt-moduuli todennukseen. Komentokeskukset käyttävät Microsoftin ensimmäisen osapuolen Power BI -asiakassovellusta, joten sinun ei tarvitse rekisteröidä omaa sovellusta Microsoft Entra:ssa.
  • Apache.Arrow- ja Apache.Arrow.Compression .NET-kirjastot vastauksen deserialisoimiseksi. Execute DAX Queries REST API pakkaa Arrow-puskureita LZ4-kehyksen pakkauksella, joten Apache.Arrow.Compression ja sen riippuvuudet (K4os.Compression.LZ4, K4os.Compression.LZ4.Streams, K4os.Hash.xxHash, ZstdSharp.Port) ovat pakollisia. Seuraava vaihe näyttää, miten ne ladataan.
  • Seuraavat vuokralaisasetukset, jotka on käytössä Power BI:n ylläpitäjäportaalissa:
    • Dataset Execute Queries REST API ( kehittäjäasetuksissa).
    • Salli XMLA-päätepisteet ja analysoi Excel paikallisilla semanttisilla malleilla (kohdassa Integraatioasetukset).

Asenna PowerShell 7.4 tai uudempi käyttämällä wingetiä:

winget install --id Microsoft.PowerShell --source winget

Asennuksen jälkeen laukaise uusi kuori .pwsh Suorita tämän tutoriaalin jäljellä olevat komennot kyseisestä istunnosta.

Asenna MicrosoftPowerBIMgmt-moduuli. Lippu -Force hyväksyy PowerShell Gallery untrusted-repository -kehotteen.

Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser -Force

Lataa tarvittavat NuGet-paketit ja purkaa niiden kokoonpanot .C:\Tools\Apache.Arrow\ Tiedosto on ZIP-arkisto, joten Expand-Archive se .nupkg toimii suoraan sillä. Silmukka valitsee kunkin paketin korkeimman netX.0 kohdekansion, jotta kokoonpanot pysyvät yhteensopivina, kun paketit julkaisevat uusia kohteita.

$dest = "C:\Tools\Apache.Arrow"
New-Item -ItemType Directory -Force -Path $dest | Out-Null

$packages = @(
    "Apache.Arrow",
    "Apache.Arrow.Compression",
    "K4os.Compression.LZ4",
    "K4os.Compression.LZ4.Streams",
    "K4os.Hash.xxHash",
    "ZstdSharp.Port"
)

foreach ($pkg in $packages) {
    $nupkg  = Join-Path $env:TEMP "$pkg.nupkg"
    $expand = Join-Path $env:TEMP $pkg
    if (Test-Path $expand) { Remove-Item $expand -Recurse -Force }

    Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/$pkg" -OutFile $nupkg
    Expand-Archive -Path $nupkg -DestinationPath $expand -Force

    $libDirs = Get-ChildItem (Join-Path $expand "lib") -Directory
    $best = $libDirs | Where-Object { $_.Name -match "^net\d" } |
            Sort-Object Name -Descending | Select-Object -First 1
    if (-not $best) {
        $best = $libDirs | Sort-Object Name -Descending | Select-Object -First 1
    }

    Get-ChildItem (Join-Path $best.FullName "*.dll") |
        Copy-Item -Destination $dest -Force
}

1 - Autentikointi

Kirjaudu Power BI -palvelu -palveluun interaktiivisesti ja hae sitten käyttöoikeustoken. Komentojärjestelmä Connect-PowerBIServiceAccount ei vaadi oman sovelluksen rekisteröintiä Microsoft Entra:ssa.

Connect-PowerBIServiceAccount -WarningAction SilentlyContinue
$accessToken = (Get-PowerBIAccessToken).Authorization -replace '^Bearer\s+',''

2 - Rakenna pyyntö, jossa on useita RERERATE-lauseita

Määrittele työtilan ja semanttisen mallin tavoitteet. Sitten rakenna pyyntörunko. Ominaisuus query on yksittäinen merkkijono, joka sisältää kolme EVALUATE lausetta, jotka on erotettu tyhjillä viivoilla.

$groupId   = "YOUR_WORKSPACE_ID"
$datasetId = "YOUR_DATASET_ID"

$query = @"
EVALUATE
ROW("RowCount", COUNTROWS('Sales'))

EVALUATE
TOPN(10, 'Sales', 'Sales'[Amount], DESC)

EVALUATE
SUMMARIZECOLUMNS(
    'Date'[Year],
    "TotalSales", SUM('Sales'[Amount]))
"@

$body = @{
    query                  = $query
    resultsetRowcountLimit = 500000
} | ConvertTo-Json

3 - Lähetä pyyntö ja tallenna raaka vastausvirta

Lähetä POST-pyyntö ja lue vastausrunko binäärivirtana. Käytä HttpWebRequest sen sijaan, että käyttäisit Invoke-RestMethod, Invoke-PowerBIRestMethod, tai Invoke-WebRequest. Vastaus on binäärinen Arrow IPC -virta. Korkeamman tason PowerShell-komentoyksiköt tulkitsevat vastausrunkoja tekstiksi, mikä korruptoi binäärisisällön. HttpWebRequest palauttaa raakavirran muokkaamattomana.

$url = "https://api.powerbi.com/v1.0/myorg/groups/$groupId" +
       "/datasets/$datasetId/executeDaxQueries"

$request = [System.Net.HttpWebRequest]::Create($url)
$request.Method      = "POST"
$request.ContentType = "application/json"
$request.Accept      = "application/vnd.apache.arrow.stream"
$request.Timeout     = 180000   # milliseconds
$request.Headers.Add("Authorization", "Bearer $accessToken")

$bodyBytes     = [System.Text.Encoding]::UTF8.GetBytes($body)
$requestStream = $request.GetRequestStream()
$requestStream.Write($bodyBytes, 0, $bodyBytes.Length)
$requestStream.Close()

$response       = $request.GetResponse()
$responseStream = $response.GetResponseStream()

# Buffer the response into memory so the parser can iterate over multiple Arrow IPC streams.
$memoryStream = New-Object System.IO.MemoryStream
$responseStream.CopyTo($memoryStream)
$responseStream.Close()
$response.Close()
$memoryStream.Position = 0

4 - Jäsennä monitulosjoukkovaste

Vastauselimenä yhdistetään yksi Apache Arrow IPC -virta per EVALUATE lausunto. PowerShell ei toimita Arrow-jäsentimen kanssa, joten tämä vaihe lataa Apache.Arrow .NET-kirjaston pienen C#-apuvälineen kautta, joka lisätään .Add-Type Stream-loop-logiikan pitäminen C#:ssa pitää puhelupaikan lyhyenä ja palauttaa listan tulosjoukoista, joita PowerShell-skriptisi voi iteroida. Apuri avaa uuden ArrowStreamReader jokaisen virran lopun merkin jälkeen, joten sama silmukka käsittelee minkä tahansa määrän tulosjoukkoja vastauksessa.

Add-Type -Path "C:\Tools\Apache.Arrow\Apache.Arrow.dll"
Add-Type -Path "C:\Tools\Apache.Arrow\Apache.Arrow.Compression.dll"

# Reference the full .NET reference set that ships with PowerShell 7 so the
# inline C# below can resolve BCL types such as List<T> and Dictionary<,>.
$refs  = Get-ChildItem "$PSHOME\ref\*.dll" | ForEach-Object FullName
$refs += Get-ChildItem "C:\Tools\Apache.Arrow\*.dll" | ForEach-Object FullName

Add-Type -ReferencedAssemblies $refs -IgnoreWarnings -WarningAction SilentlyContinue -TypeDefinition @"
using System;
using System.Collections.Generic;
using System.IO;
using Apache.Arrow;
using Apache.Arrow.Compression;
using Apache.Arrow.Ipc;

public class DaxResultSet
{
    public List<string> ColumnNames = new List<string>();
    public List<Dictionary<string, object>> Rows =
        new List<Dictionary<string, object>>();
}

public static class DaxMultiResultReader
{
    public static List<DaxResultSet> ReadAll(Stream stream)
    {
        var results = new List<DaxResultSet>();
        var codecFactory = new CompressionCodecFactory();
        while (stream.Position < stream.Length)
        {
            var rs = new DaxResultSet();
            bool gotSchema = false;
            using (var reader = new ArrowStreamReader(stream, codecFactory, leaveOpen: true))
            {
                RecordBatch batch;
                while ((batch = reader.ReadNextRecordBatch()) != null)
                {
                    using (batch)
                    {
                        if (!gotSchema)
                        {
                            foreach (var f in batch.Schema.FieldsList)
                                rs.ColumnNames.Add(f.Name);
                            gotSchema = true;
                        }
                        for (int r = 0; r < batch.Length; r++)
                        {
                            var row = new Dictionary<string, object>();
                            for (int c = 0; c < batch.ColumnCount; c++)
                                row[rs.ColumnNames[c]] = GetValue(batch.Column(c), r);
                            rs.Rows.Add(row);
                        }
                    }
                }
            }
            if (gotSchema) results.Add(rs);
        }
        return results;
    }

    private static object GetValue(IArrowArray a, int i)
    {
        if (a == null) return null;
        if (a is DictionaryArray da)
        {
            // Resolve the dictionary index, then look up the value in the dictionary.
            int dictIndex;
            switch (da.Indices)
            {
                case Int32Array idx32: if (idx32.IsNull(i)) return null; dictIndex = idx32.GetValue(i).Value;       break;
                case Int16Array idx16: if (idx16.IsNull(i)) return null; dictIndex = idx16.GetValue(i).Value;       break;
                case Int8Array  idx8:  if (idx8.IsNull(i))  return null; dictIndex = idx8.GetValue(i).Value;        break;
                case Int64Array idx64: if (idx64.IsNull(i)) return null; dictIndex = (int)idx64.GetValue(i).Value;  break;
                default: return da.Indices.ToString();
            }
            return GetValue(da.Dictionary, dictIndex);
        }
        if (a is StringArray sa)      return sa.GetString(i);
        if (a is BooleanArray ba)     return ba.IsNull(i) ? (object)null : ba.GetValue(i);
        if (a is Int64Array i64)      return i64.IsNull(i) ? (object)null : i64.GetValue(i);
        if (a is Int32Array i32)      return i32.IsNull(i) ? (object)null : i32.GetValue(i);
        if (a is DoubleArray d)       return d.IsNull(i)   ? (object)null : d.GetValue(i);
        if (a is Decimal128Array dec) return dec.GetValue(i);
        if (a is Date32Array d32)     return d32.GetDateTime(i);
        if (a is Date64Array d64)     return d64.GetDateTime(i);
        if (a is TimestampArray ts)   return ts.GetTimestamp(i);
        return a.ToString();
    }
}
"@

$results = [DaxMultiResultReader]::ReadAll($memoryStream)
Write-Host "Received $($results.Count) result sets."

5 - Työskentele jokaisen tulosjoukon kanssa

Muunna jokainen tulosjoukko riveiksi PSCustomObject . Nyt voit ohjata rivit , Where-ObjectGroup-Object, Export-Csv, tai minkä tahansa muun PowerShell-komentokeskuksen kautta.

function ConvertTo-PSObjectRows {
    param([Parameter(Mandatory)] $ResultSet)
    foreach ($row in $ResultSet.Rows) {
        $obj = [ordered]@{}
        foreach ($col in $ResultSet.ColumnNames) { $obj[$col] = $row[$col] }
        [PSCustomObject]$obj
    }
}

$rowCount    = ConvertTo-PSObjectRows -ResultSet $results[0]
$topProducts = ConvertTo-PSObjectRows -ResultSet $results[1]
$yearTotals  = ConvertTo-PSObjectRows -ResultSet $results[2]

$rowCount    | Format-Table
$topProducts | Format-Table
$yearTotals  | Format-Table

Jokainen muuttuja sisältää vastaavan EVALUATE lauseen rivit siinä järjestyksessä kuin lauseet esiintyvät pyynnössä.

Vianmääritys

  • 401 Luvaton — Välimuistissa oleva token vanhentui. Päivitä Connect-PowerBIServiceAccount se uudelleen ja lue $accessToken uudestaan .Get-PowerBIAccessToken
  • MSAL-varoitukset Connect-PowerBIServiceAccountMicrosoftPowerBIMgmt niputtaa vanhemman MSAL.NET-tiedoston, joka lähettää sisäisiä jäljitysviestejä (esimerkiksi , SetAuthorityUriTryNormalizeRealm, MsaDeviceOperationProvider is not available) varoitusvoimakkuudella. Ne on turvallista jättää huomiotta, kunhan komentokeskus tulostaa lohkonEnvironmentUserName / TenantId / . Tukahduttaaksesi heidät, hyväksy -WarningAction SilentlyContinue.
  • HTTP 200 virhetulosjoukolla — HTTP-pyyntö onnistui, mutta Arrow-virta sisältää virheen. Tutki skeeman metatiedot , IsError=trueja lue FaultCode ja .FaultString Lisätietoja löytyy parhaista käytännöistä Execute DAX Queries REST API:lle.
  • Invoke-RestMethod palauttaa sekavaa tekstiä — Älä käytä Invoke-RestMethod, Invoke-PowerBIRestMethod, tai Invoke-WebRequest tätä API:a. Vastaus on binäärinen; Käytä HttpWebRequest kuten vaiheessa 3.
  • Add-Type ei lataudu Apache.Arrow.dll — Windows PowerShell 5.1:ssä paketti Apache.Arrow on ristiriidassa in-box System.Memory assemblyn kanssa. Käytä PowerShell 7.4:ää tai uudempaa.
  • Ei tai vähemmän tulosjoukkoja kuin EVALUATE väitteitä — Varmista, että jokainen EVALUATE lause on syntaktisesti pätevä itsessään. Yksi virheellinen EVALUATE virhe saa API:n palauttamaan virheen osittaisen monitulosten sijaan.