Dokumentacja techniczna · wydanie 2.0
DAX
Katalog funkcji i wzorców obliczeniowych dla planowania w motoryzacji
Napisane dla kogoś, kto od lat planuje, budżetuje i rozlicza odchylenia w Excelu, a w DAX stawia pierwsze kroki. Każda pozycja mówi nie tylko co robi funkcja, ale jak o niej myśleć i gdzie się na niej przewrócisz. Przykłady odwołują się do jednego, spójnego modelu: sprzedaż aut, sieć dealerska, serwis, magazyn części i plan.
- Pozycji w katalogu
- —
- Wzorców łączonych
- 12
- Środowisko
- Power BI · Fabric
- Poziom wejścia
- Od zera
Sześć pojęć, bez których DAX zawsze będzie zgadywaniem
Większość „dziwnych wyników” w DAX to nie błąd składni, tylko nieporozumienie co do jednego z tych sześciu pojęć. Piętnaście minut tutaj oszczędza tydzień szukania po omacku.
Kolumna kontra miara
Kolumna obliczeniowa liczy się raz, przy odświeżaniu, i zapisuje wynik przy każdym wierszu — jak formuła przeciągnięta w dół arkusza. Zajmuje pamięć i nie reaguje na to, co klikniesz w raporcie.
Miara nie ma wartości, dopóki nie trafi na wizualizację. Liczy się od nowa w każdej komórce macierzy, według tego, co ta komórka aktualnie filtruje.
Zasada kciuka: jeśli wynik ma sumować się, zmieniać po kliknięciu w slicer albo pokazywać procent — to miara. Kolumna tylko wtedy, gdy potrzebujesz czegoś do grupowania, filtrowania lub osi wykresu.
Kontekst filtra
To zbiór wszystkich filtrów, które działają w chwili liczenia miary: wiersz i kolumna macierzy, slicery, filtry strony, legenda wykresu, klik na innym wizualu.
Miara SUM(Sprzedaz[Ilosc]) nie ma jednej wartości — w wierszu „Region Południe / marzec” zwróci coś innego niż w wierszu „Region Północ / kwiecień”. Sama formuła się nie zmienia; zmienia się to, co widzi.
Excelowa analogia: to obszar tabeli przestawnej, który po rozwinięciu podwójnym kliknięciem daje listę wierszy stojących za liczbą.
Kontekst wiersza
Pojawia się tylko w kolumnie obliczeniowej i wewnątrz funkcji iterujących (SUMX, AVERAGEX, FILTER). Oznacza „jesteśmy teraz na konkretnym wierszu tabeli i możemy odczytać jego pola”.
Kontekst wiersza sam z siebie nie filtruje reszty modelu. To najczęstsze źródło zdziwienia u osób przychodzących z Excela — bycie na wierszu faktury nie zawęża automatycznie żadnej innej tabeli.
Przejście kontekstu
Gdy wewnątrz kontekstu wiersza użyjesz CALCULATE (albo wywołasz miarę, bo miara jest w CALCULATE zapakowana automatycznie), bieżący wiersz zamienia się w filtr nałożony na cały model.
Dzięki temu SUMX(Dealerzy, [Przychód netto]) działa: dla każdego dealera przejście kontekstu zawęża sprzedaż do tego dealera. Bez tego mechanizmu dostałbyś tę samą sumę całkowitą powieloną tyle razy, ilu masz dealerów.
Model gwiazdy
Tabele faktów (Sprzedaz, Serwis, Produkcja) trzymają zdarzenia: dużo wierszy, dużo liczb. Tabele wymiarów (Kalendarz, Pojazdy, Dealerzy, Klienci) trzymają opisy: po czym chcesz kroić.
Filtruj zawsze po wymiarach, nigdy po kolumnach tabeli faktów. Miara napisana na modelu gwiazdy jest krótsza, czytelniejsza i liczy się w ułamku czasu.
Jedna szeroka tabela „bo tak było w Excelu” to najdroższa decyzja projektowa, jaką można podjąć w Power BI.
Tabela dat
Cała analiza czasowa (YTD, rok do roku, kroczące 12 miesięcy) wymaga osobnej tabeli kalendarza: ciągłej, bez luk, obejmującej pełne lata, oznaczonej w modelu jako Oznacz jako tabelę dat.
Kolumny Rok, Miesiac, RokMiesiac, RokFiskalny buduj w kalendarzu, nie w tabeli sprzedaży. Wtedy jedna oś czasu obsługuje sprzedaż, serwis i plan naraz.
Model danych używany we wszystkich przykładach
Każdy fragment kodu w tym podręczniku odwołuje się do tego samego modelu. Podmiana nazw na własne zajmuje minutę, a dzięki spójności widać, jak funkcje ze sobą współpracują.
| Tabela | Rola | Kluczowe kolumny |
|---|---|---|
| Kalendarz | wymiar dat | Data, Rok, Kwartal, Miesiac, RokMiesiac, RokFiskalny, NrMiesiaca |
| Sprzedaz | fakty | SalesID, DataSprzedazy, VIN, DealerID, ModelID, KlientID, Ilosc, CenaNetto, Rabat, KosztWlasny |
| Pojazdy | wymiar | ModelID, Marka, Model, Wersja, Segment, TypNapedu, MocKM, EmisjaCO2, CenaKatalogowa |
| Dealerzy | wymiar | DealerID, NazwaDealera, Region, Wojewodztwo, TypPlacowki, Email |
| Klienci | wymiar | KlientID, TypKlienta (B2C / B2B / Flota), Segment, DataPierwszegoZakupu |
| Serwis | fakty | ZlecenieID, VIN, DataPrzyjecia, DataWydania, DealerID, TypNaprawy, KosztCzesci, KosztRobocizny, RoboczoGodziny |
| Produkcja | fakty | DataProdukcji, ZakladID, ModelID, SztukiWyprodukowane, SztukiWadliwe, CzasTaktu |
| Magazyn | snapshot | Data, CzescID, StanMagazynowy, CenaJednostkowa |
| Plan | budżet | Rok, NrMiesiaca, DealerID, ModelID, PlanSztuki, PlanWartosc |
Relacje: Kalendarz[Data] zasila Sprzedaz[DataSprzedazy] i Serwis[DataPrzyjecia] relacjami aktywnymi oraz Serwis[DataWydania] relacją nieaktywną. Tabela Plan celowo nie ma relacji fizycznej — łączymy ją przez TREATAS, bo ma inne ziarno niż sprzedaż (miesiąc zamiast dnia, model zamiast egzemplarza).
Excel → DAX. To, co już umiesz, tylko inaczej zapisane
Nie uczysz się liczyć od nowa — uczysz się innego zapisu tych samych operacji. Ta tabela to najkrótsza droga od „wiem, co chcę policzyć” do „wiem, jak to napisać”.
| W Excelu robisz | W DAX piszesz | Co się zmienia w myśleniu |
|---|---|---|
| SUMA(zakres) | SUM(Sprzedaz[Ilosc]) | Nie wskazujesz zakresu komórek, tylko całą kolumnę. Zakres wyznaczy kontekst filtra wizualizacji. |
| SUMA.JEŻELI | CALCULATE(SUM(...), Pojazdy[Segment]="SUV") | Warunek nie stoi obok danych, tylko modyfikuje kontekst, w którym liczy się agregacja. |
| SUMA.WARUNKÓW | CALCULATE(SUM(...), warunek1, warunek2) | Kolejne argumenty CALCULATE łączą się operatorem „oraz”. Kolejność nie ma znaczenia. |
| SUMA.ILOCZYNÓW | SUMX(Sprzedaz, Sprzedaz[Ilosc] * Sprzedaz[CenaNetto]) | Iterator mnoży wiersz po wierszu, a dopiero potem sumuje. To jedna z najważniejszych różnic. |
| WYSZUKAJ.PIONOWO do słownika | RELATED(Pojazdy[Segment]) | Relacja w modelu zastępuje funkcję wyszukiwania. Definiujesz ją raz, działa wszędzie. |
| WYSZUKAJ.PIONOWO bez relacji | LOOKUPVALUE(Dealerzy[Region], Dealerzy[DealerID], 42) | Awaryjne wyjście, gdy relacji nie da się zbudować. Wolniejsze — używaj świadomie. |
| JEŻELI.BŁĄD(a/b; 0) | DIVIDE(a, b, 0) | Jedna funkcja zamiast opakowywania dzielenia w obsługę błędu. Szybsza i czytelniejsza. |
| LICZ.JEŻELI | CALCULATE(COUNTROWS(Sprzedaz), warunek) | Zliczasz wiersze tabeli w zawężonym kontekście, nie komórki spełniające kryterium. |
| LICZ.UNIKATOWE | DISTINCTCOUNT(Sprzedaz[VIN]) | Działa na kolumnie i respektuje wszystkie aktywne filtry. Uwaga na koszt przy dużych kardynalnościach. |
| % udziału w sumie kolumny | DIVIDE([Sztuki], CALCULATE([Sztuki], ALLSELECTED(...))) | Mianownik trzeba świadomie „odfiltrować”. Excel robił to za ciebie, DAX wymaga decyzji. |
| Suma narastająco w kolumnie | TOTALYTD([Przychód netto], Kalendarz[Data]) | Zamiast rozciągać formułę w dół, deklarujesz okres. Działa na każdym poziomie hierarchii dat. |
| Odwołanie do komórki obok (LAG) | OFFSET(-1, ...) lub DATEADD(...) | Nie ma „komórki obok”. Jest porządek sortowania i przesunięcie w nim. |
| Nazwane komórki pomocnicze | VAR nazwa = ... RETURN ... | Zmienne liczą się raz i porządkują formułę. To najlepszy nawyk, jaki możesz wyrobić na starcie. |
| Tabela przestawna | Wizualizacja macierzy | Miara jest wspólna dla wszystkich wizualizacji, więc definicję marży poprawiasz raz, a nie w ośmiu arkuszach. |
| Arkusz „Plan” obok „Wykonania” | Tabela Plan + TREATAS | Nie sklejasz danych w jedną płachtę. Zestawiasz dwie tabele o różnym ziarnie wspólnym wymiarem. |
| Ukrywanie zer formatowaniem | BLANK() kontra 0 | Pusta wartość w DAX ukrywa cały wiersz wizualizacji. Czasem tego chcesz, czasem to ukrywa problem. |
Katalog funkcji
Każda pozycja ma kod, poziom trudności i przykład osadzony w modelu opisanym wyżej. Filtruj po poziomie, gdy chcesz zobaczyć wyłącznie to, co potrzebne na start, albo szukaj po nazwie funkcji i po treści opisu.
Nic nie pasuje do tego zapytania. Spróbuj krótszej frazy albo wyczyść filtr poziomu.
Dwanaście wzorców, z których składa się raport planisty
Pojedyncza funkcja rzadko rozwiązuje problem. Poniżej pełne rozwiązania złożone z kilku funkcji naraz, każde z rozbiorem na kroki — po to, żeby dało się je przerobić na własny model, a nie tylko skopiować.
Gdy wynik jest zły. Tabela objawów
Miary rzadko psują się losowo. Prawie każdy błędny wynik da się przypisać do jednej z poniższych przyczyn — zacznij od objawu, nie od czytania kodu od początku.
| Objaw | Najczęstsza przyczyna | Co zrobić |
|---|---|---|
| Ta sama liczba powtarza się w każdym wierszu | Miara nie reaguje na kontekst, bo filtr został zdjęty przez ALL albo warunek w CALCULATE nadpisał filtr wiersza |
Sprawdź, czy zamiast ALL nie powinno być ALLSELECTED, i czy warunek nie potrzebuje opakowania w KEEPFILTERS |
| Suma całkowita nie równa się sumie wierszy | To normalne dla wskaźników nieaddytywnych: procentów, cen średnich, liczb unikalnych | Nie „naprawiaj” liczby. Ukryj ją w wierszu sumy przez HASONEVALUE albo policz sumę osobnym wzorem |
| Wiersze znikają z tabeli | Miara zwraca BLANK(), a wizualizacja ukrywa puste wiersze |
Użyj COALESCE(miara, 0), jeśli wiersz ma pozostać widoczny — na przykład dealer z planem, ale bez sprzedaży |
| Porównanie rok do roku jest puste | Kalendarz nie jest oznaczony jako tabela dat, ma luki albo nie obejmuje pełnych lat | Zbuduj kalendarz przez CALENDARAUTO, oznacz go w modelu i sprawdź, czy obejmuje 1 stycznia i 31 grudnia każdego roku |
| Plan pokazuje pustkę mimo danych w tabeli | Niezgodność typów albo zapisu w kolumnach łączonych przez TREATAS |
Porównaj wartości po obu stronach: liczba kontra tekst, „01” kontra 1, spacje na końcu identyfikatora |
| Udziały procentowe nie sumują się do stu | Mianownik policzony przez ALL, więc ignoruje wybór użytkownika w slicerach |
Zamień na ALLSELECTED — wtedy podstawą jest to, co użytkownik faktycznie widzi |
| Ranking wszędzie pokazuje jedynkę | Brak ALL w pierwszym argumencie RANKX — porównanie odbywa się w obrębie jednego wiersza |
Dodaj ALL lub ALLSELECTED na kolumnie, po której budujesz ranking |
| Błąd o braku kontekstu wiersza | Użycie RELATED albo odwołania do kolumny w mierze bez iteratora |
Opakuj wyrażenie w SUMX lub inny iterator, albo sięgnij po agregację zamiast pojedynczej wartości |
| Wynik zmienia się po dodaniu slicera, choć nie powinien | Filtrowanie dwukierunkowe w relacji przenosi filtr tam, gdzie nie powinien dotrzeć | Wyłącz filtrowanie dwukierunkowe w modelu i włączaj je punktowo przez CROSSFILTER w konkretnej mierze |
| Wizualizacja liczy się kilkanaście sekund | FILTER na całej tabeli faktów albo DISTINCTCOUNT na kolumnie o wysokiej kardynalności |
Filtruj wymiary zamiast faktów, a przy zliczaniu unikalnych rozważ COUNTROWS(SUMMARIZE(...)) |
| Miesiące sortują się alfabetycznie | Kolumna nazwy miesiąca nie ma ustawionego sortowania według kolumny liczbowej | W modelu ustaw sortowanie kolumny Miesiac według NrMiesiaca |
| Liczby zgadzają się w sumie, ale nie w przekrojach | Agregat policzony na złym poziomie: SUM tam, gdzie potrzebny był SUMX |
Sprawdź, czy obliczenie nie wymaga danych z dwóch kolumn naraz — wtedy zawsze potrzebny jest iterator |
Dziesięć kroków, w tej kolejności
Kolejność nie jest przypadkowa. Każdy krok opiera się na poprzednim, a próba przeskoczenia etapu z kontekstem filtra kończy się miesiącami zgadywania, dlaczego liczby nie chcą się zgodzić.
- Zbuduj kalendarz i oznacz go jako tabelę dat. Zanim policzysz cokolwiek. Bez tego połowa katalogu nie działa poprawnie.
- Napisz trzy miary bazowe ze wzorca W01 i sprawdź je na macierzy: sztuki, przychód, marża.
- Poczuj różnicę między
SUMaSUMX. Policz przychód oboma sposobami i zobacz, dlaczego jeden wynik jest bez sensu. - Naucz się
CALCULATEz jednym warunkiem. Sprzedaż BEV, sprzedaż flotowa, sprzedaż jednego regionu. - Zderz się z nadpisywaniem filtra. Wstaw miarę z warunkiem do macierzy po typach napędu i zobacz, co się stanie. Potem dodaj
KEEPFILTERS. - Policz pierwszy udział procentowy i porównaj wynik z
ALLorazALLSELECTEDprzy włączonym slicerze. - Dodaj analizę czasową: rok do roku i narastające, według wzorca W03.
- Podłącz plan przez
TREATAS— wzorzec W02. To moment, w którym DAX zaczyna zarabiać na siebie w pracy planisty. - Przepisz najdłuższą miarę na zmienne
VAR. Zobaczysz, o ile łatwiej ją czytać i o ile szybciej działa. - Zbuduj mostek cena–wolumen–miks ze wzorca W05 i sprawdź, czy kontrola zwraca zero. Jeśli tak, rozumiesz kontekst filtra.