了解 ORDERBY、PARTITIONBY和 MATCHBY 函数

ORDERBYPARTITIONBYMATCHBY 函数在 DAX 中是特殊函数,只能与 DAX 窗口函数一起使用,包括:INDEXOFFSETWINDOWRANKROWNUMBER

了解 ORDERBY, PARTITIONBY并且 MATCHBY 对于成功使用 Window 函数至关重要。 此处提供的示例使用 OFFSET,但同样适用于其他 Window 函数。

情景

让我们从一个根本不使用 Window 函数的示例开始。 如下所示的表返回每个日历年的总销售额(每色)。 有多种方法可以定义此表,但由于我们想了解 DAX 中发生的情况,我们将使用计算表格。 下面是表格表达式:

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

你将看到,此计算表表达式使用 SUMMARIZECOLUMNS 根据 DimProduct 表中的 Color 列和 DimDate 表中的 CalendarYear 列计算 FactInternetSales 表中 SalesAmount 列的 SUM。 下面是结果:

颜色 CalendarYear 本年度销售额
“黑色” 2017 393885
“黑色” 2018 1818835
“黑色” 2019 3981638
“黑色” 2020 2644054
“蓝色” 2019 994448
“蓝色” 2020 1284648
“彩色” 2019 48622
“彩色” 2020 57849
“NA” 2019 207822
“NA” 2020 227295
“红色” 2017 2961198
“红色” 2018 3686935
“红色” 2019 900175
“红色” 2020 176022
“银色” 2017 326399
“银色” 2018 750026
“银色” 2019 2165176
“银色” 2020 1871788
“白色” 2019 2517
“白色” 2020 2589
“黄色” 2018 163071
“黄色” 2019 2072083
“黄色” 2020 2621602

现在,让我们想象一下,我们正在努力解决计算每种颜色销售额差异、逐年变化的业务问题。 实际上,我们需要一种方法来查找上一年相同颜色的销售额,并在上下文中减去当年销售额。 例如,对于组合 [Red, 2019] ,我们要查找 [Red, 2018] 的销售额。 有了它后,我们可以从当前销售额中减去它,并返回所需的值。

使用 OFFSET

OFFSET 非常适合与回答上述业务问题所需的先前计算进行典型比较,因为它允许我们执行相对移动。 我们的第一次尝试可能是:

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

此表达式正在发生很多事情。 我们用ADDCOLUMNS扩展了之前的表格,添加了一个名为PreviousColorSales的列。 对于使用 OFFSET 检索到的上一个 Color,该列的内容设置为 CurrentYearSales,即 SUM (FactInternetSales[SalesAmount])。

结果为:

颜色 CalendarYear 本年销售额 PreviousColorSales
“黑色” 2017 393885
“黑色” 2018 1818835 393885
“黑色” 2019 3981638 1818835
“黑色” 2020 2644054 3981638
“蓝色” 2019 994448 2644054
“蓝色” 2020 1284648 994448
“彩色” 2019 48622 1284648
“彩色” 2020 57849 48622
“NA” 2019 207822 57849
“NA” 2020 227295 207822
“红色” 2017 2961198 227295
“红色” 2018 3686935 2961198
“红色” 2019 900175 3686935
“红色” 2020 176022 900175
“银色” 2017 326399 176022
“银色” 2018 750026 326399
“银色” 2019 2165176 750026
“银色” 2020 1871788 2165176
“白色” 2019 2517 1871788
“白色” 2020 2589 2517
“黄色” 2018 163071 2589
“黄色” 2019 2072083 163071
“黄色” 2020 2621602 2072083

这离我们的目标更近一步,但如果我们仔细观察,它与我们追求的完全不匹配。 例如,对于 [Silver, 2017] ,PreviousColorSales 设置为 [Red, 2020]。

添加 ORDERBY

上述定义等效于:

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]
        )
    )

在本例中,对 OFFSET 的调用使用 ORDERBY 按 Color 和 CalendarYear 升序对表进行排序,从而确定被视为返回的上一行。

之所以这两个结果是等效的,是因为ORDERBY自动包含了关系中所有不在PARTITIONBY中的列。 由于 PARTITIONBY 未指定, ORDERBY 因此设置为 Color、CalendarYear 和 CurrentYearSales。 但是,由于关系中的 Color 和 CalendarYear 对是唯一的,因此添加 CurrentYearSales 不会更改结果。 事实上,即使我们只指定 Color in ORDERBY,结果也是相同的,因为 CalendarYear 将自动添加。 这是因为该函数将根据需要向 ORDERBY 添加任意数量的列,以确保每一行都可以由 ORDERBY 和 PARTITIONBY 列唯一标识。

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]
        )
    )

添加 PARTITIONBY

现在,为了几乎获得我们想要的结果,可以使用 PARTITIONBY,如以下计算表表达式所示:

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]
        )
    )

请注意,此处指定 ORDERBY 是可选的,因为 ORDERBY 自动包含关系中 PARTITIONBY未指定的所有列。 因此,以下表达式返回相同的结果,因为 ORDERBY 将自动设置为 CalendarYear 和 CurrentYearSales:

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]
        )
    )

注释

虽然 ORDERBY 自动设置为 CalendarYear 和 CurrentYearSales,但无法保证其添加顺序。 如果在 CalendarYear 之前添加了 CurrentYearSales,则生成的顺序与预期顺序不内联。 在指定ORDERBY和PARTITIONBY时明确,以避免混淆和意外结果

这两个表达式都返回了我们想要的结果。

颜色 CalendarYear 本年度销售额 PreviousYearSalesForSameColor
“黑色” 2017 393885
“黑色” 2018 1818835 393885
“黑色” 2019 3981638 1818835
“黑色” 2020 2644054 3981638
“蓝色” 2019 994448
“蓝色” 2020 1284648 994448
“彩色” 2019 48622
“彩色” 2020 57849 48622
“NA” 2019 207822
“NA” 2020 227295 207822
“红色” 2017 2961198
“红色” 2018 3686935 2961198
“红色” 2019 900175 3686935
“红色” 2020 176022 900175
“银” 2017 326399
“银色” 2018 750026 326399
“银色” 2019 2165176 750026
“银色” 2020 1871788 2165176
“白色” 2019 2517
“白色” 2020 2589 2517
“黄色” 2018 163071
“黄色” 2019 2072083 163071
“黄色” 2020 2621602 2072083

如此表所示,PreviousYearSalesForSameColor 列显示上一年相同颜色的销售额。 对于 [Red,2020],它返回的是 [Red,2019] 的销售数据,依此类推。 如果没有前一年,例如[Red, 2017],则不返回任何值。

你可以将 PARTITIONBY 视为一种方法,用于将表划分为执行 OFFSET 计算的各个部分。 在上面的示例中,表格分为任意多个部分,每个颜色各有一种。 然后,在每个部分内计算 OFFSET,按 CalendarYear 排序。

从视觉上看,发生的情况是:

按日历年显示的 OFFSET 表

首先,对PARTITIONBY的调用导致表格被分为多个部分,每种颜色各自占有一部分。 这由表图像中的浅蓝色框表示。 接下来, ORDERBY 确保每个部分都按 CalendarYear 排序(由橙色箭头表示)。 最后,在每个已排序部分中,对于每一行,OFFSET 查找其上方的行,并在 PreviousYearSalesForSameColor 列中返回该值。 由于对于每个部分的第一行,同一部分中没有上一行,因此 PreviousYearSalesForSameColor 列的该行的结果为空。

要获得最终结果,只需从调用 OFFSET 返回的相同颜色的上一年销售额中减去 CurrentYearSales。 由于我们对显示同一颜色的上一年销售额不感兴趣,而只关注当前年度的销售额和同比差异。 下面是最终计算表表达式:

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]
        )
    )

下面是该表达式的结果:

颜色 CalendarYear 本年度销售额 YoYSalesForSameColor
“黑色” 2017 393885 393885
“黑色” 2018 1818835 1424950
“黑色” 2019 3981638 2162803
“黑色” 2020 2644054 -1337584
“蓝色” 2019 994448 994448
“蓝色” 2020 1284648 290200
“彩色” 2019 48622 48622
“彩色” 2020 57849 9227
“NA” 2019 207822 207822
“NA” 2020 227295 19473
“红色” 2017 2961198 2961198
“红色” 2018 3686935 725737
“红色” 2019 900175 -2786760
“红色” 2020 176022 -724153
“银色” 2017 326399 326399
“银色” 2018 750026 423627
“银色” 2019 2165176 1415150
“银色” 2020 1871788 -293388
“白色” 2019 2517 2517
“白色” 2020 2589 72
“黄色” 2018 163071 163071
“黄色” 2019 2072083 1909012
“黄色” 2020 2621602 549519

使用 MATCHBY

你可能已经注意到我们根本没有指定 MATCHBY 。 在这种情况下,不需要这样做。 ORDERBY 和 PARTITIONBY 中的列(就上面示例中指定的而言)足以唯一识别每一行。 由于我们没有指定MATCHBY,所以列ORDERBY和PARTITIONBY被用于唯一标识每一行,从而可以进行比较,以便OFFSET能够提供有意义的结果。 如果列中ORDERBYPARTITIONBY的列无法唯一标识每行,则如果这些额外列允许唯一标识每行,则可以将其他列添加到ORDERBY子句中。 如果不可能,则返回错误。 在此最后一种情况下,指定 MATCHBY 可能有助于解决错误。

如果指定了MATCHBY,那么MATCHBY和PARTITIONBY中的列将用于唯一标识每一行。 如果不可能,则返回错误。 即使 MATCHBY 不需要,也应考虑显式指定 MATCHBY 以避免任何混淆。

继续上述示例,下面是最后一个表达式:

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]
        )
    )

如果想要显式说明应如何唯一标识行,可以按以下等效表达式所示指定 MATCHBY :

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]
        )
    )

由于MATCHBY已指定,因此在MATCHBY和PARTITIONBY中指定的列都用于唯一标识行。 由于在 MATCHBY 和 PARTITIONBY 中都指定了 Color,因此以下表达式等效于上一个表达式:

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]
        )
    )

由于到目前为止,在我们所看的示例中不需要指定MATCHBY,因此我们来看看一个确实需要MATCHBY的略有不同的示例。 在本例中,我们有一个订单行列表。 每一行都表示订单的订单行。 订单可以有多个订单行,订单行 1 显示在多个订单上。 此外,对于每个订单行,我们都有 ProductKey 和 SalesAmount。 表中相关列的示例如下所示:

销售订单号 SalesOrderLineNumber 产品密钥 销售额
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

请注意,需有 SalesOrderNumber 和 SalesOrderLineNumber 才能唯一识别行。

对于每个订单,我们希望返回同一产品(由 ProductKey 表示)之前的销售金额,并按销售金额降序排列。 以下表达式不起作用,因为 vRelation 中可能存在多个行,因为它被 OFFSET传入:

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

此表达式返回错误:“OFFSET''s Relation 参数可能有重复行,这是不允许的。

若要使此表达式正常工作, MATCHBY 必须指定并必须包含唯一定义行的所有列。 MATCHBY 此处是必需的,因为关系 FactInternetSales 不包含任何显式键或唯一列。 但是,列 SalesOrderNumber 和 SalesOrderLineNumber 共同构成 一个组合键,其中它们一起存在于关系中是唯一的,因此可以唯一标识每一行。 仅指定 SalesOrderNumber 或 SalesOrderLineNumber 是不够的,因为两列都包含重复值。 以下表达式可解决问题:

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

此表达式确实返回了我们想要的结果:

销售订单号 SalesOrderLineNumber 产品密钥 销售金额 以前的销售金额
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