在 DAX 中引入 RANK 窗口函数

本文翻译自Marco Russo & Alberto Ferrari的文章—《Introducing the RANK window function in DAX》来源:SQLBI RANK是一种基于多列对项进行排名的新的DAX函数。本文介绍了RANK函数及其与RANKX函数的区别。

DAX目前已经提供了两个排名函数:RANK.EQ和RANKX。虽然RANK.EQ很少使用,但RANKX长期以来一直是主要的排名函数。

新的RANK函数使在多列上进行排名变得更加容易,因为它作为窗口函数的一员,支持多列排序作为一项原生功能。由于窗口函数中当前行的概念,RANK还有助于解决RANKX中的一个微妙问题——虽然它很少发生,但可能会影响对十进制数字进行排名。

作为一个示例,我们使用一个度量值,将销售金额四舍五入到最接近的400K的倍数,以引入排名中的并列项。

Rounded Sales = MROUND( [Sales Amount], 400000 )

让我们从对RANKX的简要回顾开始。使用RANKX需要复杂的语法和细节注意。下面的公式虽然正确,但有一些缺点,对于新手来说解决起来并不直观。的确,如果我们想要基于Product[Brand]列获得排名,使用RANKX,我们可以编写以下代码:

RANKX =

RANKX (

ALLSELECTED ( 'Product'[Brand] ),

[Rounded Sales], , DESC, DENSE

)

当在矩阵中使用时,总计级别(total level)显示的值并没有太多意义。

不仅总计级别显示的价值不太明显。而且,如果有任何小计,它们会显示相同的结果。

我们可以通过使用HASONEVALUE或ISINSCOPE来解决这个问题。然而,问题在于,由于RANKX的行为有些反直觉,你需要注意这些小细节。此外,使用RANKX对并列项进行相同排名,就像前面屏幕截图中的Contoso和Litware一样。有时,这是期望的结果。然而,我们经常希望排名能够区分并列项,例如使用品牌的字母顺序作为排序的第二个规则。我们期望的结果如下:

以这种方式获得结果是一个有趣的DAX练习,但太过复杂和耗费时间。RANK通过一种简单的方式解决了这个问题。实际上,使用RANK,你可以使用窗口函数的语法提供多个order-by列(排序字段)。RANK的公式如下:

RANK =

RANK (

DENSE,

ALLSELECTED ( 'Product'[Brand] ),

ORDERBY ( [Rounded Sales], DESC, 'Product'[Brand], ASC )

)

因为RANK将当前行与提供的源表进行排序,所以当没有当前行时,如在总计级别(total level),RANK会显示空白而不是不正确的排名。

正如我们在 SQLBI+ 白皮书(
https://www.sqlbi.com/whitepapers/windows-functions-in-dax/)中提到的窗口函数,我们认为预计算窗口函数的源表是最佳实践。这提高了代码的可读性,有时也可以产生更好的查询计划。优化前面查询公式的更好写法如下

VAR SourceTable =

ADDCOLUMNS ( ALLSELECTED ( Product[Brand] ), "@Amt", [Rounded Sales] )

VAR Result =

RANK (

DENSE,

SourceTable,

ORDERBY ( [Rounded Sales], DESC, Product[Brand], ASC )

)

RETURN

Result

将代码变得更易读的主要原因是为了清楚地了解源表的内容以及如何计算排名列。通过查看源表的内容,很明显 @Amt 列是在当前筛选器上下文中计算的,并且仅覆盖了 Product[Brand] 列。因此,我们知道排名是基于当前筛选器上下文的局部排序。实际上,通过扩展矩阵以显示多个类别,你可以看到排名总是为每个类别重新开始。

在评估用于排序的金额时,标记出替换了筛选器上下文的哪些部分是非常重要的,以避免常见的陷阱。例如,如果我们要将月份相互排名,下面的选择就是错误的写法:

RANK Month (Wrong) =

VAR SourceTable =

ADDCOLUMNS (

ALLSELECTED ( 'Date'[Month] ),

"@Amt", [Sales Amount]

)

VAR Result =

RANK (

DENSE,

SourceTable,

ORDERBY ( [@Amt] )

)

RETURN

Result

当在矩阵中使用时,该选择的结果是错误的。

原因在于源表中的列没有替换筛选器上下文的正确部分。月份是按月份号排序的,因此月份号作为查询的一部分被使用并且结果被筛选。源表覆盖了月份名称,但没有覆盖月份号。下面是一个正确的公式:

RANK Month =

VAR SourceTable =

ADDCOLUMNS (

ALLSELECTED (

'Date'[Month],

'Date'[Month Number]

),

"@Amt", [Sales Amount]

)

VAR Result =

RANK (

DENSE,

SourceTable,

ORDERBY ( [@Amt] )

)

RETURN

Result

同时覆盖月份和月份号的筛选器上下文可以产生正确的结果。

RANK并不能代替RANKX。开发人员可以使用 RANKX 的第三个参数来根据配置表对值进行排序,但这些场景相对较少,RANK通常是更好的选择。

结论

RANK是一个简化排名单个或多个列的函数。当排名不直接显示在报表中时,在行为上它表现得更加一致,并且它能减少DAX代码中可能出现的错误。尽管不是完全替代RANKX的函数,但在大多数场景下它都表现得完美,这使得它成为当今DAX中最好的排名函数。



如果您想深入学习微软Power BI,欢迎登录网易云课堂试听学习我们的“从Excel到Power BI数据分析可视化”系列课程。或者关注我们的公众号(PowerPivot工坊)后猛戳”在线学习”。



长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~


Power Pivot工坊