前言
在前面介绍自动匹配原理的文章中,有提到Auto-Exists机制本身就存在Bug,那么本篇文章就来探讨一下这个问题,而这个问题其实就是对度量值所处的计值环境的研究。
这个Bug其实PowerBI官方也是清楚的,但由于海量的报表已经运行在这个Bug的基础之上,影响范围过大,因此也不好直接修复这个问题。所以在2024年10月,PowerBI更新了一个名为“值筛选行为”的功能,就是为了解决这个Bug,并通过提供手工切换的方式,来让用户自行决定是否要继续沿用这个Bug特性,还是采用另一种修复后的计值流程。
正所谓,本来没有路,走的人多了也就成了路。由于影响范围过大且Bug行为相对稳定可重复,这个Bug已经不再是Bug了,它演变成了一个特殊的特性。所以,还是很有必要对这个Bug特性进行研究和掌握的。但这毕竟是Bug,因此即使是在大量案例的基础下归纳总结出的计值流程也不一定正确,只能说是符合绝大多数场景,在某些场景下可能不适用。
本篇文章只介绍该Bug特性,关于通过更改“值筛选行为”进行修复后的正确计值流程将在后续文章中介绍。另外,本篇文章介绍的内容仅代表作者的自身理解,建议读者在掌握了计值上下文和自动匹配原理后再进行阅读。
Bug的证实
一直以来我都害怕是自己学艺不精,即使在面对各种DAX原理无法解释清楚、甚至自相矛盾的案例时,我都仅仅只是怀疑,而不敢光明正大的对所有人说,这就是一个盖棺定论的Bug。
直到后来,该Bug由DAX之父Jeffrey Wang亲自承认,才终于解决了我多年以来的困惑。以下是Jeffrey Wang的原话:
Jeffrey Wang SAYS : You’re sharp! You’ve uncovered one of the key reasons we introduced independent mode. There’s been a longstanding “bug”, a performance optimization, that effectively triggers independent mode under certain conditions.
As it turns out, this optimization happens in several common scenarios (including your repro), which likely explains why few users reported issues with coalesced behavior: they were often unknowingly experiencing independent mode.
We discovered this “bug” a long time ago but chose not to fix it, as doing so would have forced many reports into the less intuitive coalesced behavior, potentially breaking them.
以上出自Jeffrey Wang的博客评论,因网络问题我无法亲自留言,故由另一位小伙伴“一棵松 (VAC) ”代为留言提问。链接:Understanding the Impact of Value Filter Behavior。
一切正常时的计值流程
由于Bug的影响,Auto-Exists机制在不同的场景中会有不同的行为,导致度量值所处的计值环境也随之不同,既有没触发Bug的正常行为,也有触发了Bug所带来的几种特殊行为。
下面先来看一下没触发Bug时的Auto-Exists机制,也就是PowerBI产品组最开始所希望达到的效果,如下图所示:
首先,报表环境的筛选器的交互方式都是相交的,无论是相同列或非相同列,一律是相交行为。另外在上图这个案例中,由于两个切片器以及矩阵的两个行标签都是来自同一个表,因此还会触发Auto-Exists机制。下面以矩阵第一行为例来简单介绍其计值流程,具体如下图所示:
简单来说就是因为Auto-Exists机制,使得产品代码筛选器也被过滤到只剩一个值,而这个值刚好是矩阵行标签的产品所对应的产品代码,所以Sales-ALL Product度量值虽然移除了产品名称筛选器,但由于产品代码筛选器还在,所以计算的其实还是同一个产品的销售额。
Bug的出现
上面的案例介绍的就是PowerBI产品组最开始所希望达到的效果,看起来好像没什么问题,计值流程也完美符合Auto-Exists的理论。但实际上,如果再移除多一个产品类别筛选器,就可以看到问题了。如下图所示,添加一个新的度量值去同时移除产品类别和产品名称的筛选器。
可以发现,新添加的度量值的结果竟然返回了切片器所选的四个产品代码的销售额之和。而这与DAX原理其实是不符合的,因为根据原理,其计值流程应该如下图所示的才对,即最终新添加的度量值也应该返回矩阵当前行的产品的销售额。
所以这里其实是出现了Bug,也就是Jeffrey Wang所说的:在很多常见场景中,Auto-Exists优化机制并没有按预期设计的行为去执行,反而触发了“值筛选行为”为“独立”模式下的行为。
There’s been a longstanding “bug”, a performance optimization, that effectively triggers independent mode under certain conditions.
As it turns out, this optimization happens in several common scenarios (including your repro), which likely explains why few users reported issues with coalesced behavior: they were often unknowingly experiencing independent mode.
也就是说,Bug的出现才是常态,而完全正常的、不含Bug的Auto-Exists机制的出现场景反而是少数,不得不说这很幽默。
对Bug特性的研究
下面就来介绍一下我对该Bug特性的研究,给大家做一个参考吧。根据大量案例下的研究,该Bug特性的计值流程可以分成三部分,其中一个就是上面介绍的完全正常的Auto-Exists机制的行为,另外两个就是根据报表环境中是否出现了固化筛选器来划分。
而这三种场景下的计值流程都是不一致的,所以也没法总结归纳出一个恰当的”理论“来诠释它,毕竟这是个Bug,行为有点迷是可以理解的。所以只能是分类处理,死记硬背这三个场景下各自的计值流程了。
1、没触发Bug,完全正常的Auto-Exists机制的场景
该场景下的计值流程就如上面案例介绍的一样,所有报表环境中存在的初始筛选器都先进行相交,然后如果存在同一个表上的两个或以上的筛选器,那么再触发Auto-Exists机制,将那些无效的筛选器组合给过滤掉,过滤后剩下的筛选器组合就是提供给度量值的计值环境了。
而该正常场景反而碰到的不多,它属于极少数情况,该场景的触发特点有以下几点:
- 存在两个及以上的切片器,并且切片器的字段之间含有类别与子类别这种层级关系
- 视觉对象存在两个或以上的轴字段,并且轴字段间也含有类别与子类别这种层级关系
- 切片器的字段与视觉对象的轴字段之间有重叠,即出现相同字段,且重叠的必须是类别颗粒度层级的字段
- 以上出现的所有字段都来自同一个表
当满足以上要求时,如果度量值中没有移除筛选器,或移除的筛选器只是子类别颗粒度层级的字段上的筛选器时,那么其计值流程就会遵循正常的行为,即不会出现Bug。但如果尝试在度量值里同时移除类别和子类别字段上的筛选器时,那么反而会触发Bug,此时触发Bug后的计值流程可以参考下面介绍的两个场景中的计值流程。
由于该完全正常的场景属于极少数情况,所以不妨倒转天罡,把正常的行为当作Bug,而把真正的Bug特性当作正常的行为,这样可能会更好理解,也更容易记住。
2、报表环境中只存在标准筛选器时的场景
为便于描述,将报表环境分为两部分:一部分是视觉对象的轴字段提供的筛选器,比如矩阵的行列标签提供的筛选器。另一部分则是除了视觉对象的轴字段提供的筛选器之外的其他初始筛选器,统一称之为切片器环境。
那么该场景中的计值流程如下:
由于不会触发Bug的完全正常的场景是极少数,而且报表环境中一般也不会存在固化筛选器,所以这个其实就是最常见的场景了,将上面的计值流程搞清楚并记住,基本就掌握了80%的Bug特性了。
3、报表环境中存在固化筛选器时的场景
在报表环境中添加固化筛选器的方式有两种,其中最常见的一种就是通过层级切片器,另一种则比较罕见,那就是给存在两个或以上轴字段的视觉对象添加度量值视觉筛选器。
同样的,为便于描述,将报表环境分为两部分:一部分是视觉对象的轴字段提供的筛选器,比如矩阵的行列标签提供的筛选器。另一部分则是除了视觉对象的轴字段提供的筛选器之外的其他初始筛选器,统一称之为切片器环境。
那么该场景中的计值流程如下:
总的来说,报表环境中出现固化筛选器的概率是很低的,并且常见于新手,因为他们经常会使用层级切片器,而不是把层级切片器拆分成多个标准筛选器。所以,只要能够遵守良好的开发实践规范,那么这种存在固化筛选器的场景其实也不容易遇上。
总结
一直以来,各种PowerBI或DAX的资料基本都没有介绍过度量值所处的计值环境,而本篇文章对其进行了研究,并给出了自身的理解心得,勉强算是补充了这块知识点。
无论对计值上下文和筛选器的交互掌握得有多么娴熟,如果最开始时度量值所处的计值环境就理解出错了,那么后续的筛选器交互再怎么正确,也不可能得到想要的结果,正所谓差之毫厘,谬之千里。
最后再次提醒,本篇文章介绍的内容仅代表作者的自身理解,有任何问题欢迎留言讨论!