CALCULATE w DAX: jak działa i kiedy jej używać (przykłady)

Funkcja CALCULATE DAX liczy wyrażenie w zmienionym kontekście filtru. Najprościej mówiąc, bierze Twoje obliczenie, na przykład sumę, średnią albo liczbę wierszy, i przed policzeniem dokłada swoje filtry, zmienia istniejące albo je zdejmuje. Dlatego stoi za większością bardziej złożonych miar w Power BI, od porównania rok do roku po procent udziału w całości.

Pokażę Ci, jak CALCULATE działa w praktyce, na danych HR, tych samych, na których pracujemy w kursie Power BI. Jeśli dopiero zaczynasz z językiem DAX, ten artykuł dobrze uzupełni podstawy. Na końcu znajdziesz błędy, o które najczęściej pytają osoby uczące się pisać miary.

Co robi funkcja CALCULATE?

Zacznijmy od intuicji. Każda miara w Power BI liczy się w jakimś kontekście. Jeśli wrzucisz miarę Liczba pracowników do tabeli z działami, w wierszu Production zobaczysz liczbę pracowników produkcji, a w wierszu Sales liczbę pracowników sprzedaży. Kod miary jest ten sam, zmienia się tylko to, co Power BI w danym miejscu przefiltrował.

CALCULATE pozwala zmienić ten kontekst od środka miary. W kursie mówię o niej, że to taki modyfikator filtrów: funkcja nałoży swoje filtry, nawet jeśli na raporcie nikt ich nie zaznaczył.

W praktyce ta funkcja może:

  • dodać filtr, na przykład policzyć tylko aplikantów na stanowiska Engineer,
  • nadpisać filtr, który już istnieje na tej samej kolumnie,
  • usunąć filtry, gdy połączysz ją z ALL albo REMOVEFILTERS.

Ma też drugie ważne zastosowanie: potrafi przekształcić kontekst wiersza w kontekst filtru (ang. context transition). Spotkasz się z tym przy bardziej zaawansowanych obliczeniach, na przykład w iteratorach typu SUMX. Tutaj skupiamy się na filtrach, bo od nich warto zacząć.

Składnia CALCULATE

				
					CALCULATE(<wyrażenie>, <filtr1>, <filtr2>, ...)
				
			

Wyrażenie to obliczenie, które chcesz wykonać. Musi zwracać jedną wartość, więc najczęściej będzie to SUM, AVERAGE, COUNTROWS albo inna miara.

Filtry określają warunki, w jakich wyrażenie ma zostać policzone. Możesz podać jeden, kilka albo żadnego. Filtrem może być:

  • warunek logiczny na kolumnie, na przykład EmployeeData[Department Type] = "Production",
  • tabela, na przykład wynik funkcji FILTER,
  • funkcja modyfikująca filtry, na przykład ALL, REMOVEFILTERS albo KEEPFILTERS.

Kilka filtrów podanych po przecinku musi być spełnionych jednocześnie, tak jak przy AND. Pełny opis parametrów znajdziesz w dokumentacji funkcji CALCULATE na Microsoft Learn.

Kontekst filtru: skąd miara wie, że ma liczyć per dział?

W tym miejscu większość osób się zacina. Dobrze pokazuje to pytanie, które dostałam od jednej z osób w kursie. Dotyczyło miary porównującej średnią ocenę pracowników w dziale ze średnią w całej firmie:

				
					Filter vs Overall Rating Comparison =
VAR AVGRating = AVERAGE(EmployeeData[Current Employee Rating])
VAR AVGRatingALL =
    CALCULATE(
        AVERAGE(EmployeeData[Current Employee Rating]),
        ALL(EmployeeData)
    )
RETURN
    DIVIDE(AVGRating, AVGRatingALL)
				
			

Pytanie brzmiało mniej więcej tak: skoro w mierze nigdzie nie odwołujemy się do działu, skąd Power BI ma wiedzieć, że ma liczyć średnią per dział?

Dział przychodzi z wizualizacji, więc w kodzie nie musi się pojawiać. Kiedy wrzucasz tę miarę do tabeli, w której wierszami są działy, Power BI liczy ją osobno dla każdego wiersza. W wierszu Production wszystkie dane są już przefiltrowane do produkcji. To jest właśnie kontekst filtru: zestaw filtrów, które przychodzą z wizualizacji, fragmentatorów i panelu filtrów.

Zobacz, co dzieje się w każdym wierszu tej tabeli:

  • AVGRating liczy się w kontekście z wizualizacji. W wierszu Production to średnia ocena pracowników produkcji.
  • AVGRatingALL to CALCULATE z ALL. W tym modelu dział jest kolumną tabeli EmployeeData, więc ALL(EmployeeData) zdejmuje również filtr działu i w każdym wierszu wynik jest ten sam: średnia ocena wszystkich pracowników w danych.
  • DIVIDE dzieli jedno przez drugie. Wynik powyżej 1 oznacza dział z oceną wyższą niż średnia firmy, a poniżej 1 dział z oceną niższą.

Jeśli chcesz to zobaczyć na własne oczy, dodaj obie zmienne jako osobne miary do tej samej tabeli z działami. Pierwsza będzie się zmieniać w każdym wierszu, a druga wszędzie zostanie taka sama. To proste ćwiczenie dobrze pokazuje, jak działa kontekst filtru.

CALCULATE DAX z ALL: średnia ocena pracowników w działach i średnia całej firmy w Power BI

Na tych danych średnia ocena w Executive Office wynosi 2,79, a w całej firmie 2,97. Miara zwraca więc 0,94, czyli dział wypada o około 6% poniżej średniej. Admin Offices ma wynik 1,02, czyli trochę powyżej. Zwróć też uwagę na wiersz Total: tam filtra działu nie ma, więc AVGRating i AVGRatingALL pokazują tę samą wartość, a porównanie daje równe 1.

CALCULATE DAX w praktyce: przykłady na danych HR

Przykłady pochodzą z modelu HR z trzema tabelami: EmployeeData (pracownicy), RecruitmentData (kandydaci) i TrainingDevelopmentData (szkolenia).

Jeden warunek: ilu kandydatów aplikowało na stanowiska Engineer?

				
					Applicants for Engineer Positions =
CALCULATE(
    COUNTROWS(RecruitmentData),
    CONTAINSSTRING(RecruitmentData[Job Title], "Engineer")
)
				
			

COUNTROWS liczy wiersze w tabeli RecruitmentData. Przed policzeniem CALCULATE nakłada warunek, który zostawia tylko wiersze z nazwą stanowiska zawierającą tekst „Engineer”. CONTAINSSTRING nie rozróżnia wielkości liter, więc uwzględni też zapis engineer albo ENGINEER.

Warto pamiętać, że CONTAINSSTRING wyszukuje fragment tekstu, dlatego może uwzględnić również stanowiska takie jak Engineering Manager. Z kolei COUNTROWS liczy liczbę rekordów, więc jeśli jeden kandydat aplikował na kilka stanowisk i występuje w tabeli kilkukrotnie, zostanie policzony więcej niż raz. W takim przypadku otrzymasz liczbę aplikacji, a nie unikalnych kandydatów.

				
					Average Desired Salary for Engineer Applicants =
CALCULATE(
    AVERAGE(RecruitmentData[Desired Salary]),
    CONTAINSSTRING(RecruitmentData[Job Title], "Engineer")
)
				
			

Kilka warunków: kto pracuje w firmie dłużej niż 5 lat?

				
					Long-term Employees =
CALCULATE(
    COUNTROWS(EmployeeData),
    EmployeeData[Start Date] < EDATE(TODAY(), -60),
    ISBLANK(EmployeeData[Exit Date])
)
				
			

Tu działają dwa filtry. Pierwszy zostawia osoby zatrudnione wcześniej niż 60 miesięcy temu. EDATE(TODAY(), -60) zwraca datę sprzed dokładnie pięciu lat, więc warunek obejmuje każdego, kto przekroczył piątą rocznicę zatrudnienia. Drugi filtr zostawia osoby bez daty odejścia, które nadal pracują w firmie. Oba warunki muszą być spełnione jednocześnie.

Możesz się zastanawiać, dlaczego nie użyłam tutaj DATEDIFF z parametrem YEAR. Ta funkcja liczy przekroczenia granic lat kalendarzowych, a nie pełne lata zatrudnienia.

Weźmy pracownika zatrudnionego 15 stycznia 2021 roku. Na dzień 28 października 2026 roku pracuje już ponad pięć lat, ale DATEDIFF z parametrem YEAR zwróci 5. Warunek DATEDIFF(...) > 5 go więc pominie, mimo że spełnia nasze kryterium.

Dlatego używam EDATE(TODAY(), -60), które wyznacza datę sprzed pięciu lat. Dzięki temu uwzględniamy wszystkich pracowników, którzy rzeczywiście przekroczyli piątą rocznicę zatrudnienia.

Odwrotność drugiego warunku daje Ci osoby, które odeszły z firmy:

				
					Employees Left =
CALCULATE(
    COUNTROWS(EmployeeData),
    NOT(ISBLANK(EmployeeData[Exit Date]))
)
				
			

Warunek LUB: kilka wartości w jednej kolumnie

Filtry podane po przecinku działają jak AND. Kiedy potrzebujesz warunku LUB, na przykład pracowników sprzedaży albo produkcji, najczytelniej zapisać go operatorem IN:

				
					Employees in Sales or Production =
CALCULATE(
    COUNTROWS(EmployeeData),
    EmployeeData[Department Type] IN {"Sales", "Production"}
)
				
			

Ten sam wynik da zapis z operatorem ||: EmployeeData[Department Type] = "Sales" || EmployeeData[Department Type] = "Production". Przy dłuższej liście wartości IN jest po prostu wygodniejszy.

Usuwanie filtrów: CALCULATE z ALL

ALL ma dwa zastosowania. Może zwracać wszystkie wiersze tabeli albo wartości kolumny z pominięciem filtrów. Użyta jako argument w CALCULATE działa jak modyfikator i usuwa filtry z kontekstu obliczenia. Tutaj interesuje nas właśnie to drugie zastosowanie.

Klasyczny przykład to procent udziału w całości. Jaki procent kandydatów aplikował na stanowiska Engineer?

				
					Percentage of Engineer Applicants =
DIVIDE(
    CALCULATE(
        COUNTROWS(RecruitmentData),
        CONTAINSSTRING(RecruitmentData[Job Title], "Engineer")
    ),
    CALCULATE(
        COUNTROWS(RecruitmentData),
        ALL(RecruitmentData)
    )
)
				
			

Licznik to znany już filtr na Engineer. Mianownik liczy kandydatów, a ALL usuwa filtry z tabeli RecruitmentData, więc obejmuje wszystkich kandydatów niezależnie od wyborów na raporcie.

Ten sam schemat sprawdza się przy porównaniach. Żeby zestawić średni koszt szkolenia Technical Skills ze średnią wszystkich szkoleń, potrzebujesz dwóch miar i trzeciej, która je dzieli:

				
					Average Cost of Technical Skills Training =
CALCULATE(
    AVERAGE(TrainingDevelopmentData[Training Cost]),
    TrainingDevelopmentData[Training Program Name] = "Technical Skills"
)

Average Cost of All Trainings =
CALCULATE(
    AVERAGE(TrainingDevelopmentData[Training Cost]),
    ALL(TrainingDevelopmentData)
)

Cost Ratio of Technical Skills to Average =
DIVIDE(
    [Average Cost of Technical Skills Training],
    [Average Cost of All Trainings]
)
				
			

Porównanie z poprzednim rokiem

Ten przykład wychodzi poza dane HR, bo najczęściej spotkasz go w raportach sprzedażowych. Sprzedaż sprzed roku liczysz tak:

				
					Sprzedaż rok wcześniej =
CALCULATE(
    SUM(Sprzedaż[Kwota]),
    SAMEPERIODLASTYEAR(Kalendarz[Data])
)
				
			

CALCULATE czy FILTER: który zapis wybrać?

Porównaj dwie wersje tej samej miary:

				
					Average Age in Production =
CALCULATE(
    AVERAGE(EmployeeData[Age]),
    EmployeeData[Department Type] = "Production"
)
				
			
				
					Average Age in Production =
CALCULATE(
    AVERAGE(EmployeeData[Age]),
    FILTER(EmployeeData, EmployeeData[Department Type] = "Production")
)
				
			

Na pierwszy rzut oka robią to samo, ale wynik nie zawsze jest identyczny. Zwykły warunek na kolumnie nadpisuje istniejący filtr tej samej kolumny. FILTER przegląda tabelę EmployeeData w takim kształcie, w jakim widać ją w aktualnym kontekście.

Weźmy tabelę, w której wiersze pochodzą z kolumny EmployeeData[Department Type]. W wierszu Sales pierwsza miara zastąpi filtr tej kolumny wartością Production i pokaże średni wiek pracowników produkcji. Druga będzie szukać pracowników produkcji wśród osób przefiltrowanych już do działu Sales, więc zwróci pusty wynik.

Jest też kwestia wydajności. FILTER przechodzi po każdym wierszu tabeli, więc przy dużych danych może spowalniać raport. Prosty warunek na kolumnie silnik Power BI filtruje znacznie sprawniej i właśnie takie podejście zaleca Microsoft w wytycznych dotyczących DAX.

Według dokumentacji Microsoft filtr logiczny w CALCULATE nie może:

  • odwoływać się do miary,
  • odwoływać się do kolumn z kilku różnych tabel,
  • zawierać zagnieżdżonego CALCULATE,
  • używać funkcji, które przeglądają albo zwracają tabelę.

Jeśli Twój warunek wymaga którejś z tych rzeczy, FILTER jest właściwym narzędziem. Tak będzie na przykład, gdy chcesz policzyć kandydatów tylko w miesiącach, w których jakaś miara przekroczyła próg.

Najczęstsze błędy przy CALCULATE

Najczęstsze błędy — szybka diagnoza

Objaw Przyczyna Co zrobić?
Miara daje różne wyniki w wierszach, choć w kodzie nie ma filtra Filtr przychodzi z wizualizacji
✓ Sprawdź wiersze tabeli, fragmentatory i panel filtrów
Mimo ALL lub REMOVEFILTERS wynik reaguje na fragmentator Filtr przychodzi z innej tabeli przez relację
✓ Wskaż właściwą tabelę albo kolumnę
Błąd przy warunku z miarą Filtr logiczny nie przyjmuje miar
✓ Użyj FILTER
Dane produkcji widać w wierszu Sales CALCULATE nadpisała filtr
✓ Użyj KEEPFILTERS

Szukanie filtra w kodzie miary

Jeśli miara pokazuje różne wyniki w różnych wierszach, a w kodzie nie widzisz żadnego warunku, filtr przychodzi z wizualizacji, fragmentatora albo panelu filtrów. Wróć wtedy do sekcji o kontekście filtru.

Zdejmowanie filtrów z niewłaściwej tabeli

To pytanie często pojawia się na grupach o Power BI: w mierze jest REMOVEFILTERS albo ALL, a wynik dalej zmienia się po kliknięciu we fragmentator. Przyczyną może być budowa modelu i kierunek, w którym filtry przechodzą przez relacje. Działa to też w drugą stronę, bo usunięcie filtrów z całej tabeli faktów obejmuje również powiązane z nią tabele wymiarów. Łatwo więc zdjąć więcej, niż się zamierzało.

Zanim użyjesz ALL, odpowiedz sobie, które filtry mają zniknąć, a które powinny dalej wpływać na wynik. Jeśli chcesz zignorować tylko wybór działu, wskaż konkretną kolumnę albo tabelę działów zamiast czyścić całą tabelę pracowników.

Miara w warunku logicznym

Zapis CALCULATE([Sprzedaż], [Marża] > 0.2) nie zadziała, bo filtr logiczny nie może odwoływać się do miary. W takiej sytuacji potrzebujesz FILTER.

Zaskoczenie, że CALCULATE nadpisała filtr

Kiedy warunek dotyczy kolumny przefiltrowanej już w raporcie, CALCULATE zastępuje ten filtr swoim. Miara z warunkiem EmployeeData[Department Type] = "Production" pokaże dane produkcji nawet w wierszu Sales. Jeśli chcesz zawęzić wynik zamiast go nadpisywać, opakuj warunek w KEEPFILTERS:

				
					Average Age in Production (keep filters) =
CALCULATE(
    AVERAGE(EmployeeData[Age]),
    KEEPFILTERS(EmployeeData[Department Type] = "Production")
)
				
			

Teraz w wierszu Production zobaczysz wynik, a w pozostałych działach pustą komórkę, bo filtr z wizualizacji i warunek z miary muszą być spełnione jednocześnie.

Co dalej po CALCULATE

Kiedy CALCULATE przestanie Cię zaskakiwać, większość trudniejszego DAX zacznie się układać. Porównania rok do roku, udziały procentowe, rankingi i miary narastające opierają się na tym samym mechanizmie. Dobry kolejny krok to funkcje inteligencji czasowej i iteratory typu SUMX, bo tam kontekst filtru spotyka się z kontekstem wiersza.

Do samodzielnych ćwiczeń przydadzą Ci się darmowe materiały do nauki DAX, które zebrałam w jednym miejscu.

Jeśli chcesz nauczyć się Power BI i DAX krok po kroku na prawdziwym modelu danych, zajrzyj do mojego Kursu Power BI. Większość przykładów z tego artykułu pochodzi właśnie z kursowego modelu HR.

A jeśli CALCULATE masz już opanowaną i chcesz wejść głębiej w zaawansowany DAX, sprawdź kurs zaawansowanego DAX Marka Gołaszewskiego na DataDo.

2 komentarze do “CALCULATE w DAX: jak działa i kiedy jej używać (przykłady)”

  1. Odnośnik zwrotny: Pytania rekrutacyjne z DAX i Power BI początkujący - Business Intelligence Developer

  2. Odnośnik zwrotny: Język DAX – wykorzystaj potencjał Power BI - Business Intelligence Developer

Możliwość komentowania została wyłączona.

Przewijanie do góry