Změna kontextu řádku na kontext filtru v jazyku DAX

Úvodní obrázek

Změna kontextu řádku na kontext filtru je proces, který mění existující kontext řádku na jeho ekvivalent ve formě kontextu filtru. Znalost principu kontextu řádku a znalost principu kontextu filtru je tedy základním předpokladem pro porozumění procesu změny kontextu. Ke změně kontextu dochází před vyhodnocením prvního argumentu ve funkcích CALCULATE() a CALCULATETABLE().

Funkce CALCULATE a změna kontextu

Změna kontextu probíhá před vyhodnocením výrazu (prvního argumentu) ve funkci CALCULATE() a CALCULATETABLE(). Pro zjednodušení uvažujme v tomto článku pouze funkci CALCULATE(). Všechny uvedené principy nicméně platí také pro funkci CALCULATETABLE() s tím rozdílem, že funkce CALCULATE() vrací skalární hodnotu, zatímco funkce CALCULATETABLE() vrací tabulku.

Při změně kontextu dochází k zneplatnění všech kontextů řádků, ve kterých je funkce CALCULATE() vyhodnocena. Následně je funkcí CALCULATE() vytvořen nový kontext filtru. Tento nový kontext filtru obsahuje všechny sloupce a jejich aktuálních hodnoty, které v aktuálním kroku iterace představovaly původní kontext řádku.

Kontext řádku obsahuje vždy pouze jeden řádek, nový kontext filtru pak může po jeho aplikaci filtrovat více řádků, pokud tyto řádky mají ve všech sloupcích stejné hodnoty. 

Dalším důležitým prvkem principu změny kontextu je fakt, že kontext řádku iteruje tabulku, zatímco kontext filtru filtruje model. Kontext řádku tedy není propagován přes relace, zatímco kontext filtru ano.

Pokud při vyvolání změny kontextu existuje více kontextů řádku, například díky vnořeným iteračním funkcím, nový kontext filtru reflektuje všechny tyto řádky a jejich sloupce s jejich aktuálními hodnotami, které dříve tvořili kontexty řádku.

Změna kontextu v počítaných sloupcích

Princip změny kontextu můžeme pozorovat na následujícím jednoduchém příkladu, který je vytvořen ve cvičném souboru Contoso Sales Sample for Power BI Desktop.pbix.

V Power BI souboru si v tabulce 'Sales'  nejdříve vytvoříme nový počítaný sloupec, kde budeme počítat sumu ze sloupce celkových prodejů.

Počítaný sloupec:

SumaProdeju = SUM(Sales[SalesAmount])

Výsledkem bude nový sloupec Sales[SumaProdeju], který je nyní součástí tabulky.

DAX - Změna kontextu řádku na kontext filtru 2

Výsledkem nového sloupce je stejná hodnota v každém řádku, která se rovná sumě prodejů z celé tabulky, bez ohledu na kontext řádku, který vzniká vždy při vytvoření nového kalkulovaného sloupce. To je dáno tím, že sumarizační funkce, mezi které patří také funkce SUM(), ignorují kontext řádku.

Pokud stejný výpočet zabalíme do funkce CALCULATE(), dojde ke změně kontextu řádku na kontext filtru. Nový kontext filtru obsahuje celý řádek, ve kterém se při iteraci aktuálně nacházíme. Jak již víme, sumarizační funkce ignorují kontext řádku, nicméně nový kontext filtru již výsledek ovlivní.

Počítaný sloupec:

SumaProdejuCalculate = CALCULATE(SUM(Sales[SalesAmount]))

Stejný výpočet již díky funkci CALCULATE() vrací jiné výsledky.

DAX - Změna kontextu řádku na kontext filtru 3

Pokud v tabulce nejsou duplicitní řádky, bude se hodnota takto vytvořeného vzorce rovnat v každém řádku hodnotě ze zdrojového sloupce. Pokud by byly v tabulce duplicitní řádky, dojde po změně kontextu řádku na kontext filtru k duplicitnímu započtení hodnot, které jsou právě v opakujících se řádcích. 

Tento jev si je možné představit tak, že pokud zafiltrujeme tabulku celým jedním řádkem(nový kontext filtru), tak výsledkem jsou všechny řádky, které odpovídají hodnotám tohoto filtru. Pokud tedy jednomu řádku odpovídá více stejných řádků, budou tyto započítány opakovaně. Tento problém je detailněji popsán v článku pod tímto odkazem.

Protože má tabulka 'Sales' primární klíč, k duplicitnímu započítání hodnot nemůže dojít a výsledkem nového počítaného sloupce jsou hodnoty, které odpovídají hodnotám ve zdrojovém sloupci pro daný řádek.

Měřítka a změna kontextu

Ke změně kontextu dochází také při vyhodnocení měřítek, pokud je v době vyhodnocení měřítka aktivní jeden nebo více kontextů řádku. Každé měřítko, které vytvoříme v modelu, je totiž na pozadí obalené do funkce CALCULATE().

Měřítko [SalesAmount] je v modelu vytvořené jako suma hodnot ze sloupce 'Sales'[Sales Amount].

Měřítko:

SalesAmount = SUM(Sales[SalesAmount])

Ve skutečnosti je měřítko při jeho použití vyhodnoceno následovně.

Měřítko:

SalesAmount =
CALCULATE
(
SUM(Sales[SalesAmount])
)

Tento fakt si můžeme jednoduše ověřit tak, že vložíme odkaz na měřítko při definici nového počítaného sloupce v tabulce 'Sales'.

Počítaný sloupec:

SumaProdejuMeritko = [SalesAmount]

Jak je možné vidět na obrázku níže, při vyhodnocení takto vytvořeného počítaného sloupce také došlo ke změně kontextu řádku na kontext filtru.

DAX - Změna kontextu řádku na kontext filtru 4

Pro připomenutí si znovu uvedeme definici sloupce 'Sales'[SumaProdeju], 'Sales'[SumaProdejuCalculate] a  sloupce 'Sales'[SumaProdejuMeritko].

Počítané sloupce:

SumaProdeju = SUM(Sales[SalesAmount])
SumaProdejuCalculate = CALCULATE(SUM(Sales[SalesAmount]))
SumaProdejuMeritko = [SalesAmount]

Protože je měřítko [SalesAmount] na pozadí obalené do pro autora skryté funkce CALCULATE(), došlo při jeho vyhodnocení v počítaném sloupci ke změně kontextu řádku na kontext filtru. Výsledek sloupce 'Sales'[SumaProdejuMeritko] je v každém řádku tabulky stejný, jako sloupce 'Sales'[SumaProdejuCalculate], který obsahuje explicitně vloženou funkci CALCULATE(). 

Změna kontextu v iteračních funkcích

Kontext řádku vzniká automaticky také v iteračních funkcích. Pokud bychom chtěli vytvořit novou počítanou tabulku s kategoriemi produktů v jednom sloupci a s prodeji produktů podle kategorií v druhém sloupci, níže uvedený postup nebude fungovat.

Počítaná tabulka:

Kategorie a prodeje nefunkční =
ADDCOLUMNS
(
VALUES(ProductCategory[ProductCategory]),
"Prodeje",
SUM(Sales[SalesAmount])
)

Výsledek takto definované počítané tabulky vypadá následovně. 

DAX - Změna kontextu řádku na kontext filtru 5

Výraz SUM(Sales[SalesAmount]) je vyhodnocen v kontextu řádku každé kategorie, který je vytvořen iterační funkcí ADDCOLUMNS(). Sumarizační funkce ale ignorují kontext řádku. Pokud ale v šestém řádku kódu změníme kontext řádku na kontext filtru pomocí funkce CALCULATE(), výsledkem bude požadovaná tabulka s kategoriemi v prvním sloupci a hodnotami prodejů podle kategorií ve sloupci druhém.

Počítaná tabulka:

Kategorie a prodeje =
ADDCOLUMNS
(
VALUES(ProductCategory[ProductCategory]),
"Prodeje",
CALCULATE
(
SUM(Sales[SalesAmount])
)
)

Výsledek takto definované počítané tabulky bude vypadat následovně.

DAX - Změna kontextu řádku na kontext filtru 6

Funkce CALCULATE() vyvolala změnu kontextu řádku na kontext filtru. Výraz SUM(Sales[SalesAmount]) je proto vyhodnocen v kontextu filtru každé kategorie. Pozorný čtenář může podotknout, že stejného výsledku bychom dosáhli také při použití měřítka [SalesAmount] díky funkci CALCULATE() na pozadí každého měřítka, stejně jako v případě příkladu s počítanými sloupci.

Změna kontextu a vztahy mezi tabulkami

Kontext řádku není propagován přes relace do provázaných tabulek. Na druhou stranu kontext filtru přes relace prochází. Proto je možné využít změnu kontextu pro získání hodnot z jiné tabulky, kde řádky v tabulce, ve které tvoříme nový počítaný sloupec, tvoří po  vyvolání změny kontextu v každém kroku iterace filtr pro navázanou tabulku.

DAX - Změna kontextu řádku na kontext filtru 7

Model v souboru Contoso Sales Sample for Power BI Desktop nastavenou relaci mezi tabulkou 'Product' a tabulkou 'Sales'. Díky této relaci můžeme v dimenzní tabulce 'Product' vytvořit nový počítaný sloupec, kde budeme pro každý jeden produkt počítat sumu prodejů z faktové tabulky 'Sales'.

Počítaný sloupec:

ProdejeProduktu = CALCULATE(SUM(Sales[SalesAmount]))

Výsledkem je nový počítaný sloupec v tabulce 'Product', který obsahuje sumu za prodeje konkrétního produktu.

DAX - Změna kontextu řádku na kontext filtru 8

Funkce CALCULATE() vytvoří z každého řádku tabulky 'Product' nový filtr, který obsahuje všechny sloupce a jejich hodnoty z aktuálního řádku, ve kterém je výpočet sumy prodejů vyhodnocen. Tento filtr je pak aplikován na celý model, a díky relaci také na tabulku 'Sales'. Hodnota nového sloupce 'Product'[ProdejeProduktu] proto v každém řádku obsahuje sumu prodejů z tabulky 'Sales' pro daný produkt v aktuálním řádku.

Pořadí vyhodnocení filtrů

Ke změně kontextu dochází před aplikací filtrů ve funkcích CALCULATE() a CALCULATETABLE(). To je možné vidět na následujícím příkladu, kdy pomocí funkce REMOVEFILTERS() odstraníme filtry z tabulky 'Product', ve které je kalkulovaný sloupec vytvořen a kde dochází k přeměně kontextu řádku na kontext filtru, který je následně díky funkci REMOVEFILTERS() odebrán.

Počítaný sloupec:

ProdejeProduktuREMOVEFILTERS =
CALCULATE
(
SUM(Sales[SalesAmount]),
REMOVEFILTERS('Product')
)

Výsledek výše uvedeného výpočtu vypadá následovně.

DAX - Změna kontextu řádku na kontext filtru 9

Výsledkem je suma všech hodnot ze sloupce 'Sales'[Sales Amount]. Nově vzniklý kontext filtru byl odstraněn pomocí funkce REMOVEFILTERS(), protože aplikování filtrů uvnitř funkce CALCULATE() nastává až po změně kontextu vyhodnocení.

Shrnutí

Princip změny kontextu řádku na kontext filtru je dobré znát, abychom se při výpočtech vyhnuli neočekávaným výsledkům. Změna kontextu je velice užitečná vlastnost, která může být pro příležitostného autora DAX kódu zcela skrytá, nicméně při detailní znalosti tohoto principu může být využita k psaní pokročilých DAX výrazů.

Ke změně kontextu řádku na kontext filtru dochází při vyhodnocení funkcí CALCULATE() a CALCULATETABLE(). Protože je každé měřítko na pozadí obalené skrytě do funkce CALCULATE(), dochází ke změně kontextu také při vyhodnocení měřítek, pokud jsou vyhodnoceny v kontextu řádku.

Mezi kontextem řádku a kontextem filtru je zásadní rozdíl. Kontextu řádku odpovídá vždy jeden jediný konkrétní řádek. Po změně kontextu řádku na kontext filtru může dojít k situaci, kdy po aplikaci nového kontextu filtru bude filtrovaná tabulka obsahovat více řádků. Se změnou kontextu souvisí také agregační funkce, které ignorují kontext řádku, ale kontext filtru reflektují.

č. 16

Komentáře