DAX 第八篇:ALLSELECTED函数和影子筛选上下文
ALLSELECTED 函数有两种用法:作为表函数,或者作为CALCULATE函数的筛选调节器,这两种功能都是通过使用迭代器在过滤器上下文堆栈上留下的最后一个影子过滤器上下文来实现的。ALLSELECTED 函数是DAX中唯一一个使用影子筛选上下文的函数。
使用影子筛选上下来定义ALLSELECTED 函数的功能:
- 当ALLSELECTED 函数作为表值函数时,ALLSELECTED 忽略任何应用到参数(列或表)上的过滤器,并返回最后一个影子筛选上下文中的可见值
- 当ALLSELECTED 函数作为CALCULATE函数的筛选调节器时,ALLSELECTED 函数移除参数(列或表)上的过滤器,并返回最后一个影子筛选上下文中的可见值。
- 当ALLSELECTED 函数作为CALCULATE函数的筛选调节器,并且ALLSELECTED 函数没有参数时,ALLSELECTED 函数移除所有的过滤器,并返回最后一个影子筛选上下文中的可见值。
如果传递给ALLSELECTED函数的参数不存在于影子筛选上下文中,也就是说,参数列不存在于影子筛选上下文中,或参数表中的任何列都不存在于影子筛选上下文中,那么ALLSELECTED函数不会用影子筛选上下文进行过滤。
在影子筛选上下文中,ALLSELECTED函数执行的操作都是首先恢复最后一个影子筛选上下文,如果没有影子筛选上下文,那么ALLSELECTED相当于ALL函数;如果存在影子筛选上下文,那么根据ALLSELECTED函数参数的不同,可以分为三种情况:
- ALLSELECTED(Table[Column]) ,列作为参数,影子筛选上下文过滤该列,如果该列不存在于影子筛选上下文中,那么该列忽略任何过滤器;
- ALLSELECTED(Table) ,表作为参数,影子筛选上下文过滤表中的任何一列,如果所有列都不存在于影子筛选上下文中,那么该表忽略任何过滤器;
- ALLSELECTED(),函数没有参数,那么恢复位于最后一个影子筛选上下文中的所有列上的过滤器,无参数的ALLSELECTED函数只能作为CALCULATE函数的筛选调节器。
一,初次认识ALLSELECTED 函数
从官方文档Microsoft Docs上得知,ALLSELECTED 函数的作用是:从当前的查询中,删除列和行中的上下文过滤器,同时保留所有其他上下文过滤器或显式过滤器,此功能可用于获取基于Visual的总计:
ALLSELECTED([<tableName> | <columnName>[, <columnName>[, <columnName>[,…]]]] )
该函数的返回值是移除了行和列的筛选上下文。对于参数而言,如果有多个<columnName>参数,那么这些列都必须源自同一个表。
ALLSELECTED 函数看起来非常简单,实际上隐藏了一个陷阱,官方文档没有讲清除ALLSELECTED 函数是如何删除列和行的上下文过滤,本质上是ALLSELECTED函数恢复最后一个影子筛选上下文。
1,ALLSELECTED 函数用作表函数
ALLSELECTED 函数用作表函数,参数只有两种方式:列参数,表参数
AllSelectedCustomerSales :=
SUMX (
ALLSELECTED ( Customer ),
[SalesAmount]
)
2,ALLSELECTED 函数用作CALCULATE函数的筛选调节器
ALLSELECTED 函数用作CALCULATE函数的筛选调节器,不会返回任何数据行。有三种使用方式:列参数,表参数和无参数
AllSelectedColumn := CALCULATE ( [SalesAmount], ALLSELECTED ( Customer[Occupation] ) ) AllSelectedTable := CALCULATE ( [SalesAmount], ALLSELECTED ( Customer ) ) AllSelectedAll := CALCULATE ( [SalesAmount], ALLSELECTED () )
二,影子筛选上下文
影子筛选上下文存在于迭代函数中,如果没有迭代函数,那么就没有影子筛选上下文,ALLSELECTED函数也就不会执行任何操作。也就是说,迭代函数创建影子筛选上下文,影子筛选上下文的作用域是在迭代函数中。当迭代函数创建影子筛选上下文时,它保持初始的休眠状态,并且只能被ALLSELECTED函数激活。影子筛选上下文类似于筛选上下文,都能通过过滤数据而影响计算的结果,但是休眠状态的影子筛选上下文不会以任何方式影响计算结果,只有被ALLSELECTED函数激活后,才会影响计算结果。
如果在ALLSELECTED函数执行前存在多个迭代函数,那么ALLSELECTED函数会恢复最后一个影子筛选上下。
如果传递给ALLSELECTED函数的参数列没有被影子筛选上下文筛选,那么ALLSELECTED函数就不会执行任何操作。
为了区分影子筛选上下文,把普通的筛选上下文称为显式筛选上下文。
举个例子1,下面DAX返回一个表值函数,Pct是DAX开发工程师编写的代码,变量 FilteredBrands 和 CALCULATETABLE等价于Power BI 的DAX引擎返回的查询语句,这个查询语句的执行过程主要分为4步:
Pct = DIVIDE([Sales Amount], CALCULATE([Sales Amount], ALLSELECTED(Product[Brand]) ) ) var FilteredBrands = FILTER(ALL(Product[Brand]), Product[Brand] in {'B1','B2','B3','B4','B5','B6'}) return CALCULATETABLE( ADDCOLUMNS( VALUES(Product[Brand]) ,"Sales Amount", [Sales Amount] ,"Pct",[Pct] ) , FilteredBrands )
Step1:外层CALCULATE函数创建一个包含6个Brand的筛选上下文,开始执行ADDCOLUMS函数。
Step2:ADDCOLUMS函数是一个迭代函数,在迭代开始之前,迭代函数首先计算VALUES函数的返回值,并根据VALUES函数的返回值创建影子筛选上下文。
Step3:ADDCOLUMS函数开始迭代,对于每一次迭代,迭代函数都把当前的行上下文转换为等价的筛选上下文(即显式筛选上下文),对于本例,筛选上下文是FilteredBrands表中的一个值。
Step4:当执行Pct度量时,Pct度量值调用ALLSELECTED函数,ALLSELECTED函数执行的操作是:
- 如果ALLSELECTED函数有参数,那么恢复作为参数的列或表上的最后一个影子筛选上下文。
- 如果ALLSELECTED函数没有参数,那么恢复所有列上的最后一个影子筛选上下文。
因为迭代函数创建的最后一个影子筛选上下文是Product[Brand] in {'B1','B2','B3','B4','B5','B6'},所以,ALLSELECTED函数恢复这个筛选上下文,这使得Pct度量中的分母计算6个Brand的[Sales Amount],而分子没有ALLSELECTED函数,其筛选上下文是由当前行转换的筛选上下文,只能计算单个Brand的[Sales Amount]。
通过这个例子可以看出,利用影子筛选上下文,ALLSELECTED函数可以移除当前Visual上的筛选上下文,而保留(或恢复到)当前Visual之外的筛选上下文。
三,单列参数:ALLSELECTED(Table[Column] )
我们要学习的第一课:当列作为ALLSELECTED的参数时,ALLSELECTED (Table[Column] )忽略(或移除)该列上的显式过滤器上下文,恢复该列上的最后一个影子过滤器上下文。 ALLSELECTED 与 VALUES或 DISTINCT非常不同。 VALUES 和 DISTINCT 始终考虑过滤器上下文,而 ALLSELECTED 不考虑。 ALLSELECTED 作用于列并检查该列是否被影子过滤器上下文过滤,忽略任何交叉过滤器。
结合下面的Product的数据表来分析结果:
1,没有影子筛选上下文,并且ALLSELECTED(Table[Column] )用作表值函数
CONCATENATEX 是迭代器函数,ALLSELECTED(Table[Column] )用作表值函数,ALLSELECTED ( Table[Column] ) 首先搜索最后一个影子筛选上下文,如果该列存在于影子筛选上下文中,那么用影子筛选上下文对该列进行过滤;如果该列不存在于影子筛选上下文中,那么不对该列进行任何过滤,并返回该列的值,这些列值是在最后一个影子过滤上下文中被实际过滤之后,仍然可见的值。
由于ALLSELECTED(Table[Column] )没有检索到影子筛选上下文,因此不会对Table[Column]进行过滤,外部的显式筛选上下文被ALLSELECTED(Table[Column] )忽略。也就是说,ALLSELECTED(Table[Column] ) 作用于列并检查该列是否被影子过滤器上下文过滤,该列只会被影子筛选上下文过滤,并忽略任何显式筛选上下文。
ALLSELECTED 不考虑过滤器上下文,它的主要目的是检索先前设置的影子过滤器上下文。 ALLSELECTED 与 VALUES或 DISTINCT非常不同。 VALUES 和 DISTINCT 始终考虑过滤器上下文,而 ALLSELECTED 不考虑。
EVALUATE CALCULATETABLE ( ROW ( "Products", CONCATENATEX ( ALLSELECTED( 'Product'[Product] ), Product[Product], ", " ) ), Product[Color] IN { "Blue", "Green" }, Product[Brand] IN { "Contoso", "Fabrikam" } )
结果是返回所有的产品:Bike, Helmet, Shoes, Robot, Shirt, Rollerblades, Motorbike, Keyboard, Piano
对于一下DAX代码,返回的值是Contoso, Fabrikam,原因是 Product[Brand] IN { "Contoso", "Fabrikam" } 实际上等价于 FILTER(ALL(Product[Brand]), Product[Brand] IN { "Contoso", "Fabrikam" }) ,Filter是迭代函数,创建了影子筛选上下文,ALLSELECTED(Product[Brand] )恢复了这个影子筛选上下文。
EVALUATE CALCULATETABLE ( ROW ( "Brands", CONCATENATEX ( ALLSELECTED(Product[Brand]), Product[Brand], ", " ) ), Product[Color] IN { "Blue", "Green" }, Product[Brand] IN { "Contoso", "Fabrikam" } )
2,迭代函数创建影子筛选上下文,并且ALLSELECTED(Table[Column] )用作表值函数
现在是时候生成影子过滤器上下文并要求 ALLSELECTED 激活它了。为此,我们需要一个迭代器——我们用 ADDCOLUMNS 替换 ROW,在 Brand上迭代,如下代码所示:
EVALUATE CALCULATETABLE ( ADDCOLUMNS ( VALUES ( 'Product'[Brand] ), "Brands", CONCATENATEX ( ALLSELECTED ( 'Product'[Brand] ), Product[Brand], ", " ) ), Product[Color] IN { "Blue", "Green" }, Product[Brand] IN { "Contoso", "Fabrikam" } )
发生的情况是 ADDCOLUMNS(它是一个迭代器)生成一个包含迭代表的影子过滤器上下文,迭代表是VALUES(Product[Brand]) 的结果
如何解释这个情况?首先,ADDCOLUMNS是一个迭代器,VALUES ( 'Product'[Brand] )受到显式筛选上下文的影响,对Brand进行过滤,VALUES ( 'Product'[Brand] )计算的结果不仅是确定了迭代的数据行,还是确定了影子筛选上下文。然后,ALLSELECTED ( 'Product'[Brand] )激活了迭代器在过滤器上下文堆栈上留下的最后一个影子过滤器上下文,即VALUES ( 'Product'[Brand] )确定的影子筛选上下文。
3,没有影子筛选上下文,并且ALLSELECTED(Table[Column]) 用作CALCULATE的筛选修改器
ALLSELECTED(Table[Column]) 用作CALCULATE的筛选修改器,恢复该Column上的最后一个影子筛选上下文。如果相应的列上没有影子筛选上下文,那么ALLSELECTED(Table[Column]) 不会过滤该列。也就是说,移除该列上的显式筛选上下文,并应用影子筛选上下文。
因为没有影子过滤器上下文,所以ALLSELECTED(Table[Column])不会对 Table[Column] 列应用任何类型的过滤器;由于ALLSELECTED 用于CALCULATE函数的筛选过滤器,因此,忽略该列上的显式过滤器。综上两点,ALLSELECTED(Table[Column])不会过滤对过滤Table[Column]。实际上,VALUES 通过使用颜色和品牌上的过滤器获得当前上下文中可见的产品。换句话说,过滤 Product[Product] 的不是 ALLSELECTED,而是VALUES。
下面的DAX代码中,ALLSELECTED ( Product[Product] )没有找到Product[Product]列上的影子筛选上下文,因此Product[Product]没有受到任何过滤,相当于ALL(Product[Product])。VALUES ( Product[Product] )受到显式筛选上下文的影响,即受到外层CALCULATETABLE的过滤器的影响。
EVALUATE CALCULATETABLE ( ROW ( "Products", CALCULATE ( CONCATENATEX ( VALUES ( Product[Product] ), Product[Product], ", " ), ALLSELECTED ( Product[Product] ) ) ), Product[Color] IN { "Blue", "Green" }, Product[Brand] IN { "Contoso", "Fabrikam" } )
结果是:Helmet、Shoes、Keyboard、Piano
4,存在影子筛选上下文,并且ALLSELECTED(Table[Column]) 用作CALCULATE的筛选修改器
ALLSELECTED 返回在最后一个影子过滤器上下文中可见的品牌,产生预期的结果。 如何确定这确实是 ALLSELECTED 的行为? 可以尝试将 VALUES 替换为 ALL,这样 ADDCOLUMNS 的迭代就不再发生在两个品牌上,而是发生在所有品牌上:
EVALUATE CALCULATETABLE ( ADDCOLUMNS ( ALL ( 'Product'[Brand] ), "Brands", CONCATENATEX ( ALLSELECTED ( 'Product'[Brand] ), Product[Brand], ", " ) ), Product[Color] IN { "Yellow", "White" }, -- non-existng color Product[Brand] IN { "Contoso", "Fabrikam" } )
这个DAX代码中出现了2个影子筛选上下文,这两个影子筛选上下文都是用于过滤Brand列:
- Product[Brand] IN { "Contoso", "Fabrikam" } 创建了第一个影子筛选上下文;
- ALL ( 'Product'[Brand] )创建了第二个影子筛选上下文,也是最后一个影子筛选上下文,
ALLSELECTED ( 'Product'[Brand] ) 恢复Brand列上的最后一个影子筛选上下文,而忽略显式筛选上下文。也就是说,移除某列上的显式筛选上下文,并应用该列上的最后一个影子筛选上下文;如果该列上没有影子筛选上下文,那么该列不进行任何过滤,返回所有唯一值。
四,表参数:ALLSELECTED(Table)
ALLSELECTED(Table)用于恢复最后一个影子筛选上下文,如果表中所有列上都没有在影子筛选上下文中,那么
当使用 ALLSELECTED( Table ) 时,事情开始变得更加复杂。实际上,一个表包含多个列。 在每一列上,可能有一个显式过滤器(使用 CALCULATE 设置)或一个由迭代器设置的影子过滤器。 此外,多个过滤器——显式过滤器或影子过滤器——可以嵌套在每一列上,使事情变得更加复杂。
让我们从一张完整的表开始研究 ALLSELECTED,查询的结果是:Helmet, Shoes, Keyboard, Piano。
EVALUATE CALCULATETABLE ( ROW ( "Products", CONCATENATEX ( ALLSELECTED ( 'Product' ), Product[Product], ", " ) ), Product[Color] IN { "Blue", "Green" }, Product[Brand] IN { "Contoso", "Fabrikam" } )
有如下的数据表Product
利用该表执行如下查询,SUMMARIZECOLUMNS函数首先把Color、Brand和Product作为显式过滤器上下文,分别作用于三个CALCULATE表达式。
ByValues_All移除了Product字段上的过滤器,VALUES获得按照Color和Brand过滤之后的唯一值。
ByValues_AllSelected使用ALLSELECTED函数作为过滤器修改器,由于没有影子筛选上下文,因此,ALLSELECTED函数只是移除了Product字段上的显式过滤器,而不会应用任何影子筛选上下文,VALUES获得按照Color和Brand过滤之后的唯一值。
ByAllSelected使用ALLSELECTED函数作为表值函数,由于没有影子筛选上下文,因此ALLSELECTED函数忽略显式过滤器,也不应用影子筛选上下文,获得了Product字段的所有值。
ByAll使用ALL函数作为表值函数,获得Product字段的所有值。
EVALUATE SUMMARIZECOLUMNS( 'Product'[Color], 'Product'[Brand], 'Product'[Product], "ByValues_All", CALCULATE ( CONCATENATEX ( VALUES('Product'[Product] ), Product[Product], ", " ), ALL ( Product[Product] ) ), "ByValues_AllSelected", CALCULATE ( CONCATENATEX ( VALUES ( 'Product'[Product] ), Product[Product], ", " ), ALLSELECTED ( Product[Product] ) ), "ByAllSelected", CALCULATE ( CONCATENATEX ( ALLSELECTED ( 'Product'[Product] ), Product[Product], ", " ) ), "ByAll", CALCULATE ( CONCATENATEX ( ALLSELECTED ( 'Product'[Product] ), Product[Product], ", " ) ) )
从查询结果上来看,ByValues_All和ByValues_AllSelected的值是完全相同的,我们有理由相信,当没有影子筛选上下文时,ALLSELECTED 用于移除Product[Product]上的过滤器。
五,无参数:ALLSELETED()
ALLSELECTED() 只能用作 CALCULATE 的过滤器参数,它对某个影子过滤上下文引用的所有列执行 ALLSELECTED。 在实践中,恢复每列的最后一个影子过滤器上下文。
AllSel := CALCULATE ( SUM ( Sales[Quantity] ), ALLSELECTED () ) AllSelSumX := SUMX ( VALUES ( 'Product'[Brand] ), SUMX ( VALUES ( 'Product'[Color] ), CALCULATE ( SUM ( Sales[Quantity] ), ALLSELECTED () ) ) )
然后,我们将它们投影到 Power BI 报告中:
在报告中,只选择了 Contoso 和 Fabrikam,总共有 6 个产品。 AllSel 的每一行都是矩阵中的 Quantity 总数。
令人困惑的结果AllSelSumX ,所有的数字看起来都是错误的。事实上,一旦清楚 ALLSELECTED 是如何工作的,就能明白这些数字都是完全正确的。
我们从只包含一种品牌、一种颜色的过滤器上下文开始,例如,对于Contoso/Blue的值30。两个嵌套迭代器(品牌上的 SUMX 和颜色上的 SUMX)都只迭代一行,外层的迭代函数SUMX会把行上下文传递到内层的迭代函数SUMX中。在CALCULATE中,行上下文会被转换为等效的筛选上下文,用于唯一确定该行。而ALLSELECTED 恢复最后一个影子过滤器上下文,实际上,内层SUMX中的VALUES ( 'Product'[Color] )只包含一行,也就是Contoso/Blue的数量30。
当计算Contoso的数量时,内层SUMX中的VALUES ( 'Product'[Color] )包含Blue、Greed和Red三个值,这三个值是迭代的行集(即迭代器需要逐行扫描的表),也是ALLSELECTED 恢复的最后一个影子过滤器上下文。也就是说,CALCULATE函数每一次迭代都是计算三个Color的总数量60,并且需要迭代三次。
AllSelSumX计算Contoso的过程是:
180 等于 60 乘以 3;对品牌进行一次迭代;对颜色进行三次迭代;并且,颜色的最内层影子过滤器上下文包含三种颜色。
强烈建议用户花些时间来理解 Contoso 行的计算过程,理解了ALLSELECTED 的工作原理。
六,ALLSELECTED函数和上下文转换
ALLSELECTED 是否以任何方式与上下文转换交互?我们知道 CALCULATE 生成一个过滤器上下文,该过滤器上下文等效于任何现有的行上下文,作为其创建的过滤器上下文的一部分。我们还知道,通过上下文转换生成的过滤器上下文的优先级低于任何显式过滤器上下文。使用 ALLSELECTED 作为 CALCULATE 过滤器参数意味着将影子过滤器上下文转换为显式过滤器上下文。这样,通过上下文转换生成的过滤器,获得跟显式筛选上下文一样的优先级。
因此,看起来 ALLSELECTED 删除了由上下文转换生成的最后一个过滤器上下文,实际上不会发生这样的删除。删除由上下文转换生成的最后一个过滤器上下文的效果只是将影子过滤器上下文转换为显式过滤器上下文的副作用。
七,总结
ALLSELECTED函数的最佳实践是:当且仅当ALLSELECTED函数被直接置于Matrix、Table等Visual对象中的度量调用时,它才能用于检索当前Visual之外的筛选上下文。
遵循一个简单的规则:如果一个度量在代码的任何地方使用了ALLSELECTED函数,那么这个度量就不能被其他任何度量调用。
如果有任何数量的迭代,建议用户避免使用 ALLSELECTED,因为结果非常难以理解。因此,强烈建议:ALLSELECTED 不应该在迭代中使用。在大多数情况下,变量可以避免在迭代中使用 ALLSELECTED,为此强烈建议使用变量。
参考文档: