Sdílet prostřednictvím


Principy funkcí ORDERBY, PARTITIONBYa MATCHBY

Funkce ORDERBY, PARTITIONBYa MATCHBY v DAX jsou speciální funkce, které lze používat pouze společně s funkcemi okna DAX: INDEX, OFFSET, WINDOW, RANK, ROWNUMBER.

Pochopení ORDERBY, PARTITIONBYa MATCHBY je důležité pro úspěšné použití funkcí Okna. Zde uvedené příklady používají OFFSET, ale podobně platí i pro ostatní funkce Okna.

Scénář

Začněme příkladem, který nepoužívá funkce Okna vůbec. Níže je uvedena tabulka, která vrací celkové prodeje podle barvy za kalendářní rok. Existuje několik způsobů, jak tuto tabulku definovat, ale protože nás zajímá, co se stane v DAX, použijeme počítanou tabulku. Tady je výraz tabulky:

BasicTable = 
    SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
    )

Tento výraz počítané tabulky používá SUMMARIZECOLUMNS k výpočtu SUM sloupce SalesAmount v tabulce FactInternetSales podle sloupce Color z tabulky DimProduct a sloupce CalendarYear z tabulky DimDate. Tady je výsledek:

Barva Kalendářní rok Prodeje za současný rok
"Černá" 2017 393885
"Černá" 2018 1818835
"Černá" 2019 3981638
"Černá" 2020 2644054
"Modrá" 2019 994448
"Modrá" 2020 1284648
Více 2019 48622
Více 2020 57849
"NA" 2019 207822
"NA" 2020 227295
"Červená" 2017 2961198
"Červená" 2018 3686935
"Červená" 2019 900175
"Červená" 2020 176022
"Silver" 2017 326399
"Silver" 2018 750026
"Silver" 2019 2165176
"Silver" 2020 1871788
Bílá 2019 2517
Bílá 2020 2589
"Žlutá" 2018 163071
"Žlutá" 2019 2072083
"Žlutá" 2020 2621602

Teď si představme, že se snažíme vyřešit obchodní otázku výpočtu rozdílu v prodejích v jednotlivých barvách za rok. V podstatě potřebujeme způsob, jak najít prodeje pro stejnou barvu v předchozím roce a odečíst je od prodeje v aktuálním roce. Například pro kombinaci [Red, 2019] hledáme prodej pro [Red, 2018]. Jakmile to máme, můžeme ho odečíst od aktuálního prodeje a vrátit požadovanou hodnotu.

Použití OFFSET

OFFSET je ideální pro typické porovnání s předchozími typy výpočtů potřebnými k zodpovězení výše popsané obchodní otázky, protože nám umožňuje provádět relativní pohyb. Náš první pokus může být:

1stAttempt = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "PreviousColorSales",
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation
            ),
            [CurrentYearSales]
        )
    )

Tento výraz se hodně používá. Pro rozšíření předchozí tabulky o sloupec s názvem PreviousColorSales jsme použili ADDCOLUMNS. Obsah tohoto sloupce je nastavený na CurrentYearSales, což je SUM(FactInternetSales[SalesAmount]) pro předchozí barvu (načteno pomocí OFFSET).

Výsledkem je:

Barva Kalendářní rok Prodeje za současný rok Prodej předchozích barev
"Černá" 2017 393885
"Černá" 2018 1818835 393885
"Černá" 2019 3981638 1818835
"Černá" 2020 2644054 3981638
"Modrá" 2019 994448 2644054
"Modrá" 2020 1284648 994448
Více 2019 48622 1284648
Více 2020 57849 48622
"NA" 2019 207822 57849
"NA" 2020 227295 207822
"Červená" 2017 2961198 227295
"Červená" 2018 3686935 2961198
"Červená" 2019 900175 3686935
"Červená" 2020 176022 900175
"Silver" 2017 326399 176022
"Silver" 2018 750026 326399
"Silver" 2019 2165176 750026
"Silver" 2020 1871788 2165176
Bílá 2019 2517 1871788
Bílá 2020 2589 2517
"Žlutá" 2018 163071 2589
"Žlutá" 2019 2072083 163071
"Žlutá" 2020 2621602 2072083

Toto je jeden krok blíž k našemu cíli, ale pokud se podíváme pozorně, neshoduje se přesně s tím, o co usilujeme. Například pro [Silver, 2017] PreviousColorSales je nastaven na [Red, 2020].

Přidání ORDERBY

Výše uvedená definice odpovídá:

1stAttemptWithORDERBY = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "PreviousColorSales",
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([Color], ASC, [CalendarYear], ASC, [CurrentYearSales], ASC)
            ),
            [CurrentYearSales]
        )
    )

V tomto případě volání OFFSET používá ORDERBY k seřazení tabulky podle barvy a kalendáře ve vzestupném pořadí, které určuje, co se považuje za předchozí vrácený řádek.

Důvodem, proč jsou tyto dva výsledky ekvivalentní, je to, že ORDERBY automaticky obsahuje všechny sloupce z relace, které nejsou v PARTITIONBY. Vzhledem k tomu, že PARTITIONBY nebylo zadáno, ORDERBY je nastaveno na Barva, Kalendářní rok a Prodej za aktuální rok. Vzhledem k tomu, že páry Color a CalendarYear v relaci jsou jedinečné, přidání CurrentYearSales nezmění výsledek. Ve skutečnosti, i když bychom v ORDERBYzadali pouze barvu , výsledky jsou stejné, protože CalendarYear by se automaticky přidal. Je to proto, že funkce přidá tolik sloupců, kolik je potřeba k ORDERBY, aby každý řádek mohl být jednoznačně identifikován ORDERBY a PARTITIONBY sloupci:

1stAttemptWithORDERBY = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS(
        vRelation,
        "PreviousColorSales",
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([Color])
            ),
            [CurrentYearSales]
        )
    )

Přidání PARTITIONBY

Teď téměř dosáhneme výsledku, po kterém jdeme, můžeme použít PARTITIONBY, jak je znázorněno v následujícím počítaném výrazu tabulky:

UsingPARTITIONBY = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "PreviousColorSales",
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([CalendarYear]), 
                PARTITIONBY ([Color])
            ),
            [CurrentYearSales]
        )
    )

Všimněte si, že zadání ORDERBY je zde volitelné, protože ORDERBY automaticky obsahuje všechny sloupce z relace, které nejsou zadány v PARTITIONBY. Následující výraz tedy vrátí stejné výsledky, protože ORDERBY je nastavena na CalendarYear a CurrentYearSales automaticky:

UsingPARTITIONBYWithoutORDERBY = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "PreviousColorSales",
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                PARTITIONBY ([Color])
            ),
            [CurrentYearSales]
        )
    )

Poznámka

I když je ORDERBY nastavena na CalendarYear a CurrentYearSales automaticky, neposkytuje se žádná záruka, v jakém pořadí budou přidány. Pokud se před CalendarYear přidá CurrentYearSales, výsledné pořadí není v souladu s očekáváním. Být explicitní při zadávání ORDERBY a PARTITIONBY, aby nedocházelo k nejasnostem a neočekávaným výsledkům.

Oba výrazy vrátí výsledek, který hledáme.

Barva Kalendářní rok Prodeje za současný rok ProdejZaStejnouBarvuZMinuléhoRoku
"Černá" 2017 393885
"Černá" 2018 1818835 393885
"Černá" 2019 3981638 1818835
"Černá" 2020 2644054 3981638
"Modrá" 2019 994448
"Modrá" 2020 1284648 994448
Více 2019 48622
Více 2020 57849 48622
"NA" 2019 207822
"NA" 2020 227295 207822
"Červená" 2017 2961198
"Červená" 2018 3686935 2961198
"Červená" 2019 900175 3686935
"Červená" 2020 176022 900175
"Silver" 2017 326399
"Silver" 2018 750026 326399
"Silver" 2019 2165176 750026
"Silver" 2020 1871788 2165176
Bílá 2019 2517
Bílá 2020 2589 2517
"Žlutá" 2018 163071
"Žlutá" 2019 2072083 163071
"Žlutá" 2020 2621602 2072083

Jak vidíte v této tabulce, sloupec PreviousYearSalesForSameColor zobrazuje prodeje za předchozí rok pro stejnou barvu. Pro [Red, 2020] vrátí prodeje z roku [Red, 2019] a tak dále. Pokud neexistuje žádný předchozí rok, například v případě [Červená, 2017], nebude vrácena žádná hodnota.

PARTITIONBY si můžete představit jako způsob rozdělení tabulky na části, ve kterých se má provést výpočet OFFSET. V předchozím příkladu je tabulka rozdělena na tolik částí, kolik je barev, jedna pro každou barvu. Pak se v rámci každé části vypočítá OFFSET a seřadí podle kalendářního roku.

Pohledem na situaci je vidět toto:

tabulka zobrazující OFFSET podle kalendářního roku

Nejprve volání PARTITIONBY vede k rozdělení tabulky do částí, jedna pro každou barvu. Toto je reprezentováno světle modrými poli na obrázku tabulky. Následně ORDERBY zajistí, aby každá část byla seřazena podle kalendářního roku, jak ukazují oranžové šipky. Nakonec v každé seřazené části pro každý řádek OFFSET najde řádek nad ním a vrátí danou hodnotu ve sloupci PreviousYearSalesForSameColor. Vzhledem k tomu, že pro každý první řádek v každé části není ve stejné části žádný předchozí řádek, výsledek tohoto řádku pro sloupec PreviousYearSalesForSameColor je prázdný.

Abychom dosáhli konečného výsledku, jednoduše musíme odečíst CurrentYearSales z prodeje za předchozí rok pro stejnou barvu vrácenou voláním na OFFSET. Vzhledem k tomu, že nás nezajímá zobrazení prodejů za předchozí rok pro stejnou barvu, ale pouze prodeje za aktuální rok a meziroční rozdíl. Tady je konečný počítaný výraz tabulky:

FinalResult = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "YoYSalesForSameColor",
        [CurrentYearSales] -
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([CalendarYear]),
                PARTITIONBY ([Color])
            ),
            [CurrentYearSales]
        )
    )

A tady je výsledek tohoto výrazu:

Barva Kalendářní rok Prodeje za současný rok YoYSalesForSameColor
"Černá" 2017 393885 393885
"Černá" 2018 1818835 1424950
"Černá" 2019 3981638 2162803
"Černá" 2020 2644054 -1337584
"Modrá" 2019 994448 994448
"Modrá" 2020 1284648 290200
Více 2019 48622 48622
Více 2020 57849 9227
"NA" 2019 207822 207822
"NA" 2020 227295 19473
"Červená" 2017 2961198 2961198
"Červená" 2018 3686935 725737
"Červená" 2019 900175 -2786760
"Červená" 2020 176022 -724153
"Silver" 2017 326399 326399
"Silver" 2018 750026 423627
"Silver" 2019 2165176 1415150
"Silver" 2020 1871788 -293388
Bílá 2019 2517 2517
Bílá 2020 2589 72
"Žlutá" 2018 163071 163071
"Žlutá" 2019 2072083 1909012
"Žlutá" 2020 2621602 549519

Použití MATCHBY

Možná jste si všimli, že jsme vůbec neurčili MATCHBY. V tomto případě není nutné. Sloupce v ORDERBY a PARTITIONBY (pokud byly uvedeny v příkladech výše) jsou dostatečné k jednoznačné identifikaci jednotlivých řádků. Vzhledem k tomu, že jsme nezadali MATCHBY, sloupce zadané v ORDERBY a PARTITIONBY slouží k jedinečné identifikaci jednotlivých řádků, aby bylo možné je porovnat a umožnit tak OFFSET smysluplný výsledek. Pokud sloupce v ORDERBY a PARTITIONBY nemohou jednoznačně identifikovat každý řádek, je možné do klauzule ORDERBY přidat další sloupce, pokud tyto nadbytečné sloupce umožňují jedinečně identifikovat každý řádek. Pokud to není možné, vrátí se chyba. V tomto posledním případě může zadání MATCHBY pomoct s řešením chyby.

Pokud je zadán MATCHBY, sloupce v MATCHBY a PARTITIONBY slouží k jedinečné identifikaci jednotlivých řádků. Pokud to není možné, vrátí se chyba. I když MATCHBY není potřeba, zvažte explicitní zadání MATCHBY, aby nedocházelo k nejasnostem.

Pokračování z příkladů uvedených výše, zde je poslední výraz:

FinalResult = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "YoYSalesForSameColor",
        [CurrentYearSales] -
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([CalendarYear]),
                PARTITIONBY ([Color])
            ),
            [CurrentYearSales]
        )
    )

Pokud chceme explicitně určit, jak mají být řádky jednoznačně identifikovány, můžeme zadat MATCHBY, jak je znázorněno v následujícím ekvivalentním výrazu:

FinalResultWithExplicitMATCHBYOnColorAndCalendarYear = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "YoYSalesForSameColor",
        [CurrentYearSales] -
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([CalendarYear]),
                PARTITIONBY ([Color]),
                MATCHBY ([Color], [CalendarYear])
            ),
            [CurrentYearSales]
        )
    )

Vzhledem k tomu, že je zadán MATCHBY, oba sloupce zadané v MATCHBY i v PARTITIONBY slouží k jedinečné identifikaci řádků. Vzhledem k tomu, že v MATCHBY i PARTITIONBYje zadaná funkce Color, je následující výraz ekvivalentní předchozímu výrazu:

FinalResultWithExplicitMATCHBYOnCalendarYear = 
    VAR vRelation = SUMMARIZECOLUMNS ( 
        DimProduct[Color], 
        DimDate[CalendarYear], 
        "CurrentYearSales", ROUND ( SUM ( FactInternetSales[SalesAmount] ), 0 )
        )
    RETURN
    ADDCOLUMNS (
        vRelation,
        "YoYSalesForSameColor",
        [CurrentYearSales] -
        SELECTCOLUMNS (
            OFFSET (
                -1,
                vRelation,
                ORDERBY ([CalendarYear]),
                PARTITIONBY ([Color]),
                MATCHBY ([CalendarYear])
            ),
            [CurrentYearSales]
        )
    )

Vzhledem k tomu, že určení MATCHBY není nutné v příkladech, na které jsme se zatím podívali, podívejme se na mírně jiný příklad, který vyžaduje MATCHBY. V tomto případě máme seznam řádků objednávek. Každý řádek představuje položku objednávky. Objednávka může mít více řádků objednávky a řádek objednávky 1 se zobrazí v mnoha objednávkách. Kromě toho pro každý řádek objednávky máme ProductKey a SalesAmount. Ukázka relevantních sloupců v tabulce vypadá takto:

ČísloObjednávkyProdeje ČísloŘádkuObjednávky Produktový klíč Částka prodeje
SO51900 1 528 4,99
SO51948 1 528 5.99
SO52043 1 528 4,99
SO52045 1 528 4,99
SO52094 1 528 4,99
SO52175 1 528 4,99
SO52190 1 528 4,99
SO52232 1 528 4,99
SO52234 1 528 4,99
SO52234 2 529 3.99

Všimněte si, že SalesOrderNumber i SalesOrderLineNumber jsou potřeba k jednoznačné identifikaci řádků.

Pro každou objednávku chceme vrátit předchozí objem prodeje stejného produktu (reprezentovaný kódem ProductKey) seřazený hodnotou SalesAmount v sestupném pořadí. Následující výraz nebude fungovat, protože ve vRelation jsou potenciálně více řádků, což se předává do OFFSET:

ThisExpressionFailsBecauseMATCHBYIsMissing = 
    ADDCOLUMNS (
        FactInternetSales,
        "Previous Sales Amount",
            SELECTCOLUMNS (
                OFFSET (
                    -1,
                    FactInternetSales,
                    ORDERBY ( FactInternetSales[SalesAmount], DESC ),
                    PARTITIONBY ( FactInternetSales[ProductKey] )
                ),
                FactInternetSales[SalesAmount]
            )
    )

Tento výraz vrátí chybu: "OFFSETrelační parametr může mít duplicitní řádky, které nejsou povoleny."

Aby tento výraz fungoval, musí být zadán MATCHBY a musí obsahovat všechny sloupce, které jednoznačně definují řádek. MATCHBY se tady vyžaduje, protože relace FactInternetSales neobsahuje žádné explicitní klíče ani jedinečné sloupce. Sloupce SalesOrderNumber a SalesOrderLineNumber však společně tvoří složený klíč, kde jejich existence je jedinečná ve vztahu a může tedy jednoznačně identifikovat každý řádek. Není dostatečné uvést pouze SalesOrderNumber nebo SalesOrderLineNumber, protože oba sloupce obsahují opakující se hodnoty. Tento problém řeší následující výraz:

ThisExpressionWorksBecauseOfMATCHBY = 
    ADDCOLUMNS (
        FactInternetSales,
        "Previous Sales Amount",
            SELECTCOLUMNS (
                OFFSET (
                    -1,
                    FactInternetSales,
                    ORDERBY ( FactInternetSales[SalesAmount], DESC ),
                    PARTITIONBY ( FactInternetSales[ProductKey] ),
                    MATCHBY ( FactInternetSales[SalesOrderNumber], 
                                FactInternetSales[SalesOrderLineNumber] )
                ),
                FactInternetSales[SalesAmount]
            )
    )

A tento výraz skutečně vrací výsledky, které hledáme.

ČísloObjednávkyProdeje ČísloŘádkuObjednávky Produktový klíč Částka prodeje Předchozí částka prodeje
SO51900 1 528 5.99
SO51948 1 528 4,99 5.99
SO52043 1 528 4,99 4,99
SO52045 1 528 4,99 4,99
SO52094 1 528 4,99 4,99
SO52175 1 528 4,99 4,99
SO52190 1 528 4,99 4,99
SO52232 1 528 4,99 4,99
SO52234 1 528 4,99 4,99
SO52234 2 529 3.99

ORDERBY PARTITIONBY MATCHBY INDEX OFFSET WINDOW RANK ROWNUMBER