了解 ORDERBY、PARTITIONBY 和 MATCHBY 函数

DAX 中的ORDERBYPARTITIONBYMATCHBY函数是只能与 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。 下面是结果:

Color CalendarYear CurrentYearSales
“黑色” 2017 393885
“黑色” 2018 1818835
“黑色” 2019 3981638
“黑色” 2020 2644054
“Blue” 2019 994448
“Blue” 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])。

结果为:

Color CalendarYear CurrentYearSales PreviousColorSales
“黑色” 2017 393885
“黑色” 2018 1818835 393885
“黑色” 2019 3981638 1818835
“黑色” 2020 2644054 3981638
“Blue” 2019 994448 2644054
“Blue” 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 不会更改结果。 事实上,即使我们只在 ORDERBY 中指定 Color,结果也是相同的,因为 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 时要显式,从而避免混淆和意外结果

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

Color CalendarYear CurrentYearSales PreviousYearSalesForSameColor
“黑色” 2017 393885
“黑色” 2018 1818835 393885
“黑色” 2019 3981638 1818835
“黑色” 2020 2644054 3981638
“Blue” 2019 994448
“Blue” 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 排序。

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

Table showing OFFSET by Calendar Year

首先,调用 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]
        )
    )

下面是该表达式的结果:

Color CalendarYear CurrentYearSales YoYSalesForSameColor
“黑色” 2017 393885 393885
“黑色” 2018 1818835 1424950
“黑色” 2019 3981638 2162803
“黑色” 2020 2644054 -1337584
“Blue” 2019 994448 994448
“Blue” 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 能够给出有意义的结果。 如果 ORDERBY 和 PARTITIONBY 中的列无法唯一识别每一行,可以将这些额外的列添加到 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。 表中相关列的示例如下所示:

SalesOrderNumber SalesOrderLineNumber ProductKey SalesAmount
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 才能唯一识别行。

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

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

此表达式返回错误:“OFFSET 的 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]
            )
    )

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

SalesOrderNumber SalesOrderLineNumber ProductKey SalesAmount 先前的销售额
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