Calculate是目前DAX语言中最重要、最有用同时也是最复杂的函数,值得单独成章进行介绍。涉及Calculate函数的内容很多,这里介绍的是行上下文转换
。
1 上下文转换的定义2 触发上下文转换的条件3 计算列中的上下文转换3.1 简单应用3.2 计值顺序4 度量值中的上下文转换4.1 简单应用4.2 筛选器交互5 行上下文嵌套时的上下文转换6 上下文转换的注意事项
计值上下文分为筛选上下文
和行上下文
。筛选上下文用于筛选数据(筛选整个模型),而行上下文用于迭代表(迭代一个表)。
CALCULATE执行的一个操作可以将行上下文转换为筛选上下文,这个操作就是上下文转换
,其定义如下:
CALCULATE可以使任何行上下文无效。它会自动添加在当前行上下文中迭代的所有列作为筛选器参数——筛选它们正在迭代的实际值。
换句话说,行上下文转换就是把行上下文转换成一组筛选器,这些筛选器再进行交互,然后产生筛选上下文,因此行上下文转换的产物就是筛选上下文。
上下文转换会在以下情况下发生:
CALCULATE
或CalculateTable
函数时。度量值
时,因为引用的度量值在CALCULATE函数内部执行它的DAX代码。单层行上下文的转换非常直观,相信都能理解。那下面就通过一个简单例子来介绍下,具体如下图所示:
对于计算列SumOfValue
,由于在计算列的初始计值环境里不存在任何筛选器,所以筛选上下文里的数据为所有数据,故导致每一行的结果都是总计值。
在原有计算列表达式上添加CALCULATE函数,结果如下图所示:
行上下文遇到CALCULATE函数时,会发生行上下文转换。简单来说,就是会为每一行的各个列设置筛选器,筛选的内容即为各列在当前行的对应值。由于上面这个例子中的每一行都不重复,所以行上下文转换后所得到的筛选器筛选出来的可见数据就只有一行,即当前行的数据,故SUM函数汇总后的值与当前行的值一致。
需要注意,行上下文转换后所得到的筛选上下文并不一定只有当前行这一行可见数据,当基础表里存在重复行时,那么由某个行上下文转换而来的筛选上下文的可见数据有可能是包含多行的,虽然这些行都是相同的。
下面再来看一个例子,假设现在需要添加一个计算列,计算当前类别对应的所有值中的最大值,结果如下图所示:
其中使用到的计算列表达式如下:
MaxValueOfCategory =
CALCULATE ( MAX ( 'Table'[value] ), ALLEXCEPT ( 'Table', 'Table'[Category] ) )
设计思路是,找到当前行的类别在基础表中所对应的所有值,然后再取最大即可。当行上下文发生转换后,可以得到三个筛选器,而在这个例子中只需要类别筛选器即可,因此只要把其它筛选器移除掉就可以得到当前行的类别所对应的所有值,然后在修改后的筛选上下文里直接取最大值即可。
(1)ALLEXCEPT用作CALCULATE调节器时,将移除第一参数指定的表的扩展表中除所指定列之外的其余列上的所有筛选器。
(2)CALCULATE调节器在上下文转换之后应用,因此可以改变上下文转换的效果。
把3.1节计算列的表达式用度量值重写,具体的表达式如下:
SumOfValue-Measure = SUM('Table'[value])
然后再在计算列里引用这个度量值,结果如下图所示:
在计算列里引用度量值,会使行上下文发生转换,变成筛选上下文;引用度量值会使行上下文发生转换的原因是DAX引擎自动添加的CALCULATE函数。
如果在一个已经具有筛选器的计值环境下发生行上下文转换,那么转换而来的筛选器与原本就存在的筛选器之间的交互又是如何的呢?
答案是,遵循筛选器交互的最基本原则,那就是非相同列的筛选器为相交,相同列的筛选器则用后执行的覆盖前面的。其中,行上下文转换而来的筛选器较后执行。
来看下面的一个例子,用到的度量值如下:
产品所对应订单的销量 =
CONCATENATEX ( ALL ( '销售表'[订单号] ), CALCULATE ( SUM ( '销售表'[销量] ) ), " | " )
所有订单的销量 =
CONCATENATEX (
ALL ( '销售表'[订单号], '销售表'[产品] ),
CALCULATE ( SUM ( '销售表'[销量] ) ),
" | "
)
CONCATENATEX( Table, Expression, [Delimiter], …)
对Table的每一行计值表达式Expression,将所有结果连接到一起返回单个字符串,并使用指定的分隔符Delimiter分隔。
新建一个矩阵,行标签为销售表的产品字段,将上面两个度量值放入矩阵中,结果如下:
(1)对第一个度量值来说,由于ALL函数只返回全部订单号所形成的单列表,因此当发生行上下文转换时,上下文转换而来的订单号筛选器将与行标签提供的产品筛选器相交,所以只有行标签所显示的产品对应的订单才有值,不属于行标签的产品的订单对应的值将为空。
(2)对第二个度量值来说,因为ALL函数返回了全部订单号与产品形成的表,当发生行上下文转换时将得到两个筛选器,一个是订单号的筛选器,另一个则是产品的筛选器,那么在与外部的行标签提供的产品筛选器交互时,转换而来的产品筛选器将覆盖行标签的产品筛选器,因此在迭代计算的过程中,行标签的产品筛选器将影响不了计值环境,因此第二个度量值的所有订单都能够计算出对应的销量。
想要彻底掌握行上下文嵌套时的行上下文转换,是需要一些前置知识的,比如:筛选器的交互方式、CALCULATE函数的计值流程、扩展表原理、行上下文嵌套等等。
当存在多层行上下文嵌套时,如果发生了行上下文转换,那么所有层级的行上下文都会进行转换,而不是仅仅只转换某一层。但在转换时的执行顺序是有先后的,将按照从外到内,依次从最外层的行上下文开始转换,直到最内层行上下文转换完毕。
那么在这个转换的过程中,转换而来的筛选器依然遵守筛选器交互的最基本原则,即非相同列的筛选器为相交,相同列的筛选器则用后执行的覆盖前面的。因此,层级越靠内的行上下文由于越后执行转换,其转换而来的筛选器将具有一定的优势,最内层的行上下文转换而来的筛选器则能够完全保留。所以很多人都错以为多层行上下文嵌套时的行上下文转换是仅转换最内层的行上下文,这个理解方式是错误的。
值得注意的是,由行上下文转换而来的筛选器也有可能会不遵守筛选器交互的最基本原则,例如某层行上下文中使用了KEEPFILTERS
函数,那么其转换而来的全部筛选器的交互方式将变为相交。但只要按照行上下文转换的顺序,依次地处理每个筛选器的交互即可保证不出错。
那下面就通过一个案例来熟悉一下这种多层级的转换过程,用到的数据与模型如下图:
现在需要统计每个产品的销量,并把销冠产品(销量最大的产品)的销量单独显示出来,具体效果如下图所示:
解决思路很简单,只要判断当前行标签的产品的销量是否等于最大销量,若等于就显示相应的销量,否则为空。因此最佳的解决方法就是使用IF函数来求解。为了演示的需要,这里我们采用FILTER函数的第二参数来进行判断,人为地增加一些难度。
首先,来看一个错误的写法,用到的度量值如下:
销量 = SUM('订单表'[数量])
销冠产品的销量-Wrong =
CALCULATE (
[销量],
FILTER ( VALUES ( '产品表'[产品代码] ), [销量] = MAXX ( ALL ( '产品表'[产品代码] ), [销量] ) )
)
结果如下图:
上面这个错误的写法只有两层的行上下文嵌套,并不会太复杂,而且两层的嵌套还是很常见的。那么下面来分析一下,这个写法为何不能得到正确结果。
(1)行标签提供了一个产品名称的筛选器,然后VALUES被其筛选,返回相应的产品代码,此时如果FILTER第二参数为真,那么VALUES返回的产品代码将成为CALCULATE的内部筛选器并与行标签的产品名称筛选器相交,由于产品名称与产品代码相对应,因此必然能够返回当前行标签的产品的销量;若FILTER第二参数为假,那么FILTER将返回空,使得最终结果为空。
因此,整个逻辑的最核心部分就是FILTER的第二参数,如果能够在行标签为销冠产品时让FILTER第二参数为真,其它产品为假,那么将达到案例所需的效果。
(2)对于上图标注的第一个销量度量值来说,其所处的计值环境有行标签提供的产品名称筛选器,以及FILTER提供的行上下文,当其开始计值时,行上下文转换得到一个产品代码筛选器,将与行标签提供的产品名称筛选器相交,由于产品名称与产品代码相对应,因此返回当前行标签的产品对应的销量。
(3)对于上图标注的第二个销量度量值来说,其所处的计值环境有行标签提供的产品名称筛选器、FILTER提供的行上下文,以及MAXX提供的行上下文。当其开始计值时,两层行上下文都将发生转换,但MAXX提供的行上下文较后执行,且FILTER与MAXX的行上下文转换后的筛选器均为产品代码列上的筛选器。因此,MAXX提供的行上下文转换后得到的产品代码筛选器将覆盖由FILTER提供的行上下文转换而来的产品代码筛选器,然后再与行标签提供的产品名称筛选器相交。因此在MAXX函数迭代的过程中,只有与行标签的产品名称相对应的产品代码才会有值,其它的则为空,因此MAXX最终返回的其实就是当前行标签的产品对应的销量。
所以FILTER第二参数其实是恒成立的,使得所有行标签都能够计算到对应的销量。
(4)经过上面的梳理,已经成功找到了这个错误写法之所以错误的原因了,那就是FILTER第二参数恒成立了,达不到筛选的效果,但最根本的原因其实还是MAXX函数在迭代过程中无法找到所有产品的最大值,因为其受到了行标签提供的产品名称筛选器的影响。既然找到原因了,那么修改起来也就有了针对性,只要在MAXX函数的计值环境中把行标签提供的产品名称筛选器移除即可,具体如下:
销冠产品的销量-Correct =
CALCULATE (
[销量],
FILTER (
VALUES ( '产品表'[产品代码] ),
[销量] = MAXX ( ALL ( '产品表'[产品代码] ), CALCULATE ( [销量], ALL ( '产品表'[产品名称] ) ) )
)
)
结果如下图:
那么这个案例到这里就结束了,下面给出一些该案例的错误写法与正确写法,仅供参考:
(1)
销冠产品的销量-Wrong1 =
CALCULATE (
[销量],
FILTER ( ALL ( '产品表' ), [销量] = MAXX ( ALL ( '产品表'[产品代码] ), [销量] ) )
)
销冠产品的销量-Correct1 =
CALCULATE ( [销量], FILTER ( '产品表', [销量] = MAXX ( ALL ( '产品表' ), [销量] ) ) )
结果如下图:
(2)
销冠产品的销量-Wrong2 =
CALCULATE (
[销量],
FILTER ( ALL ( '产品表' ), [销量] = MAXX ( ALL ( '产品表' ), [销量] ) )
)
销冠产品的销量-Correct2 =
CALCULATE (
[销量],
KEEPFILTERS ( FILTER ( ALL ( '产品表' ), [销量] = MAXX ( ALL ( '产品表' ), [销量] ) ) )
)
结果如下图:
参考资料:
[1] 理解上下文转换(https://www.powerbigeek.com/understanding-context-transition/)
[2] DAX函数-CALCULATE上下文转换(https://zhuanlan.zhihu.com/p/601798829)
[3] DAX权威指南(第二版)(https://www.powerbigeek.com/definitive-guide-to-dax-cn/)
[4] CALCULATE 指南(https://www.powerbigeek.com/definitive-guide-to-calculate/)
[5] 理解ALLEXCEPT函数(https://blog.csdn.net/Degenerate_Memory/article/details/118158014)
[6] 理解行上下文转换(https://mp.weixin.qq.com/s/wvPlLTpPFngy-raAI_qMww)
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有