DirectQuery zwalnia po jednej miarze DAX: najszybszy sposób namierzenia winowajcy

Jak namierzyć miarę DAX, która spowalnia DirectQuery: testy DAX vs SQL, typowe „winne” funkcje, eksplozja kontekstu filtrów, folding oraz szybkie refaktoryzacje miar.
05 lipca 2026
blog

Dlaczego jedna miara DAX potrafi „zabić” DirectQuery, mimo że reszta raportu działała szybko?

W trybie DirectQuery miara DAX nie jest liczona „u siebie” w pamięci modelu, tylko musi zostać przetłumaczona na zapytania wysyłane do źródła danych. Większość prostych miar (sumy, liczniki, proste filtry) mapuje się na jedno względnie tanie zapytanie SQL i dlatego raport działa szybko. Problem zaczyna się, gdy pojedyncza miara wymusza taki sposób obliczeń, którego nie da się efektywnie złożyć w jedno zapytanie po stronie bazy albo który generuje zapytanie znacznie cięższe niż wszystko pozostałe.

Taka „winna” miara zwykle powoduje eksplozję pracy po stronie źródła: skanowanie dużych fragmentów tabel, tworzenie kosztownych pośrednich zestawów, dodatkowe złączenia, sortowania lub grupowania, albo wykonywanie wielu zapytań zamiast jednego. W konsekwencji czas odpowiedzi wizualizacji rośnie skokowo, a ponieważ ta miara bywa użyta w wielu wizualach (lub w filtrach/warunkach formatowania), spowalnia nie tylko jeden element, ale praktycznie cały widok raportu.

Dodatkowo DirectQuery działa w kontekście interakcji użytkownika: każdy klik, filtr czy zmiana zakresu powoduje ponowne przeliczenie miar w nowym kontekście filtrów. Jeżeli jedna miara ma złożoną logikę zależną od kontekstu (np. iteracje po wierszach, obliczenia „na poziomie wiersza” agregowane później, złożone warunki), to jej koszt jest ponoszony za każdym razem. Dlatego raport mógł być szybki, dopóki nie pojawiła się miara, która generuje nieproporcjonalnie drogie zapytania lub serię zapytań do źródła.

Jak sprawdzić, czy problem jest w DAX, w SQL generowanym przez Power BI, czy w źródle danych?

Najpewniejsza metoda to rozdzielić problem na trzy warstwy i porównać czasy wykonania tego samego zapytania: (1) miary DAX w Power BI, (2) SQL wygenerowany przez DirectQuery, (3) wykonanie tego SQL bez udziału Power BI w samym źródle danych.

  • Krok 1: ustal, czy spowalnia DAX — w Power BI uruchom Performance Analyzer dla wizualizacji, na której widać spowolnienie. Jeśli dominuje czas DAX query (a nie Visual display), problem jest w logice miary/modelu (np. zbyt kosztowny kontekst filtrowania, iteracje, złożone warunki). Jeśli dominuje Other lub render, to nie jest kwestia DAX (to jednak nie rozstrzyga jeszcze, czy winny jest SQL czy źródło danych).
  • Krok 2: sprawdź, czy winny jest SQL generowany przez Power BI — włącz logowanie/śledzenie zapytań i wyciągnij dokładny SQL wysyłany do źródła (np. przez panel diagnostyczny, logi lub profiler odpowiedni dla źródła). Następnie porównaj go z oczekiwaniami: czy jest nieproporcjonalnie rozbudowany (wiele złączeń, podzapytań, skomplikowane GROUP BY, niepotrzebnie szerokie SELECT), czy filtracja jest wykonywana „późno” (na dużych zbiorach), czy pojawiają się konstrukcje typowe dla trudnych planów (np. wielokrotne agregacje, konwersje typów w predykatach). Jeśli sam kształt SQL jest problematyczny, to sygnał, że to warstwa translacji (DAX→SQL/model) generuje koszt.
  • Krok 3: oddziel problem źródła danych od problemu Power BI — skopiuj ten sam SQL i uruchom go bezpośrednio w narzędziu bazy (np. SSMS/ADS dla SQL Server, klient dla danego silnika). Porównaj czas i plan wykonania. Jeżeli SQL jest wolny również poza Power BI, problem leży po stronie źródła danych (indeksy, statystyki, partycjonowanie, parametryzacja, obciążenie, blokady, limity zasobów). Jeżeli SQL działa szybko poza Power BI, a wolno w Power BI, zwykle chodzi o sposób wywołania (np. inna parametryzacja, częstotliwość/równoległość wywołań, dodatkowe zapytania generowane przez wizualizację) albo o narzut po stronie klienta/połączenia.

W praktyce: Performance Analyzer mówi, czy problem jest „po stronie Power BI/DAX”, a test uruchomienia tego samego SQL w bazie rozstrzyga, czy winne jest źródło danych, czy raczej sposób generowania/wykonywania SQL przez Power BI.

💡 Zawsze porównuj te same trzy czasy: DAX w Performance Analyzer, SQL wygenerowany przez DirectQuery i wykonanie tego SQL bezpośrednio w bazie — dopiero wtedy wiesz, czy boli miara, translator DAX→SQL czy samo źródło. Jeśli ten sam SQL jest wolny też w SSMS/kliencie bazy, szukaj w indeksach/planie/obciążeniu, a nie w Power BI.

Które funkcje i wzorce DAX najczęściej powodują ciężkie zapytania w DirectQuery?

W DirectQuery miara DAX jest „ciężka”, gdy jej logika nie daje się złożyć w proste, agregujące zapytanie wysyłane do źródła, albo gdy wymusza kosztowne operacje (złożone podzapytania, duże złączenia, sortowania, przetwarzanie wiersz-po-wierszu). Najczęściej dzieje się tak przy wzorcach, które intensywnie modyfikują kontekst filtra, tworzą wirtualne tabele o dużej krotności lub wykonują iteracje po wielu wierszach, bo wtedy backend musi policzyć dużo więcej niż „SUM/COUNT po filtrach”.

Typowe winowajcy to miary oparte o iteratory (SUMX, AVERAGEX, MINX, MAXX, RANKX) uruchamiane na szerokich tabelach faktów lub na wirtualnych tabelach zbudowanych w locie. Iteracja sama w sobie nie zawsze jest problemem, ale w DirectQuery często przekłada się na złożone zapytania (lub kilka zapytań) zamiast jednego prostego agregatu, szczególnie gdy w iteratorze pojawiają się warunki, wyszukiwania lub dodatkowe obliczenia wymagające dopasowań po kluczach.

Bardzo często ciężar powodują wzorce „context transition + rozszerzanie filtra”: CALCULATE z filtrami typu FILTER(ALL(...)), REMOVEFILTERS/ALL w połączeniu z predykatami, oraz użycie ALLEXCEPT w miejscach, gdzie powoduje to skanowanie dużej części danych. Takie konstrukcje łatwo zmieniają selektywne filtry na mało selektywne (np. „policz po całej tabeli”), co w DirectQuery kończy się dużą liczbą odczytów i kosztownym planem zapytania.

Problematyczne są też miary budujące i przetwarzające wirtualne tabele: SUMMARIZE/SUMMARIZECOLUMNS, ADDCOLUMNS, CROSSJOIN, GENERATE, UNION, EXCEPT, INTERSECT. Jeśli wynikowa tabela ma wysoką krotność (dużo kombinacji) lub wymaga deduplikacji, grupowania i sortowania, źródło danych musi wykonać ciężkie operacje po dużych zbiorach, a następnie jeszcze zastosować kolejne filtry/obliczenia.

Kolejna grupa to wzorce „lookup per row” oraz dynamiczne relacje: LOOKUPVALUE, RELATED/RELATEDTABLE w iteracji, oraz TREATAS z dużą liczbą wartości (np. przenoszenie filtra z tabeli o wysokiej kardynalności). W DirectQuery łatwo prowadzi to do kosztownych złączeń lub warunków typu IN na wielu wartościach, które bywają słabo optymalizowane w źródle.

Na wydajność szczególnie źle wpływają też miary „inteligencji czasowej” realizowane dynamicznie na bardzo szczegółowych kalendarzach lub przy braku dobrych relacji/indeksów w źródle: DATESYTD, TOTALYTD, DATEADD, SAMEPERIODLASTYEAR, PARALLELPERIOD. Same funkcje nie są „złe”, ale często wymuszają dodatkowe predykaty zakresowe, łączenia po dacie i przeliczenia na dużych zakresach, a w DirectQuery koszt tych operacji jest odczuwalny natychmiast.

Jak rozpoznać, że filtr kontekstu tworzy eksplozję zapytań albo zbyt szerokie skany tabel?

Najbardziej jednoznaczny sygnał w DirectQuery to sytuacja, w której pojedyncza miara DAX w tym samym wizualu powoduje nagle wysyłanie wielu bardzo podobnych zapytań do źródła (często różniących się tylko wartościami filtrów lub parametrami) zamiast jednego, spójnego zapytania. Dzieje się tak, gdy kontekst filtra jest rozbijany na wiele kombinacji (np. przez iteratory lub logikę wymuszającą obliczenia „per element”), co w praktyce widać jako serię następujących po sobie wywołań do bazy podczas jednego odświeżenia wizualu.

Drugi typowy objaw to zapytania, które semantycznie wyglądają na „słabo przycięte”: do źródła trafia SQL z mało selektywnym filtrowaniem, przez co silnik musi czytać dużą część tabeli faktów. W śladzie zapytań rozpoznasz to po braku (albo minimalnej roli) predykatów zawężających w WHERE, po pojawianiu się warunków niesargowalnych (np. obliczenia na kolumnie po stronie filtra), po filtrach realizowanych dopiero po agregacji, albo po tym, że z tabel faktów pobierane są dane dla znacznie szerszego zakresu niż wynika z interakcji użytkownika. W praktyce przekłada się to na długi czas wykonania pojedynczego zapytania, wysokie zużycie CPU/IO po stronie bazy oraz duże wolumeny przetwarzanych wierszy mimo pozornie „wąskiego” raportu.

Warto rozróżnić te dwa wzorce, bo „eksplozja zapytań” daje wiele krótszych lub średnich zapytań uruchamianych kaskadowo, a „zbyt szeroki skan” zwykle daje jedno (lub kilka) zapytań, ale z bardzo dużym kosztem wykonania. Jeśli po dodaniu miary liczba zapytań rośnie skokowo, to problemem jest najczęściej rozbijanie kontekstu na wiele przebiegów; jeśli liczba zapytań się nie zmienia istotnie, ale pojedyncze zapytanie nagle robi się ciężkie i słabo filtrowane, to kontekst filtra nie jest skutecznie składany do selektywnego SQL.

Co oznacza brak query folding w tym scenariuszu i jak to szybko zweryfikować?

Brak query folding oznacza, że kroki w Power Query (transformacje, filtrowanie, łączenia itp.) nie są tłumaczone na natywne zapytanie źródła (np. SQL) i wykonywane po stronie bazy, tylko są realizowane po stronie Power BI/Power Query. W scenariuszu DirectQuery to szczególnie problematyczne, bo zamiast jednego, „doklejonego” do miary DAX zapytania wykonywanego w bazie, możesz dostać mniej optymalny plan: część logiki jest odłączona od źródła, a silnik nie ma szans wykorzystać indeksów, statystyk i optymalizacji po stronie bazy w zakresie, w jakim mógłby przy pełnym foldingu. Efekt praktyczny bywa taki, że ta sama miara po dodaniu do wizualizacji wymusza cięższe zapytania lub większy zakres danych do przetworzenia, co objawia się nagłym spadkiem wydajności.

Najszybciej zweryfikujesz folding w Power Query, klikając prawym przyciskiem na ostatnim kroku w panelu Applied Steps i sprawdzając, czy opcja View Native Query jest dostępna. Jeśli jest wyszarzona/brak tej opcji, to od tego kroku folding jest przerwany (czyli dalsze kroki nie będą składane do zapytania źródła). Jeśli opcja jest dostępna, otwórz podgląd: powinieneś zobaczyć natywne zapytanie (np. SQL) zawierające odpowiedniki kluczowych transformacji. Dodatkowo, aby potwierdzić wpływ na DirectQuery, możesz uruchomić Performance Analyzer i sprawdzić, czy dla wizualizacji generowane są zapytania DirectQuery do źródła oraz czy ich treść/kształt zmienia się po włączeniu/wyłączeniu podejrzanych kroków w Power Query; brak foldingu zwykle koreluje z mniej „czystym” zapytaniem źródłowym lub koniecznością wykonania części logiki poza bazą.

💡 Najszybszy test foldingu to klik PPM na ostatnim kroku w Power Query i sprawdzenie „View Native Query” — jeśli jest wyszarzone, folding urwał się w tym miejscu. Dodatkowo podepnij Performance Analyzer i zobacz, czy po wyłączeniu podejrzanego kroku zmienia się kształt/ciężar zapytań DirectQuery (to zwykle potwierdza wpływ braku foldingu).

Jak przepisać miarę, żeby ograniczyć liczbę zapytań i ilość danych pobieranych z bazy?

W DirectQuery kluczowe jest to, czy miara daje się „złożyć” do jednego zapytania źródłowego i czy filtruje dane jak najwcześniej. Przepisując miarę, dąż do tego, aby silnik mógł przetłumaczyć logikę na pojedyncze SQL (lub jak najmniej zapytań) oraz aby do modelu nie trafiały niepotrzebne wiersze. Najczęstszy problem to iteratory i operacje wykonywane „po wierszach” w DAX, które mogą wymuszać pobieranie większego zestawu danych i dopiero potem liczenie po stronie Power BI.

Po pierwsze, eliminuj iteratory (SUMX, AVERAGEX, FILTER w roli generatora tabeli do dalszych obliczeń), jeśli da się je zastąpić agregacją działającą bezpośrednio na kolumnie lub prostą miarą bazową. Przykładowo zamiast SUMX(Fact, Fact[Qty] * Fact[Price]) zwykle lepiej mieć w źródle kolumnę wartości (np. Amount) i liczyć SUM(Fact[Amount]); dzięki temu agregacja jest wykonywana w bazie bez konieczności pobierania kolumn składowych i mnożenia w DAX. Jeśli nie możesz zmienić źródła, rozważ przeniesienie obliczenia do Power Query jako kolumny (o ile jest składana do źródła) lub do widoku w bazie, bo to nadal ogranicza ilość danych „do policzenia” po stronie modelu.

Po drugie, filtruj jak najwcześniej i jak najprościej. Warunki typu „top N”, skomplikowane filtry na miarach, warunki zależne od kontekstu i zagnieżdżone CALCULATE z rozbudowanymi wyrażeniami filtrującymi potrafią rozbić obliczenie na wiele zapytań albo wymusić pobranie szerszego zbioru. Jeżeli filtr jest stały (np. status = 'Closed', typ = 'A'), wprowadzaj go jako prosty filtr na kolumnie w CALCULATE (np. Fact[Status] = "Closed"), zamiast budować tabelę przez FILTER(Fact, ...). Taki zapis ma większą szansę zostać w całości „zepchnięty” do źródła i ograniczyć liczbę zwracanych wierszy.

Po trzecie, ogranicz liczbę przełączeń kontekstu i wielokrotnego liczenia tej samej logiki. Jeśli ta sama część obliczenia jest używana kilka razy, zapisz ją w zmiennej (VAR) i odwołuj się do wyniku, aby nie prowokować powtarzania zapytań o tę samą agregację. Zmienne nie zawsze gwarantują redukcję zapytań w każdym scenariuszu DirectQuery, ale często pomagają w uproszczeniu planu i ograniczeniu redundantnych odwołań do źródła.

Po czwarte, unikaj wzorców, które naturalnie zwiększają „rozmiar pracy” po stronie źródła: DISTINCTCOUNT na bardzo wysokiej kardynalności, dynamiczne „wirtualne relacje” (np. logika na TREATAS) czy wieloetapowe budowanie tabel pośrednich może skutkować cięższymi zapytaniami i większym transferem. Jeśli taki element jest konieczny, minimalizuj zakres danych, na którym działa (np. najpierw ogranicz daty i podstawowe filtry biznesowe, dopiero potem licz unikalne).

W praktyce „dobrze przepisana” miara w DirectQuery to taka, która opiera się na prostych agregacjach, ma jednoznaczne filtry na kolumnach (zamiast filtrów tabelowych), unika iteracji po faktach i nie powiela tej samej pracy kilka razy w obrębie jednego obliczenia. To bezpośrednio przekłada się na mniej zapytań oraz na to, że baza zwraca mniejszy, bardziej zagregowany wynik zamiast dużych zestawów wierszy do obróbki w Power BI.

💡 W DirectQuery przepisuj miary tak, by dawały się złożyć do prostych agregacji z filtrami kolumnowymi w CALCULATE (zamiast iteratorów SUMX/FILTER po faktach), bo to ogranicza liczbę zapytań i przenosi pracę do bazy. Jeśli kalkulacja typu Qty*Price jest częsta, zrób Amount w źródle/widoku i licz SUM(Amount), a powtarzane fragmenty logiki trzymaj w VAR, by nie prowokować redundantnych odpytań.

Kiedy lepiej przejść na Import, agregacje lub hybrydę i jak to uzasadnić biznesowo?

Warto rozważyć odejście od czystego DirectQuery wtedy, gdy problem z wydajnością nie wynika z pojedynczej „pomyłki” w DAX lub indeksach, tylko jest skutkiem samej natury zapytań generowanych przez raport: duża liczba interakcji, wysokie kardynalności, złożone filtry, wiele wizualizacji na stronie i miary, które wymuszają kosztowne operacje w źródle (np. powtórne skany, zagnieżdżone agregacje, trudne do złożenia warunki). Jeśli oczekiwanie biznesu to responsywność na poziomie sekund przy pracy „kliknięcie po kliknięciu”, to Import, agregacje lub hybryda są często jedyną stabilną drogą do przewidywalnych czasów odpowiedzi.

Import jest właściwym wyborem, gdy kluczowe jest szybkie działanie raportu i przewidywalna latencja, a dane mogą być odświeżane cyklicznie (np. co godzinę/dzień) bez utraty wartości biznesowej. Uzasadnienie biznesowe jest proste: krótszy czas odpowiedzi oznacza krótszy czas analizy (mniej „czekania”), mniejsze ryzyko porzucenia raportu przez użytkowników oraz mniej incydentów wydajnościowych eskalowanych do zespołu danych. Koszt przenosi się ze źródła transakcyjnego na okno odświeżenia i na pamięć/pojemność środowiska raportowego, co zwykle łatwiej kontrolować niż nieprzewidywalne obciążenie generowane w godzinach szczytu przez DirectQuery.

Agregacje lub hybryda mają sens, gdy część pytań biznesowych dotyczy podsumowań (typowe wykresy i KPI po dniach/tygodniach, po regionach, produktach, klientach), ale nadal potrzebujesz zejść do szczegółu „na żądanie” albo mieć świeżość dla wybranego zakresu danych. W takim układzie większość interakcji obsługujesz z warstwy szybkiej (Import/agregacje), a do źródła odpychasz tylko rzadkie drill-through lub szczegóły, dzięki czemu skracasz czasy odpowiedzi tam, gdzie użytkownik spędza 90% czasu.

Biznesowo decyzję najłatwiej obronić, wiążąc ją z mierzalnymi skutkami: wymaganym SLA czasu odpowiedzi raportu, liczbą użytkowników i równoległych interakcji, wpływem zapytań na krytyczne bazy (blokady/CPU w godzinach pracy), oraz kosztem utrzymania. Jeśli utrzymanie czystego DirectQuery wymaga stałego „gaszenia pożarów” (tuning kolejnych miar, ciągłe zmiany w źródle, spory o zasoby), to przejście na Import/agregacje/hybrydę zwykle obniża całkowity koszt posiadania przez stabilizację wydajności i odseparowanie analityki od obciążeń operacyjnych, przy jednoczesnym kontrolowaniu kompromisu świeżości przez harmonogram odświeżeń lub podział na warstwę „gorącą” i „historyczną”.

Majczęściej zadawane pytania i odpowiedzi odnośnie DirectQuery zwalnia po jednej miarze DAX: najszybszy sposób namierzenia winowajcy

Jak najszybciej sprawdzić, która miara DAX spowalnia DirectQuery?

Najszybciej zrobisz to przez Performance Analyzer i porównanie wizualizacji przed oraz po dodaniu podejrzanej miary. Jeśli po jej użyciu mocno rośnie czas DAX query, masz pierwszy mocny sygnał. Potem wyciągnij SQL wygenerowany przez DirectQuery i uruchom go bezpośrednio w bazie, aby rozdzielić problem na warstwę DAX, translacji DAX→SQL i samo źródło danych.

Po czym poznać, że problem leży w jednej miarze, a nie w całym modelu Power BI?

Problem leży w jednej miarze, gdy raport działał szybko do momentu jej dodania albo gdy spowalniają tylko wizuale, które z niej korzystają. Charakterystyczne jest też to, że po usunięciu tej miary czasy odpowiedzi wracają do normy. Taki objaw zwykle oznacza kosztowną logikę DAX, a nie ogólny problem całego modelu.

Jakie objawy sugerują, że DirectQuery generuje zbyt wiele zapytań do źródła?

Najbardziej podejrzany objaw to seria wielu podobnych zapytań uruchamianych przy jednym odświeżeniu wizualizacji. Zwykle oznacza to, że kontekst filtra został rozbity na wiele przebiegów. W praktyce warto sprawdzić, czy po dodaniu jednej miary pojawiają się:

  • wiele wywołań różniących się tylko parametrami,
  • skokowy wzrost czasu odpowiedzi po kliknięciu filtra,
  • powtarzające się zapytania dla tej samej wizualizacji.
Które wzorce DAX najczęściej warto przepisać w pierwszej kolejności w DirectQuery?

W pierwszej kolejności warto przepisać iteratory, rozbudowane FILTER po faktach oraz logikę budującą duże wirtualne tabele. To właśnie te wzorce często nie składają się do prostego SQL. Szczególnie podejrzane są SUMX, AVERAGEX, FILTER(ALL(...)), TREATAS na dużych zbiorach i konstrukcje z SUMMARIZE lub ADDCOLUMNS wykonywane na szerokich tabelach.

Czy brak query foldingu może spowalniać miary DAX w DirectQuery?

Tak, brak query foldingu może pośrednio pogarszać wydajność miar DAX w DirectQuery. Gdy transformacje w Power Query nie składają się do natywnego zapytania źródła, część logiki przestaje być wykonywana optymalnie po stronie bazy. W efekcie DirectQuery może operować na mniej korzystnym kształcie danych i generować cięższe zapytania dla wizualizacji.

Jak szybko sprawdzić, czy query folding urwał się w Power Query?

Najszybszy test to sprawdzenie opcji View Native Query na ostatnim kroku w Applied Steps. Jeśli opcja jest niedostępna lub wyszarzona, folding urwał się na tym etapie albo wcześniej. Dla pewności dobrze potem porównać zachowanie wizualizacji w Performance Analyzer po wyłączeniu podejrzanego kroku i sprawdzić, czy zmienia się ciężar zapytań DirectQuery.

Jak przepisać miarę DAX, żeby ograniczyć ilość danych pobieranych z bazy?

Najlepiej upraszczać miarę do agregacji, którą baza może policzyć bez iteracji po wierszach. W praktyce pomagają takie kroki jak:

  • zastąpienie SUMX prostym SUM, jeśli da się przygotować kolumnę w źródle,
  • stosowanie filtrów kolumnowych w CALCULATE zamiast FILTER na całej tabeli,
  • ograniczanie powtarzanej logiki przez VAR,
  • przenoszenie częstych obliczeń do widoku lub źródła danych.
Kiedy przy problemach z jedną miarą lepiej rozważyć Import, agregacje lub model hybrydowy?

Import, agregacje lub hybrydę warto rozważyć wtedy, gdy problem nie wynika z jednego błędu, ale z natury zapytań w DirectQuery. Jeśli raport ma dużo interakcji, złożone miary i oczekiwanie szybkiej reakcji po każdym kliknięciu, czysty DirectQuery bywa trudny do ustabilizowania. Wtedy szybsza warstwa danych lepiej wspiera przewidywalny czas odpowiedzi i mniejsze obciążenie źródła.

icon

Formularz kontaktowyContact form

Imię *Name
NazwiskoSurname
Adres e-mail *E-mail address
Telefon *Phone number
UwagiComments