Jak zrychlit pomalý SharePoint List Refresh v Power BI
- Vojtěch Šíma
- Dec 24, 2025
- 13 min read
tl;dr pokud máš seznamy obsahující pouze primitivní typy (Text, Čísla atd.), drž se Implementation 2.0 a vypni veškeré součty/počty (totals) na zdroji. Pokud však potřebuješ absolutní maximální rychlost nebo máš komplexní sloupce, zavolej přímo SharePoint REST API a nastav limit stránkování na 5000. Podrobnosti si přečti v celém článku níže.
Implementace konektoru pro SharePoint List v Power Query (1.0 vs 2.0)
Když se chceš nativně připojit k SharePoint listu v Power Query, v GUI si vybíráš buď možnost SharePoint nebo SharePoint Online list. Jsou identické v tom, jaká M funkce se volá na pozadí; jediný rozdíl je v možnostech, které použiješ při volání.
Funkce se jmenuje SharePoint.Tables() a její možnosti jsou:
ApiVersion: Aktuálně 14, 15 nebo "Auto". Pokud to nespecifikuješ, výchozí je 14. Microsoft také uvádí, že neanglické SharePoint weby vyžadují alespoň 15.
Implementation: Aktuálně null nebo "2.0". Toto řídí, zda používáš původní chování konektoru nebo novější implementaci 2.0.
ViewMode: Aktuálně "All" nebo "Default" (platné pouze pro Implementation "2.0").
All ti vrátí všechny uživatelské sloupce plus systémové sloupce.
Default odpovídá jakémukoli zobrazení (View), které je nastaveno jako výchozí v SharePointu, takže změny v přidání/odebrání sloupců v tomto zobrazení se propíší do konektoru.
Je tam také DisableAppendNoteColumns, ale GUI tuto možnost nepoužívá. Pouze to zastaví konektor v používání samostatného endpointu pro sloupce s poznámkami.
A pod tím vším mluvíš s SharePoint REST endpointy.
Obecné pravidlo zní, že původní implementace může být bezpečnější, ale pomalejší, a nezískáš rovnou "přátelské názvy" (friendly names). Proto Implementation 2.0 s ViewMode "Default" může být dobrou alternativou; nicméně Microsoft u ní uvádí pár problémů. Ten hlavní je rozebrán v dalším odstavci. Takže si to nejdřív vyzkoušej a pak se rozhodni.
Pokud chceš verzi bez bugů, která je super rychlá a nevadí ti trocha kódu, čti dál.
Oprava chyby: /RenderListDataAsStream (500) Internal Server Error v Power BI
DataSource.Error: Web.Contents failed to get contents from 'https://daatlers.sharepoint.com/sites/Experiments/_api/Web/Lists(guid'10f52422-d47d-475e-ad30-60e906dc1a6e')/RenderListDataAsStream' (500): Internal Server ErrorPokud ses rozhodl použít Implementation 2.0, můžeš narazit na tuto chybu, i když jsi udělal všechno správně.
Nejčastěji se problém objeví, když máš seznam s více než 5000 položkami, když máš více než 12 výchozích sloupců (nebo joinuješ mnoho sloupců) nebo když používáš Součty (Totals) v některém ze sloupců.
Podle mých zkušeností fixnutí Totals většinou zabrala. Pokud nevíš, co myslím těmi Totals, podívej se na tento obrázek:

Když vidíš tento počet pod seznamem, může to skutečně způsobit tento problém. Abys ho vyřešil, klikej následovně:

Sjeď dolů, dokud neuvidíš tyto možnosti:

Rozbal Totals a zkontroluj pole, pokud některé z nich má agregaci jako 'Count', změň ho na None.

Sjeď zpátky nahoru (nebo dolů) a nezapomeň kliknout na OK.

Proč je refresh SharePoint listu v Power BI pomalý?
Teď si pojďme promluvit o skutečné optimalizaci a ne jen o řešení překážek, aby konektor vůbec fungoval.
Hlavním důvodem, proč bývá refresh SharePoint listu pomalý, je stránkování (pagination), nebo přesněji to, kolik stránek nakonec "nativní klikací" řešení stahuje. Klasický endpoint pro položky seznamu vrací ve výchozím nastavení prvních 100 položek, takže pokud to necháš stránkovat, můžeš skončit s tunou requestů. Implementation 2.0 by měla mít velikost stránky kolem 1000 nebo 2000.
Vlastní konektor, který napíšeme později, může jít až na 5000 položek na stránku. Tím bychom měli dosáhnout vyšší rychlosti refreshe.
Pokud si nejsi jistý, co je stránkování: ve zkratce je to proces rozdělení většího obsahu na menší kusy, zvané stránky. To může zlepšit výkon, když prohlížíš jen podmnožinu dat, a také to pomáhá serveru najít prostor se 'nadechnout'.
Nicméně když chceš všechny položky, rozdělení výsledků na příliš mnoho stránek znamená více requestů, a tedy více času na zpracování každého z nich. Proto musíš obecně najít rovnováhu mezi příliš mnoha requesty a příliš velkými daty na stránku.
Obecně platí, že pokud přistupuješ k API třetích stran podporující stránkování, obvykle budeš mít maximální limit položek, které můžeš na stránku vyžádat. Často bývá tato maximální hodnota nejrychlejší.
Jak postavit rychlejší SharePoint konektor v Mku
I když tohle pravděpodobně není řešení pro úplné začátečníky, dám ti snadno zkopírovatelnou funkci (nebo funkce), kterou stačí zavolat a máš hotovo.
Ta hezká část je, že se nemusíš starat o extra autentizační vrstvu pro REST API. Protože nepoužíváme POST, můžeme využít stejné OAuth2 (Organizational Account) přihlášení, které už používáš při připojování k SharePointu.
Celé to postavíme na Web.Contents a jednoduché List.Generate smyčce. Začněme definováním parametrů a funkcí.
Host jako Parameter
https://<your tenant>.sharepoint.com

Název 'site'/stránky nebo týmu jako parametr (Managed path)
site/SiteName/ nebo teams/teamName/

Získání List Id
Abychom dostali data ze seznamu, potřebujeme jeho ID, které není snadné vyčíst jednoduše z URL, takže si zavoláme funkci, která ho získá za nás.
Vše, co potřebuješ vědět, je jeho Title (název), a s tím si vystačíme.
let
getListId= (listTitle as text) as text =>
Json.Document(
Web.Contents(
host,
[
RelativePath = siteName & "_api/Web/Lists",
Headers = [Accept = "application/json"],
Query = [
#"$select" = "Title, Id",
#"$filter" = "Title eq '"& listTitle &"'"
]
]
)
)[value]{0}[Id]
in
getListId
// expected value format: 10f52422-d47d-475e-ad30-60e906dc1a6eDoporučuji toto udělat jednou a výsledek si uložit ručně do parametru. To pak vyřeší problémy, kdybys svůj seznam přejmenoval.

Získání hodnot seznamu
Pro získání hodnot si nejdřív definujme funkci, která načte jednu stránku. Pak přidáme stránkovací mechanismus, který stáhne všechny stránky a tím pádem všechny hodnoty.
Později ukážu, jak požadavek optimalizovat pomocí query parametrů a taky proces získání přátelských názvů (friendly names).
getListPage = (siteName as text, listId as text) =>
Json.Document(
Web.Contents(
host,
[
RelativePath = siteName & "_api/Web/Lists(guid'" & listId &"')/items",
Headers = [Accept = "application/json"],
Query = [#"$top"="5000"]
]
)
),
getPage = getListPage( siteName, listId )Toto je jednoduchý request, který ti vrátí prvních 5 000 položek z tvého seznamu. Pokud máš malý seznam a víš, že nepřesáhne 5 000 položek, můžeš tady skončit.
Ale ve většině případů skončíš později s více položkami tak či tak, takže to pojďme rovnou připravit na budoucnost. Postavíme paginátor, abychom pokryli všechny stránky.
Všimni si, že upravím i původní funkci, protože stránkování vyžaduje trochu jiné chování.
getListPage = (optional relativePath as nullable text, optional siteName as nullable text, optional listId as nullable text) =>
Json.Document(
Web.Contents(
host,
[
RelativePath = relativePath ?? siteName & "_api/Web/Lists(guid'" & listId &"')/items?$top=5000",
Headers = [Accept = "application/json"]
]
)
),
paginator =
List.Generate(
() => [
request = getListPage(null, siteName, listId),
next = request[#"odata.nextLink"]?,
index = 0
],
each [next]? <> null or [index]=0,
each [
request = getListPage( Text.AfterDelimiter(next, (host & "/") ) ),
next = [request]?[#"odata.nextLink"]?,
index = [index]+1
],
each [request]?[value]?
),
combinePages = List.Combine(paginator)Pokud tě nezajímají metadata, změň hlavičku Accept na: [Accept = "application/json;odata=nometadata"]
Zde je upravená verze s paginátorem, pro získání všech pooložek.
Stránkování na straně Microsoftu funguje takto: po každém requestu dostaneš URL další stránky (odata.nextLink). To znamená, že musíš poslat úplně nový request pro každou stránku a URL už obsahuje vše potřebné pro stažení dalšího kusu.
Proto jsem předělal request pro jednu stránku do getListPage, aby přijímal relativePath jako první parametr a zbytek byl volitelný. Pro první volání předáme relativePath = null, takže spadne zpátky na původní request s ?$top=5000. Pro další stránky předáme pouze relativní cestu vytaženou z odata.nextLink.
Nakonec je tam malá kontrola pro případ jedné stránky. K tomu slouží index. Zajišťuje, že generátor poběží alespoň jednou, a hodí se to i pro rychlé debugování.
Také vidíš, že dělám trochu magie s oddělovači při sestavování dalšího requestu. To proto, že musíme oddělit hosta od relativní cesty, aby query zůstala v Power BI Service refreshovatelná.
To je jedna nevýhoda implementace stránkování od Microsoftu: musíme předpokládat, že další odkaz zůstává na stejném hostu a není to nějaký náhodné 'mirror'. Host, kterého používáš pro autentizaci, nemůže být dynamický, takže nemůžeš jen tak nakrmit Web.Contents celou URL další stránky a doufat, že se to Service bude líbit.
Získání hodnot s Friendly Names, expanzí a dynamickým typováním
První verzi jsem napsal pár dní zpátky, ale pak jsem si uvědomil, že moje aktuální řešení (které by následovalo) se snadno rozbije u typů polí vyžadujících expanzi a výběr podpolí (subfields), jako je třeba User. To mě donutilo přehodnotit celý přístup. Myslel jsem, že bych mohl pomalu zvyšovat úroveň složitosti, ale uvědomil jsem si, že to musím udělat všechno najednou, protože částečná řešení nedávala smysl. Takže tady je řešení, které ti umožní definovat přátelské názvy sloupců. Získá interní názvy a typy, postaví dynamické schéma s použitím těchto přátelských názvů a přetypuje každý hlavní sloupec. Výstupem je expandovaná tabulka s typy a přátelskými názvy sloupců.
Jak jsem zmínil dříve, připrav se na pravděpodobně "overkill". Ale co, je to mnohem rychlejší než běžné klikací řešení.
Nejdřív představím pár omezení, abys ses mohl rozhodnout, jestli ti to vyhovuje. Pak vložím celý kód jako sérii kroků (ne jako funkci), abys ho mohl snáze zkoumat a upravovat. Nakonec rozeberu hlavní části.
Limitations & Workarounds
Délka URL: Pokud vyžádáš příliš mnoho polí, můžeš narazit na limit znaků v URL. V tom případě zakomentuj parametr filtrování polí (fields endpoint), stáhni všechno a pak aplikuj filtr až na stažená data.
Expanze: Typy vyžadující expanzi jsou omezeny na Id a Title. Vždy můžeš upravit sharePointTypeProfile a přidat další podpole; skript je hladce expanduje.
Strukturovaná data: Strukturované výsledky nejsou typované ani expandované. Pokud host vrátí record nebo list, sloupec zůstane formátovaný jako Record nebo List. Protože nevím, co s těmi daty chceš dělat, nechal jsem je "raw".
Metadata: Nevracím metadata. Pokud je potřebuješ, uprav Accept hlavičku.
Prerekvizity parametrů: Pokud jsi skočil rovnou sem a nečetl nic předtím, pamatuj, že tento kód vyžaduje parametry zmíněné výše, jako host, siteName a listId.
Friendly Names Driver: Postavil jsem řešení tak, aby akceptovalo přátelské názvy (to, co vidíš v prohlížeči). Je to víceméně bezpečné, protože názvy sloupců by měly být unikátní, i když to není InternalName. Udělal jsem to takto, protože získávat Internal Names z UI je otravné. Pokud narazíš na chyby, zkontroluj skutečné názvy polí přes endpoint Fields a pak uprav svůj seznam.
let
defaultNoTransformation = (x as any) => x,
defaultRecordTransformation = (x) => if x is text then Json.Document(x) else if x is record then x else null,
defaultTypeRecord = [Type = type any, ReTyper = defaultNoTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
sharePointTypeProfile = [
// TEXT TYPES
Text = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Note = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Choice = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Guid = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Computed = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Calculated = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
File = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
ContentTypeId = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
ThreadIndex = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Threading = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
WorkflowStatus = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
OutcomeChoice = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
CrossProjectLink = [Type = type nullable text, ReTyper = Text.From, ToExpand = false, SelectSubFields = {}, Optional = false],
// LIST TYPES
MultiChoice = [Type = type nullable list, ReTyper = defaultNoTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
TaxonomyFieldTypeMulti = [Type = type nullable list, ReTyper = defaultNoTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
// INTEGER TYPES
Integer = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Counter = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
ModStat = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
WorkflowEventType = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
PageSeparator = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
// NUMBER TYPES
Number = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Currency = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
GridChoice = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
AverageRating = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
RatingScale = [Type = type nullable number, ReTyper = Number.From, ToExpand = false, SelectSubFields = {}, Optional = false],
// LOGICAL TYPES
Boolean = [Type = type nullable logical, ReTyper = Logical.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Attachments = [Type = type nullable logical, ReTyper = Logical.From, ToExpand = false, SelectSubFields = {}, Optional = false],
Recurrence = [Type = type nullable logical, ReTyper = Logical.From, ToExpand = false, SelectSubFields = {}, Optional = false],
AllDayEvent = [Type = type nullable logical, ReTyper = Logical.From, ToExpand = false, SelectSubFields = {}, Optional = false],
// DATETIME TYPES
DateTime = [Type = type nullable datetimezone, ReTyper = DateTimeZone.From, ToExpand = false, SelectSubFields = {}, Optional = false],
// RECORD TYPES
URL = [Type = type nullable record, ReTyper = defaultNoTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
// EXPANDABLE TYPES
Lookup = [Type = type record, ReTyper = defaultNoTransformation, ToExpand = true, SelectSubFields = {"Id", "Title"}, Optional = false],
LookupMulti = [Type = type nullable list, ReTyper = defaultNoTransformation, ToExpand = true, SelectSubFields = {"Id", "Title"}, Optional = false],
User = [Type = type nullable record, ReTyper = defaultNoTransformation, ToExpand = true, SelectSubFields = {"Id", "Title"}, Optional = false],
UserMulti = [Type = type nullable list, ReTyper = defaultNoTransformation, ToExpand = true, SelectSubFields = {"Id", "Title"}, Optional = false],
// EXPANDABLE JSON TYPES
Thumbnail = [Type = type nullable record, ReTyper = defaultRecordTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
Location = [Type = type nullable record, ReTyper = defaultRecordTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
Image = [Type = type nullable record, ReTyper = defaultRecordTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
TaxonomyFieldType = [Type = type record, ReTyper = defaultRecordTransformation, ToExpand = false, SelectSubFields = {}, Optional = false],
// FALLBACK TYPES
Default = defaultTypeRecord,
Invalid = defaultTypeRecord,
Error = defaultTypeRecord,
MaxItems = defaultTypeRecord
],
columnsWanted = {
/*
example of fields I used for testing
replace this with your own list
"friendly_id",
"friendly_column_name",
"column_hey",
"some_nice_date",
"number",
"employee",
"location",
"longText",
"choice",
"isTrue",
"lookup",
"hyperLink",
"image",
"managedData",
"Rating (0-5)",
"Author",
"Status",
"Status (Legacy)",
"O'Reilly",
"numberNoDecimalPlace",
"lookupSingle",
"taxonomySingle"
*/
},
columnsWantedFilter = Text.Combine(
List.Transform(
columnsWanted,
each "Title eq '" & Text.Replace(_, "'", "''") & "'"
),
" or "
),
fieldsWithTypes = List.Buffer(
List.Transform(
Json.Document(
Web.Contents(host, [
RelativePath = siteName & "_api/Web/Lists(guid'" & listId & "')/fields?",
Headers = [Accept = "application/json;odata=nometadata"],
Query = [
#"$select" = "InternalName,Title,TypeAsString,LookupList,LookupField,Hidden,ReadOnlyField",
#"$filter" = columnsWantedFilter
]
])
)[value],
each _ & Record.FieldOrDefault(sharePointTypeProfile, [TypeAsString], defaultTypeRecord)
)
),
fieldSelect =
let
parent = List.Combine(
List.Transform(
List.Select(fieldsWithTypes, each not List.IsEmpty([SelectSubFields])),
(parent) => List.Transform(parent[SelectSubFields], each parent[InternalName] & "/" & _)
)
)
in
Text.Combine(List.Transform(fieldsWithTypes, each [InternalName]) & parent, ","),
fieldExpand = Text.Combine(
List.Transform(
List.Select(fieldsWithTypes, each [ToExpand]),
each [InternalName]
),
","
),
fieldsForSchema = Record.FromList(
List.Transform(fieldsWithTypes, each Record.SelectFields(_, {"Type", "Optional"})),
List.Transform(fieldsWithTypes, each [Title])
),
tableSchema = type table Type.ForRecord(fieldsForSchema, false),
getListPage = (optional relativePath as nullable text, optional siteName as nullable text, optional listId as nullable text) =>
Json.Document(
Web.Contents(host, [
RelativePath = relativePath ?? siteName & "_api/Web/Lists(guid'" & listId & "')/items?" & Uri.BuildQueryString([
#"$top" = "5000",
#"$select" = fieldSelect,
#"$expand" = fieldExpand
]),
Headers = [Accept = "application/json;odata=nometadata"]
])
),
paginator = List.Generate(
() => [
request = getListPage(null, siteName, listId),
next = request[#"odata.nextLink"]?,
index = 0
],
each [next]? <> null or [index] = 0,
each [
request = getListPage(Text.AfterDelimiter(next, (host & "/"))),
next = [request]?[#"odata.nextLink"]?,
index = [index] + 1
],
each [request]?[value]?
),
combinePages = List.Combine(paginator),
reTypeFunctionsRecord = Record.FromList(
List.Transform(fieldsWithTypes, each [ReTyper]),
List.Transform(fieldsWithTypes, each [InternalName])
),
fieldOrder = List.Transform(fieldsWithTypes, each [InternalName]),
listOfValuesFromRecords = List.Transform(
combinePages,
(r) => List.Transform(
fieldOrder,
(fx) => Record.FieldOrDefault(reTypeFunctionsRecord, fx, defaultNoTransformation)(Record.FieldOrDefault(r, fx, null) )
)
),
typedTable = #table(tableSchema, listOfValuesFromRecords)
in
typedTableNedoporučuji zkoumat kód přímo tady na blogu kvůli omezeným možnostem zobrazení, nejlepší je vložit si ho do editoru.
Vysvětlení klíčových částí
sharePointTypeProfile: Toto je mozek celého typovaného schématu. Je to record obsahující pod-record pro každý jednotlivý Typ přicházející ze SharePointu, který jsem našel.
Type & ReTyper: Toto jsou nejdůležitější části; určují, jak bude tvoje finální tabulka vypadat. ReTyper vrací funkci, která aplikuje transformaci pro každý řádek (pouhé definování typu nutně nepřetypuje hodnotu). Pokud to budeš upravovat, měj na paměti, že očekává funkci.
Vlastní transformace: Předdefinoval jsem pár transformací pro typy podobné Recordu. Pokud bys chtěl cokoliv modifikovat, například extrahovat Datum z Datetimezone pro každé pole Datetime, uprav to zde.
ToExpand & SelectSubFields: Tyto určují, zda Typ vyžaduje expanzi (jako User nebo Lookup). Ve výchozím nastavení vracím jen Id a Title, ale úprava je super snadná. Následující funkce nejsou hardcoded, takže budou hladce reagovat na tvoje změny (např. pokud potřebuješ EMail z Usera, přidej ho sem).
columnWanted: Toto je druhá nejdůležitější část (nebo možná první). Toto řídí celé query. Tady definuješ Friendly Names sloupců, které chceš. A upřímně, to je asi všechno; to je vše, co musíš udělat, abys měl funkční query. Poznámka: Zde nedefinuješ podpole.
Stahování polí & OData: Jakmile definuješ sloupce výše, query pošle request pro získání polí dostupných v seznamu. Získá dodatečné info (jako Type) a namapuje náš úvodní TypeProfile na výsledky. Poté postaví OData dotazy ($select a $expand), aby vytvořil minimální možný request pro stažení tvých dat.
Přeřazení a Přetypování: API může vrátit výsledky v jiném pořadí, než je naše schéma, takže potřebujeme mechanismus pro vynucení pořadí sloupců a přetypování dat.
Testoval jsem pár variant, ale rozhodl jsem se pro přístup založený na Recordech.
Postavil jsem record, kde každé pole je název sloupce spárovaný s transformační funkcí.
Poté upravuji stránkované výsledky: pro každý záznam v listu rekonstruuji výsledek tak, aby odpovídal přesnému pořadí a typům mého schématu.
Výsledek: Výstupem je tabulka, která je plně typovaná a dynamicky se mění na základě sloupců, které poskytneš.
Pokud chceš flexit nad kolegy, uprav query tak, aby columnWanted byl Parametr. To ti umožní přidávat nové sloupce přímo z Power BI Service bez otevření sémantického modelu. Ačkoliv to nedoporučuji, teoreticky by to mělo fungovat.
Tip na závěr, pokud bys chtěl seznam všechn uživatelů jako dimenzi, vem si pouze Idčka a vedle postav dimenze přes endpoint _api/web/siteusers.
Benchmark
Abychom obhájili všechnu tuhle práci, musíme vidět, jestli vůbec získáme nějaký výkon při refreshi. Testoval jsem to na přibližně 20 000 řádcích. Většina měla vyplněné jen základní typy, ale podmnožina byla plná "dražších" typů jako User, Managed Data, Lookup, Pictures, Rating, Locations atd.
Timer byl udělaný extrémně jednoduše; každá query byla izolovaná a spuštěná sama o sobě kliknutím na Refresh (schema a data) v Power BI Desktopu.
Scénář 1: Stažení všech sloupců (Všechny sloupce s nulovými transformacemi po načtení tabulky)
Implementace | Čas |
Legacy implementation (ApiVersion=15) | 50 sekund |
Implementation 2 (Implementation="2.0", ViewMode="Default") | 1m 20s |
Custom solution | 9 sekund |
Scénář 2: Stažení 5 základních sloupců (Pro klikací řešení jsem přidal krok "Select Columns"; pro vlastní řešení jsem definoval pole v columnsWanted)
Implementace | Čas |
Legacy implementation (ApiVersion=15) | 42 sekund |
Implementation 2 (Implementation="2.0", ViewMode="Default") | 6 sekund |
Custom solution | 3 sekundy |
Kdybychom snížili šum a začali počítat až od té doby, co mi okno ukáže, že stahuje data, proběhne vlastní řešení prakticky okamžitě. Implementace 2 byla na to víceméně obdobně.
Zatímco Implementation 2.0 funguje extrémně dobře pro jednoduché sloupce, u komplexních typů výrazně ztrácí. Vlastní řešení, i s 'obrovským overheadem', funguje velmi efektivně bez ohledu na složitost sloupců.
Díky
Pokud tohle čteš a skutečně ses podíval na kód, velký respekt. Rád bych slyšel, jestli to splnilo tvoje očekávání a jaká byla použitelnost. Pravděpodobně také zveřejním aktualizovanou verzi (po více benchmarcích) na GitHub, takže mě tam určitě sleduj.



Comments