Kontext řádku v jazyku DAX

Úvodní obrázek

Kontext řádku vzniká automaticky při vytvoření počítaného sloupce nebo programově v iteračních funkcích. Výsledkem kontextu řádku je vždy pouze jeden řádek tabulky. Kontext řádku, na rozdíl od kontextu filtru, neprochází přes relace do jiných tabulek v datovém modelu. V tomto příspěvku si na příkladech vysvětlíme co je to kontext řádku a jak s kontextem řádku pracovat v DAX výpočtech.

(Aktualizováno 15. 6. 2024)

K tomuto tématu je k dispozici také video:

Všechny příklady v tomto příspěvku jsou vytvořeny ve cvičném Power BI souboru Adventure Works DW 2020.pbix. Soubor s řešením je k dispozici ke stažení níže pod tímto příspěvkem.

V příspěvku začneme velmi jednoduchými příklady a postupně se budeme posouvat k příkladům složitějším. Některé z příkladů nemusí mít žádný analytický význam, a slouží pouze k vysvětlení dané funkcionality. Ze stejného důvodu mohou některé ze zobrazených příkladů porušovat základní pravidla pro psaní efektivních výpočtů z pohledu výkonnosti.

Kontext řádku v počítaném sloupci

Kontext řádku vzniká automaticky při vytvoření nového počítaného sloupce. Uvažujme například tabulku zákazníků s názvem 'Customer'.

Kontext řádku v jazyku DAX

Tabulka 'Customer' má v použitém modelu 5 sloupců a 18 485 řádků. Pokud v této tabulce vytvoříme nový počítaný sloupec, můžeme se ve výpočtu přímo odkazovat na název kteréhokoliv existujícího sloupce v tabulce. Ve vzorci ale nebudeme zadávat řádek, ze kterého chceme získat aktuální hodnotu. Výběr řádku probíhá automaticky, a v každém řádku tabulky se odkazem na název sloupce budeme odkazovat na aktuální hodnotu z aktuálního řádku. Tento automatický výběr aktuálního řádku se nazývá kontext řádku.

Uvažujme například situaci, kdy bychom chtěli přidat nový sloupec se jménem zákazníka a navíc s městem, ze kterého daný zákazník pochází.

Počítaný sloupec:

Zákazník a město = Customer[Customer] & " - " & Customer[City]

Díky kontextu řádku nemusíme přemýšlet o tom, jak přistoupit k hodnotě z daného sloupce v aktuálním řádku. Tento proces probíhá automaticky a odkazem na sloupec se v kontextu řádku odkazujeme na aktuální hodnotu v aktuálním řádku tabulky.

Kontext řádku v jazyku DAX 2

V dalším jednoduchém příkladu se přesuneme do tabulky prodejů, tabulky 'Sales'. V této tabulce je mimo jiné sloupec 'Sales'[Order Quantity], který obsahuje informaci o počtu odebraných kusů, a dále sloupec 'Sales'[Unit Price], který obsahuje cenu za kterou byl produkt prodán. Vynásobením hodnot z těchto dvou sloupců tak dostaneme celkovou částku v každém řádku tabulky 'Sales'.

Počítaný sloupec:

Prodeje = Sales[Unit Price] * Sales[Order Quantity]

Nový sloupec je nyní součástí tabulky 'Sales' a můžeme ho používat ve výpočtech jako kterýkoliv jiný sloupec. Můžeme si tak například vytvořit měřítko, které bude vracet sumu za prodeje produktů následujícím způsobem.

Měřítko:

Prodeje = SUM(Sales[Prodeje])

Pokud nové měřítko vložíme do vizuálu tabulka spolu s kategoriemi produktů v řádcích vizuálu, výsledek bude vypadat následovně.

Kontext řádku v jazyku DAX 3

Nové měřítko nyní vrací sumu za prodeje produktů v aktuálním kontextu vyhodnocení. Na obrázku výše tvoří kontext vyhodnocení filtr nastavený na každou kategorii v aktuálním řádku vizuálu.

Práce s kontextem řádku v počítaném sloupci je relativně intuitivní. Kontext řádku ale můžeme vytvořit také programově pomocí celé řady iteračních funkcí, jak si ukážeme v následující části příspěvku.

Kontext řádku v iteračních funkcích

Kontext řádku v iteračních funkcích se nijak neliší od kontextu řádku v počítaných sloupcích. V jazyku DAX máme k dispozici celou řadu iteračních funkcí. Mezi iterační funkce patří například funkce SUMX(), AVERAGEX(), MINX() a další agregační funkce s X na konci. Dále existují iterační funkce které vrací tabulky. Zde můžeme zmínit například funkce ADDCOLUMNS(), SELECTCOLUMNS(), FILTER() a jiné. Všechny základní iterační funkce mají ale jedno společné, a to tabulku v prvním argumentu, a výraz nebo více výrazů které jsou vyhodnoceny v kontextu řádku tabulky v prvním argumentu.

Používání iteračních funkcí může být zpočátku relativně složité, protože tabulka v prvním argumentu těchto funkcí je tzv. virtuální tabulka, kterou v rámci výpočtu nevidíme a která může obsahovat různé řádky podle aktuálních filtrů, které na výpočet působí. Začneme proto opět jednoduchým příkladem.

V předchozím příkladu jsme si vytvořili počítaný sloupec prodeje, ve kterém jsme v tabulce 'Sales' násobili hodnoty ze sloupců 'Sales'[Order Quantity] a 'Sales'[Unit Price]. Následně jsme tento nový sloupec použili v měřítku [Prodeje]. Díky iteračním funkcím ale můžeme stejného výsledku dosáhnout bez nutnosti tvořit nový počítaný sloupec. Stačí v iterační funkci SUMX() použít v prvním argumentu odkaz na tabulku 'Sales', a ve druhém argumentu funkce SUMX() zadat stejný výraz, který jsme použili pro vytvoření počítaného sloupce.

Měřítko:

Prodeje (2) =
SUMX
(
    Sales,
    Sales[Unit Price] * Sales[Order Quantity]
)

Funkce SUMX() je iterační funkce, která načte všechny řádky z tabulky 'Sales' které jsou dostupné v aktuálním kontextu filtru. Kontext filtru mohou tvořit například řádky vizuálu tabulka nebo průřezy atd. V takto zafiltrované tabulce následně dojde k vyhodnocení výrazu ve druhém argumentu, a to řádek po řádku, tedy v pro nás již známém kontextu řádku. Měřítko [Prodeje (2)] tedy bude vracet za každých okolností stejné hodnoty jako původní měřítko [Prodeje].

Kontext řádku v jazyku DAX 4

Velkou výhodou měřítka [Prodeje (2)] je že oproti měřítku [Prodeje] nemusíme k dosažení stejného výsledku rozšiřovat model o další sloupec. V našem příkladu není přidání jednoho sloupce žádný problém, nicméně v reálném modelu obvykle pracujeme s celou řadou výpočtů, a přidáváním nových a nových sloupců bychom mohli výrazně znepřehlednit celý model. Více sloupců v modelu také znamená větší velikost modelu, což by v konečném důsledku mohlo mít vliv na výkonnost a vytíženost výpočetních prostředků.

V některých typech výpočtů se můžeme setkat také s existencí více kontextů řádků současně, jak si ukážeme v následující části příspěvku.

Kontext řádku a vnořené iterace

Vnořování iteračních funkcí není úplně dobrá praxe s pohledu výkonnosti. Existují ale typy výpočtů, ve kterých je vnoření iteračních funkcí nezbytné pro dosažení požadovaného výsledku. To ale nebude platit v následujícím příkladu, který sice bude obsahovat vnořené iterační funkce, nicméně stejného výsledku bychom mohli dosáhnout i efektivnějším způsobem. Důvod pro použití následujícího příkladu je čistě edukativní, s cílem co nejjednodušším způsobem znázornit přítomnost více kontextů řádků v jednom výrazu.

Uvažujme situaci kdy bychom chtěli vytvořit výpočet který bude vracet sumu za prodeje produktů, nicméně u prodejů za určité produkty budeme chtít zobrazit částku nižší, než ve skutečnosti byla. Taková situace může nastat například pokud jsme prodávali určité produkty se slevou, nicméně informace o slevě nejsou uvedeny v tabulce prodejů.

Produkty které se prodávali ve slevě jsou všechny produkty v kategorii "Bikes". Kategorie produktů je uvedena v tabulce 'Product'. Informace o prodejích je pak v tabulce 'Sales'. Pro dosažení požadovaného výsledku proto potřebujeme pracovat s oběma tabulkami, čehož můžeme dosáhnout právě pomocí dvou vnořeních iterací. Každá iterační funkce ale bude tvořit kontext řádku, a proto v takto popsaném výpočtu budou existovat dva různé kontexty řádku současně.

Měřítko:

Prodeje (sleva na kola) =
SUMX
(
    'Product',
    SUMX
    (
        RELATEDTABLE(Sales),
        IF('Product'[Category] = "Bikes", Sales[Prodeje] * 0.95, Sales[Prodeje])
    )
)

První funkce SUMX() nejdříve načte všechny řádky z tabulky produktů dostupné v aktuálním kontextu vyhodnocení. Následně je pro každý řádek tabulky produktů vyhodnocen výraz ve druhém argumentu funkce SUMX(), který obsahuje další funkci SUMX(). Ve vnitřní funkci SUMX() je nejdříve vyhodnocena tabulka prodejů 'Sales', která bude vracet ty řádky, které jsou k dispozici pro aktuální řádek v tabulce 'Product'. Ve druhém argumentu vnitřní funkce SUMX() pak můžeme přistupovat přímo ke sloupcům jak z tabulky 'Product', tak z tabulky 'Sales', protože v této části výpočtu existuje kontext řádku pro obě tabulky. Díky tomu můžeme ve funkci IF() ověřit, jestli je v aktuálním řádku tabulky 'Product' kategorie "Bikes". Pokud ano, aplikujeme pěti procentní slevu, pokud ne, použijeme celou částku prodejů. Pokud nové měřítko vložíme do původního vizuálu tabulka, výsledek bude vypadat následovně.

Kontext řádku v jazyku DAX 5

Jak je možné vidět na obrázku výše, měřítko [Prodeje (sleva na kola)] vrací pro kategorii "Bikes" nižší hodnotu než měřítko [Prodeje]. Pro ostatní kategorie produktů vrací obě měřítka stejné hodnoty.

Poznámka: Měřítko [Prodeje (sleva na kola)] vrací správné výsledky, nicméně není ideální z pohledu výkonnosti, protože vyhodnocení IF() funkce v rámci iteračních funkcí může způsobovat u větších tabulek zpomalení výpočtu. Lepší variantu podobného výpočtu můžete najít v poslední části příspěvku věnovanému funkci IF().

Speciální situace pak může nastav v případě vnořených iterací dvou stejných tabulek. Pokud pracujeme s více kontexty řádku, a tyto kontexty řádku tvoří stejné sloupce, tak aktivní je pouze nejvnitřnější kontext řádku, a vnější kontext řádku je neaktivní.

Tato situace často nastává v počítaných sloupcích, což si můžeme opět znázornit na jednoduchém příkladu. Uvažujme například tabulku 'Product', ve které bychom chtěli vytvořit počítaný sloupec který bude obsahovat v každém řádku tabulky počet produktů, které patří do kategorie produktů v aktuálním řádku.

Kontext řádku v jazyku DAX 6

V takto popsaném výpočtu budeme nejdříve načítat všechny produkty, které následně zafiltrujeme pouze na ty produkty, které patří do kategorie produktů v aktuálním řádku tabulky. Pokud následně spočítáme řádky v takto zafiltrované tabulce, dostaneme požadovaný výsledek. Následující výpočet sice odpovídá popsané logice, nicméně nevrací správné výsledky.

Počítaný sloupec:

Počet produktů v kategorii (špatně) =
VAR ProduktyVKategorii =
    FILTER
    (
        'Product',
        'Product'[Category] = 'Product'[Category]
    )
VAR Vypocet = COUNTROWS(ProduktyVKategorii)
RETURN
    Vypocet

Problémem výše uvedeného počítané sloupce je druhý argument funkce FILTER(). Zde si musíme uvědomit, že celý výpočet je vyhodnocen v kontextu řádku tabulky 'Product', ve které tvoříme počítaný sloupec. Následně v prvním argumentu funkce FILTER() znovu načítáme celou tabulku 'Product', kterou potřebujeme zafiltrovat pouze na ty produkty, které patří do aktuální kategorie v aktuálním řádku tabulky. Podmínka ve druhém argumentu funkce FILTER() ale vrací vždy hodnotu TRUE, protože vnitřní kontext řádku vytvořený funkcí FILTER() zneaktivnil vnější kontext řádku vznikající v počítaném sloupci, protože oba kontexty řádku obsahují stejné sloupce. Ve funkci FILTER() tak ve skutečnosti ověřujeme, jestli se aktuální hodnota v aktuálním řádku tabulky 'Product' zadané v prvním argumentu funkce FILTER() rovná té stejné hodnotě, což je vždycky pravda. Výsledkem nového počítaného sloupce je proto vždy počet všech řádků v tabulce 'Product'.

Kontext řádku v jazyku DAX 7

Pro dosažení požadovaného výsledku si musíme nejdříve uložit aktuální hodnotu ze slupce 'Product'[Category], a to mimo funkci FILTER(), která zneaktivní kontext řádku vytvořený v počítaném sloupci. Tuto hodnotu následně můžeme použít pro sestavení logické podmínky ve druhém argumentu funkce FILTER().

Počítaný sloupec:

Počet produktů v kategorii =
VAR AktualniKategorie = 'Product'[Category]
VAR ProduktyVKategorii =
    FILTER
    (
        'Product',
        'Product'[Category] = AktualniKategorie
    )
VAR Vypocet =
    COUNTROWS(ProduktyVKategorii)
RETURN
    Vypocet

Nový počítaný sloupec již bude vracet počet produktů v aktuální kategorii.

Kontext řádku v jazyku DAX 8

Jak je možné vidět na obrázku výše, počítaný sloupec [Počet produktů v kategorii] nyní vrací pro každou kategorii produktů jinou hodnotu, která odpovídá počtu záznamů v tabulce 'Product' která je zafiltrovaná na aktuální kategorii.

Shrnutí

Kontext řádku vzniká automaticky při vytvoření nového počítaného sloupce v tabulce. Výpočet v měřítku je na druhou stranu vyhodnocen v kontextu filtru. Pokud potřebujeme vyhodnotit výpočet v měřítku v kontextu řádku tabulky, musíme kontext řádku vytvořit programově. K vytvoření kontextu řádku jsou v jazyku DAX k dispozici iterační funkce, které mají obvykle dva povinné argumenty. Prvním argumentem je tabulka, která je vyhodnocena v kontextu filtru. Druhým argumentem je pak výraz, který je vyhodnocen v nově vzniklém kontextu řádku tabulky uvedené v prvním argumentu iterační funkce. Dalším důležitou součástí kontextu vyhodnocení je kontext filtru. Více informací o kontextu filtru můžete najít v samostatném příspěvku.

č. 7

Komentáře