Uživatelsky definované funkce v jazyku DAX a v Power BI

Úvodní obrázek

Uživatelsky definované funkce v jazyku DAX dávají vývojářům možnost uložit si dovnitř vlastní funkce DAX kód, který je možné následně vyvolat v rámci jakéhokoliv dalšího výpočtu, a to za použití volitelně definovaných parametrů. Uživatelsky definované funkce budou mít široké využití, ale primárním cílem je uložit si na jedno místo často opakovaný výpočet.

V tomto příspěvku si na několika příkladech ukážeme, jak se uživatelsky definované funkce (dále jen zkráceně UDF) vytváří, jak se používají a v jakých situacích mohou být užitečné.

Než se ale přesuneme k vytvoření první UDF, popíšeme si ve stručnosti situaci, ve které může být použití UDF užitečné. Na následujícím obrázku je zachycen vizuál Matice, kde jsou v řádcích roky a měsíce a v hodnotách postupně měřítka [Prodeje], [Hrubí zisk] a [Počet zákazníků].

Uživatelsky definované funkce v jazyku DAX a v Power BI

Měřítka použitá ve vizuálu zobrazeném na obrázku výše jsou jednoduché agregace.

Měřítko:

Prodeje = SUM(Sales[Sales Amount])

Měřítko:

Hrubý zisk =
SUMX
(
    Sales,
    Sales[Sales Amount] - Sales[Total Product Cost]
)

Měřítko:

Počet zákazníků = DISTINCTCOUNT('Sales'[CustomerKey])

Na dalším obrázku pak můžeme vidět variace těchto výpočtů. Měřítka obsahují stejné agregace, ale každé měřítko má navíc filtr, který je vždy stejný a skládá se z nových zákazníků, tedy ze zákazníků, kteří ve vybraném období nakupovali poprvé.

Uživatelsky definované funkce v jazyku DAX a v Power BI 2

Všechna tři měřítka obsahují stejnou logiku, mění se pouze typ agregace. Logika výpočtu je jednoduchá. Nejdříve načteme zákazníky nakupující v aktuálním období, a k těmto zákazníkům přidáme první den jejich nákupu. Následně tuto tabulku se zákazníky zafiltrujeme pouze na ty zákazníky, kteří nakupovali v aktuálním období poprvé, to znamená jejich první den nákupu spadá do aktuálního období. Zafiltrovanou tabulku se zákazníky pak přidáme do filtru funkce CALCULATE() před vyhodnocením příslušného měřítka.

Poznámka: Následující výpočty pocházejí ze samostatného příspěvku, který je dostupný pod tímto odkazem. V odkazovaném článku je možné najít podrobnější popis výpočtů vztažených k analýze nových a vracejících se zákazníků. 

Měřítko:

Prodeje noví zákazníci =
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(Sales[CustomerKey]),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(Sales[Order Date]),
            REMOVEFILTERS('Date')
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES('Date'[Date])
    )
VAR Vysledek =
    CALCULATE
    (
        [Prodeje],
        KEEPFILTERS(NoviZakaznici)
    )
RETURN
    Vysledek

Měřítko:

Hrubý zisk noví zákazníci =
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(Sales[CustomerKey]),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(Sales[Order Date]),
            REMOVEFILTERS('Date')
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES('Date'[Date])
    )
VAR Vysledek =
    CALCULATE
    (
        [Hrubý zisk],
        KEEPFILTERS(NoviZakaznici)
    )
RETURN
    Vysledek

Měřítko:

Noví zákazníci =
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(Sales[CustomerKey]),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(Sales[Order Date]),
            REMOVEFILTERS('Date')
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES('Date'[Date])
    )
VAR Vysledek =
    CALCULATE
    (
        [Počet zákazníků],
        KEEPFILTERS(NoviZakaznici)
    )
RETURN
    Vysledek

Výpočty sice nejsou úplně jednoduché, nicméně jejich pochopení není nezbytné pro další výklad v tomto příspěvku. Důležité pro nás v tuto chvíli je, že každé měřítko obsahuje stejnou logiku, a výpočty se liší pouze typem agregace v proměnné Vysledek.

Situace, kdy stejnou logiku výpočtu kopírujeme a měníme jen jeho určitou část, všichni asi dobře známe. To je přesně ta situace, kdy se vyplatí vytvořit UDF pro uložení výpočtu za účelem jeho opakovaného používání.

Příklad použití vlastní DAX funkce

UDF jsou v době psaní tohoto příspěvku dostupné pouze ve verzi Preview.  Před jejich vytvořením je proto nutné tuto funkcionalitu zapnout v nastavení.

Uživatelsky definované funkce v jazyku DAX a v Power BI 3

Uživatelsky definovanou funkci, stejně jako například měřítka a další objekty, můžeme v současné době vytvořit více různými způsoby. Pro definici nové UDF tak můžeme použít například TMDL skript, dále příkaz DEFINE FUNCTION na záložce Zobrazení dotazů DAX, anebo můžeme UDF vytvořit prostřednictvím uživatelského rozhraní na záložce Zobrazení modelu

Dialog pro vytvoření nové funkce je v současnosti dostupný v sekci Sémantický model pod kategorií Funkce, kde musíme kliknout pravým tlačítkem na nabídku Funkce. Zde také uvidíme všechny UDF v aktuálním Sémantickém modelu.

Uživatelsky definované funkce v jazyku DAX a v Power BI 4

Jak již bylo uvedeno dříve, v současnosti jsou UDF dostupné pouze ve verzi Preview a je proto pravděpodobné, že v budoucnu bude možné vytvořit UDF také pomocí vlastního tlačítka na příslušné kartě v panelu karet, podobně jako je tomu u měřítek a dalších objektů.

V rámci definice UDF můžeme používat jakékoliv DAX funkce, stejně jako v běžném výpočtu. Dále je také možné odkazovat se v rámci jedné UDF na jinou, dříve vytvořenou UDF. Specifikem oproti ostatním DAX výpočtům jsou parametry. Ty jsou nepovinné, a definují se v závorce za názvem funkce. Za názvem funkce a případnými parametry následuje samotné tělo funkce.

Naše funkce, do které si uložíme výpočet pro nové zákazníky, bude vypadat následovně.

Funkce:

VypocetProNoveZakazniky = (meritkoNeboAgregaceEXPR : EXPR) =>
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(Sales[CustomerKey]),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(Sales[Order Date]),
            REMOVEFILTERS('Date')
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES('Date'[Date])
    )
VAR Vysledek =
    CALCULATE
    (
        meritkoNeboAgregaceEXPR,
        KEEPFILTERS(NoviZakaznici)
    )
RETURN
    Vysledek

Při letmém pohledu na definici funkce nazvané VypocetProNoveZakazniky si můžeme všimnout, že tato funkce obsahuje pouze jeden parametr. Parametr je definovaný v závorkách za názvem samotné funkce, jmenuje se meritkoNeboAgregaceEXPR a obsahuje také upřesnění, jak se má tento parametr chovat. Vlastnosti parametrů se definují za dvojtečkou, která následuje za samotným názvem parametru. Použitá zkratka EXPR znamená Expression. K podrobnému popisu, jaký je význam této zkratky a jak pracovat s nastavením parametrů při definici UDF se dostaneme později.

Druhá důležitá informace je, že tento parametr je použitý v proměnné Vysledek, v prvním argumentu funkce CALCULATE(). To je přesně to místo, které jsme v původních výpočtech při jejich kopírování měnili a kde jsme nahrazovali jedno měřítko za druhé. Všechno ostatní zůstávalo v rámci definice výpočtu stejné.

Nyní, když máme vytvořenou funkci s opakovaně používanou logikou, můžeme tuto funkci volat přímo v definici jakéhokoliv výpočtu. Můžeme si tak například vytvořit následující tři měřítka.

Měřítko:

(F) Prodeje noví zákazníci = VypocetProNoveZakazniky([Prodeje])

Měřítko:

(F) Hrubý zisk noví zákazníci = VypocetProNoveZakazniky([Hrubý zisk])

Měřítko:

(F) Noví zákazníci = VypocetProNoveZakazniky([Počet zákazníků])

Použitím UDF VypocetProNoveZakazniky vyvoláme celý výpočet uložený v této funkci. Na místo parametru se pak do těla funkce vloží měřítko, které jsme použili jako argument v aktuální variaci příslušného měřítka.

Pokud nová měřítka vložíme do našeho původního vizuálu Matice, dostaneme požadované výsledky.

Uživatelsky definované funkce v jazyku DAX a v Power BI 5

Na základě předcházejícího příkladu by mělo být zřejmé, v jakých situacích se mohou hodit UDF. Protože UDF mohou vracet také tabulky, a jako parametry UDF můžeme používat konkrétní hodnoty, výpočty, měřítka, tabulky nebo konkrétní sloupce, UDF můžeme použít kdykoliv potřebujeme nějakou část výpočtu uložit pro jeho další použití.

V následující části příspěvku si podrobněji popíšeme práci s uživatelsky definovanými funkcemi v jazyku DAX.

Uživatelsky definované funkce

Při definici UDF pracujeme s názvem funkce (na obrázku níže řádek 5), s parametry funkce (řádek 6), s tělem funkce (řádek 8 až 25) a s popisem funkce (řádek 2 až 4). Popis a parametry funkce jsou nepovinné.

Uživatelsky definované funkce v jazyku DAX a v Power BI 6

Názvy funkcí a parametrů musí být bez mezer a speciálních znaků, vyjma teček nebo podtržítek, a nesmí obsahovat číslice na začátku názvu.

V těle funkce můžeme použít jakýkoliv DAX výpočet, a v rámci tohoto výpočtu se můžeme odkazovat na parametry vytvořené v rámci definice funkce. DAX výpočet v těle funkce může vracet jak skalární hodnotu, tak tabulku. Typický postup při vytváření funkce je vložit do těla funkce již dříve vytvořený DAX výpočet, a určité části tohoto výpočtu nahrazovat parametry, díky kterým můžeme opakovaně používat funkci s jinými objekty nebo s jinými vstupními hodnotami. Tím se dostáváme k tomu nejsložitějšímu, a to je práce s parametry. 

Pokud budeme v rámci definice UDF používat parametry, což je nepovinné, tak je třeba pochopit, jak tyto parametry správně nastavit. Nastavení parametru můžeme provést, opět nepovinně, za dvojtečkou, která následuje za samotným názvem parametru. U parametru pak můžeme ovlivňovat o jaký Typ parametru jde, dále Podtyp parametru, a Režim parametru (originální názvy jsou Typ, SubType a ParametrMode).

Uživatelsky definované funkce v jazyku DAX a v Power BI 7

Orientace v nastavení parametrů není úplně jednoduchá a bude vyžadovat určitý čas a praxi. Začít můžeme tím, co se stane, pokud u názvu parametru neuvedeme Typ, Podtyp ani Režim parametru. V takovéto situaci bude defaultně nastaven parametr na Typ ANYVAL a Režim parametru VAL. To znamená, že v argumentu funkce zastupující takovýto parametr můžeme zadat cokoliv – tabulku, hodnotu, měřítko atd., a to co zadáme bude vyhodnoceno před vyvoláním funkce, a následně vloženo na příslušné místo, kde je parametr použitý v těle funkce.

U jediného Typu SCALAR můžeme vybrat Podtyp, kde Podtyp znamená datový typ, na který se bude snažit funkce zadanou hodnotu převést před vyvoláním funkce.

Pokud zadáme jako Typ TABLE, můžeme vybrat, zda se bude jednat o tabulku v režimu VAL nebo EXPR.

Nejpoužívanější Typy parametru pak budou ANYREF, CALENDARREF, COLUMNREF, MEASUREREF a TALBEREF. Pokud označíme daný parametr například jako typ COLUMNREF, tak funkce bude pro tento parametr přijímat argument pouze ve formě odkazu na sloupec nahraný v modelu. Obdobně pro ostatní Typy z této kategorie, MEASUREREF bude přijímat vždy jen měřítka, TABLEREF jen tabulky nahrané v modelu, a ANYREF jakýkoliv objekt nahraný v modelu, včetně jakéhokoliv výpočtu. Všechny tyto Typy s koncovkou REF jsou vyhodnoceny vždy v režimu EXPR.

Zatímco volbu Typu a případně Podtypu parametru můžeme určit relativně intuitivně, Režim parametru je něco, co je velmi důležité pochopit a co si vysvětlíme na samostatném příkladu.

Nastavení režimu parametru v UDF

Režim parametru můžeme nastavovat u Typu parametru ANYVAL, SCALAR  a TABLE. U ostatních parametrů je automaticky nastaven na EXPR.

Uživatelsky definované funkce v jazyku DAX a v Power BI 8

Pochopit rozdíl mezi Režimem parametru VAL a EXPR je zásadní, protože toto nastavení může přímo ovlivňovat výsledky funkcí.

Pokud pracujeme s parametrem v Režimu VAL, znamená to, že tento parametr je vyhodnocen před samotným voláním funkce, a do těla funkce je následně vložena hodnota parametru, která se dále v těle parametru chová jako konstanta. To znamená, že pokud použijeme parametr v Režimu VAL v rámci definice funkce vícekrát, vždy budeme pracovat se stejnou hodnotou, která je k dispozici již před vyvoláním samotné funkce. Tuto hodnotu již tedy nemůžeme ovlivnit v rámci těla funkce, například změnou kontextu filtru pomocí funkce CALCULATE().

Na druhou stranu, parametr v režimu EXPR bude vždy vyhodnocen tam, kde bude použitý. Pokud tedy parametr v režimu EXPR použijeme v rámci těla funkce na více místech, vždy bude vyhodnocen znovu, a jeho výsledek může být pokaždé jiný, v závislosti na kontextu vyhodnocení, který můžeme v rámci definice funkce upravovat, například pomocí funkce CALCULATE().

Než se přesuneme k ukázce rozdílu mezi parametrem v režimu VAL a EXPR, uvažujme následující dvě měřítka.

Měřítko:

Prodeje první dva měsíce =
CALCULATE
(
    [Prodeje],
    'Date'[Měsíc] IN {"Leden", "Únor"}
)

Měřítko:

Prodeje první dva měsíce (špatně) =
VAR AktualniProdeje = [Prodeje]
VAR Vypocet =
    CALCULATE
    (
        AktualniProdeje,
        'Date'[Měsíc] IN {"Leden", "Únor"}
    )
RETURN
    Vypocet

První měřítko, měřítko [Prodeje první dva měsíce], obsahuje uvnitř funkce CALCULATE() dodatečný filtr, který před vyhodnocením měřítka [Prodeje] v prvním argumentu zafiltruje model pouze na měsíce Leden a Únor. Proto bude toto měřítko vracet prodeje pouze a první dva měsíce roku.

Uživatelsky definované funkce v jazyku DAX a v Power BI 9

Pokud ale vložíme do stejného vizuálu měřítko [Prodeje první dva měsíce (špatně)], uvidíme prodeje za všechny měsíce, bez ohledu na filtr ve funkci CALCULATE().

Uživatelsky definované funkce v jazyku DAX a v Power BI 10

Důvodem je způsob vyhodnocení proměnných v jazyku DAX, kde proměnné jsou vyhodnocené tam, kde jsou definované, a ne tam kde jsou použité. Každá proměnná je vyhodnocena pouze jednou a to na místě její definice. Následně je hodnota v proměnné použita na jednom nebo více místech následujícího DAX výpočtu. V měřítku [Prodeje první dva měsíce (špatně)] je tedy měřítko [Prodeje] vyhodnoceno mimo funkci CALCULATE(). Následně je hodnota v proměnné AktualniProdeje vložena do funkce CALCULATE(), ale není tam vyhodnocena, ale pouze vložena jako konstanta, a proto na tuto hodnotu nemá filtr ve funkci CALCULATE() žádný vliv. Proto měřítko [Prodeje první dva měsíce (špatně)] vrací stejnou hodnotu jako měřítko [Prodeje].

Pokud dokážeme rozlišit způsob vyhodnocení výpočtu v měřítku [Prodeje první dva měsíce] a v měřítku [Prodeje první dva měsíce (špatně)], neměl by být žádný problém vnímat také rozdíl mezi režimem parametru VAL a EXPR.

V režimu VAL je parametr vyhodnocen před vyhodnocením těla funkce. Tento parametr tedy bude vyhodnocen v originálním kontextu, ve kterém voláme funkci, a manipulace s filtry uvnitř těla funkce nebude mít na hodnotu parametru vliv.

Parametr v režimu EXPR pak bude vyhodnocen vždy až na místě, kde je daný parametr použitý, a to opakovaně v případě vícenásobného použití v různých částech výpočtu.

Rozdíl si opět ukážeme na příkladu, a opět můžeme použít výpočet pro první dva měsíce, tentokrát s použitím UDF.

Funkce s parametrem  v režimu VAL bude vypadat následovně.

Funkce:

VypocetProLedenUnorSpatne = (meritkoNeboAgregaceVAL : VAL) =>
CALCULATE
(
    meritkoNeboAgregaceVAL,
    'Date'[Měsíc] IN {"Leden", "Únor"}
)

Funkce s parametrem v režimu EXPR má stejný výpočet v těle funkce, rozdíl je pouze v nastavení parametru. 

Funkce:

VypocetProLedenUnor = (vypocetNeboMeritkoEXPR : EXPR) =>
CALCULATE
(
    vypocetNeboMeritkoEXPR,
    'Date'[Měsíc] IN {"Leden", "Únor"}
)

Obě funkce následně použijeme s argumentem ve formě měřítka [Prodeje] následujícím způsobem.

Měřítko:

(F) Prodeje první dva měsíce (špatně) = VypocetProLedenUnorSpatne([Prodeje])

Měřítko:

(F) Prodeje první dva měsíce = VypocetProLedenUnor([Prodeje])

Parametr ve funkci VypocetProLedenUnorSpatne() je v režimu VAL vyhodnocen před tělem funkce, a až následně je hodnota parametru (hodnota měřítka [Prodeje] vyhodnocena v originálním kontextu) vložena do funkce CALCULATE() v těle funkce, kde již na tuto hodnotu filtr ve funkci CALCULATE() nemá vliv. Na druhou stranu, při použití parametru v režimu EXPR bude parametr vyhodnocen tam, kde je v těle funkce použitý, a proto uvidíme s použitím funkce VypocetProLedenUnor() správné výsledky.

Uživatelsky definované funkce v jazyku DAX a v Power BI 11

Jak můžeme vidět na obrázku výše, rozdíl mezi parametrem v režimu VAL a EXPR může přímo ovlivňovat výsledky. To, že v tomto příkladu je použití parametru VAL špatně ale neznamená, že budeme vždy používat parametr v režimu EXPR.

Na druhou stranu, výpočtů, kdy bude dávat smysl používat parametry v režimu VAL bude méně než v případě parametrů s režimem EXPR. Režim VAL je vhodné používat pouze v případě jednoduchých výpočtů, tedy výpočtů, ve kterých nepracujeme aktivně s kontextem řádku a s kontextem filtru.

Než se posuneme v příkladech dále, tak ještě jednou důležitá vlastnost parametrů. Pokud nespecifikujeme Typ a Režim parametru, tak defaultní nastavení je ANYVAL VAL. Pokud tedy potřebujeme pracovat s parametrem v režimu EXPR, musíme zvolit buď Typ parametru, který podporuje pouze režim EXPR, nebo přímo uvést EXPR v nastavení režimu parametru.

Dobrou praxí pak je vždy explicitně uvádět požadovaný režim parametru, a nespoléhat se na defaultní nastavení. Současně je výhodné používat na konci samotných názvů parametrů příponu EXPR nebo VAL, podle typu parametru. To je důležité pro čitelnost DAX kódu v těle funkce.

Uživatelsky definované funkce v jazyku DAX a v Power BI 12

Zaměřme se na obrázku výše na výpočet v proměnné Vysledek. Zkušený DAX vývojář ví, že pokud použije v prvním argumentu funkce CALCULATE() dříve definovanou proměnnou, tak tato proměnná nebude ovlivněna filtry v samotné funkce CALCULATE(), protože je vyhodnocena jinde a dříve. Proto je velmi důležité rozlišovat mezi parametry a proměnnými v rámci DAX kódu, protože se chovají jinak. Dobrou praxí se pak jeví pojmenovávat parametry jiným stylem než proměnné. Protože proměnné jsme zvyklí pojmenovávat stylem PascalCase, to znamená každé první písmeno slova velkým, pro parametry se nabízí styl pojmenovávání camelCase.

Parametry proto pojmenováváme tak, že první písmeno je malé, a každé další slovo začíná velkým písmenem, a na konci názvu následuje EXPR nebo VAL, podle režimu parametru. Díky tomu dokážeme při pohledu na DAX kód v definici funkce ihned rozlišit, zda se jedná o parametr nebo proměnou, a případně v jakém režimu je daný parametr vyhodnocen.

Další příklady použití UDF

V této části příspěvku si ukážeme další variace výpočtů týkajících se nových zákazníků při použití UDF. V první variantě použijeme UDF jako funkci vracející tabulku. Tuto tabulku, která bude vracet nové zákazníky, nebudeme používat přímo jako výsledek měřítka, ale použijeme ji jako filtr ve funkci CALCULATE() při definici měřítka.

Funkce, která bude vracet tabulku se zákazníky, kteří jsou noví v aktuálním kontextu vyhodnocení, může vypadat například následovně.

Funkce:

NoviZakaznici = () =>
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(Sales[CustomerKey]),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(Sales[Order Date]),
            REMOVEFILTERS('Date')
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES('Date'[Date])
    )
RETURN
    NoviZakaznici

Funkce NoviZakaznici() nemá žádný parametr, a její použití je maximálně jednoduché. Stačí se na tuto funkci odkázat kdekoliv kde budeme potřebovat vrátit tabulku s novými zákazníky.

Měřítko vracející počet nových zákazníků s použitím funkce NoviZakaznici() tak může vypadat například následovně.

Měřítko:

(FT) Noví zákazníci =
CALCULATE
(
    [Počet zákazníků],
    NoviZakaznici()
)

Pokud si nové měřítko vložíme do vizuálu, výsledkem bude opět počet nových zákazníků v aktuálním období, stejně jako v případě měřítka [(F) Noví zákazníci].

Uživatelsky definované funkce v jazyku DAX a v Power BI 13

Na předcházejícím příkladu jsme si ukázali použití UDF, která vrací tabulku, a současně neobsahuje žádný parametr. Další příklad bude do jisté míry opakem, protože si vytvoříme funkci, která bude obsahovat "maximální" počet parametrů tak, aby daná funkce byla méně závislá na modelu a mohla být jednodušeji přenositelná do modelů jiných, bez nutné dodatečné úpravy v definici funkce.

Opět budeme pracovat s novými zákazníky, a vrátíme se také k variantě, která bude vracet přímo výsledek výpočtu, ne tabulku.

Funkce:

VypocetProNoveZakaznikyNezavisla =
(
    meritkoNeboAgregaceEXPR : EXPR,
    sloupecZakazniciEXPR : COLUMNREF EXPR,
    sloupecDatumNakupuEXPR : COLUMNREF EXPR,
    sloupecDatumZDatumoveTabulkyEXPR : COLUMNREF EXPR
) =>
VAR AktualniZakazniciAPrvniDen =
    ADDCOLUMNS
    (
        VALUES(sloupecZakazniciEXPR),
        "PrvniDenNakupu",
        CALCULATE
        (
            MIN(sloupecDatumNakupuEXPR),
            REMOVEFILTERS(TABLEOF(sloupecDatumZDatumoveTabulkyEXPR))
        )
    )
VAR NoviZakaznici =
    FILTER
    (
        AktualniZakazniciAPrvniDen,
        [PrvniDenNakupu] IN VALUES(sloupecDatumZDatumoveTabulkyEXPR)
    )
VAR Vysledek =
    CALCULATE
    (
        meritkoNeboAgregaceEXPR,
        KEEPFILTERS(NoviZakaznici)
    )
RETURN
    Vysledek

Pokud se podíváme na tělo funkce, tak nikde nenajdeme odkaz na jakýkoliv objekt z modelu. Funkce obsahuje místo sloupců, tabulek a měřítek odkazy na parametry. Díky tomu je funkce jednodušeji přenositelná napříč modely. Negativem pak je složitější používání funkce, protože vyžaduje zadání více argumentů pro vyplnění všech parametrů.

Měřítko:

(FN) Noví zákazníci =
VypocetProNoveZakaznikyNezavisla
(
    [Počet zákazníků],
    Sales[CustomerKey],
    Sales[Order Date],
    'Date'[Date]
)

Pokud nové měřítko vložíme do vizuálu, opět uvidíme totožné výsledky. 

Uživatelsky definované funkce v jazyku DAX a v Power BI 14

Poslední použitá funkce VypocetProNoveZakaznikyNezavisla() obsahuje v těle funkce, konkrétně v proměnné AktualniZakazniciAPrvniDen uvnitř funkce REMOVEFILTERS(), funkci TABLEOF().  Funkce TABLEOF() vrací tabulku, ze které pochází sloupec použitý jako argument této funkce. To nám umožňuje snížit počet parametrů ve funkci. Funkce TABLEOF() dává současně autorům UDF možnost větší kontroly nad provázaností jednotlivých parametrů.

Ačkoliv se může zdát, že UDF bez odkazů na objekty (sloupce, tabulky, měřítka) v modelu je na modelu nezávislá, není to tak úplně pravda. Mezi jednotlivými parametry totiž pořád existují vztahy a tyto vztahy jsou dány strukturou konkrétního modelu. Uvažujme například vztahy mezi tabulkami vyjádřené pomocí relací, vztahy mezi sloupcem a tabulkou, do které daný sloupec patří, kardinalita sloupců a tabulek nebo způsob propagace filtrů v modelu. Tyto závislosti se nebudou vyskytovat pouze u velmi jednoduchých funkcí. U většiny složitějších funkcí je ale třeba stále počítat se vztahy mezi jednotlivými parametry. U funkcí, které budeme používat napříč modely, je proto velmi důležité dobře zdokumentovat, jak se má funkce používat a jaké má omezení.

Dokumentace a nápověda uživatelsky definovaných funkcí

Při definici UDF můžeme také ovlivnit to, co bude vidět v dialogovém okně v nápovědě k funkci při jejím použití.

Pokud funkci tvoříme v rámci příkazu DEFINE FUNCTION na záložce Zobrazení dotazů DAX, tak nápovědu k funkci umístíme před samotnou definici funkce (za DEFINE a před FUNCITON, viz obrázek), a to tak že každý řádek začneme třemi zpětnými lomítky (\\\).

Uživatelsky definované funkce v jazyku DAX a v Power BI 15

Pokud pracujeme s funkcemi na záložce Zobrazení modelu, můžeme vložit popis funkce do pole Popis na kartě Vlastnosti.

Uživatelsky definované funkce v jazyku DAX a v Power BI 16

Následně se bude popis funkce zobrazovat v kontextovém okně při použití funkce, stejně jako u běžných DAX funkcí.

Uživatelsky definované funkce v jazyku DAX a v Power BI 17

Popis funkce a jejich parametrů je zásadní pro snadné používání UDF, bez nutnosti nahlížet do těla samotné funkce při každém jejím použití.

Shrnutí

Jazyk DAX je funkcionální jazyk, a je tedy zcela přirozené, že jeho součástí je také možnost vytvořit si vlastní uživatelskou funkci. Než se ale pustíme do přepisování každého výpočtu do samostatné UDF, je třeba zvážit užitečnost této práce. Nadměrné používání UDF totiž může znepřehlednit celé řešení, protože při použití funkce nevidíme tělo funkce a jsme tak odstíněni od určité části výpočtu. UDF je dobré vytvořit tehdy, kdy opakovaně používáme podobný výpočet, pouze s jinými parametry. Díky tomu můžeme mít uloženou logiku výpočtu na jednom místě, a při změně některé jeho části se tato změna automaticky propíše všude, kde je funkce použita.

Stáhnout soubor s řešením.
č. 130

Komentáře