从SUM让人看看PowerBI DAX的坑爹之深

2019-09-23 19:36:54 浏览数 (1)

DAX,simple but not easy.DAX,简约而不简单

如果说99%的人不真正理解PowerBI DAX的SUM,你信吗?只怕是说少了,从这个意义上讲,PowerBI就是一个坑爹的。

我们敢说自己在深入的研究PowerBI,但绝不敢说我们理解了PowerBI的一切,本文让你看看它坑爹的深度。

现给出结论:在DAX中不存在真正的SUM。很多人一定懵了,DAX 在被设计的时候被刻意地与Excel函数的用法做了贴近,而导致PowerBI的运算可以很快入门,但很快就会遇到一个普遍现象:DAX的计算结果和你期望的结果完全不同,而你无法解释为什么。

SUM的迷思

从一道经典的问题来看:

用户在订单表创建了一个计算列,并写入一个简单的公式,在按下回车的时候,每行的值应该是多少?请思考10秒继续。 有两个选择: A 每行都是一样的数字,为整个列值的和。 B 每行的数字都是当前行的销售额。

这个纠结非常符合入门伙伴会犯的错误。大家在学习了行上下文以后,了解到创建计算列的时候会创建行上下文,因此,SUM( FactOrders[销售额] ) 发生在当前的行上下文中,而由于行上下文的存在,FactOrders[销售额] 应该是当前行的值,所以,SUM( FactOrders[销售额] ) 应该和当前行的值一样。这个理解从逻辑上和已经学习的规则上是完全正确的。而你立马会被颠覆三观地看到:

你毫无办法,DAX就是这么任性,怎么办怎么办,打开《DAX圣经》来寻找答案吧:

在这一章节是这样描述的:

其含义是:这里虽然有行上下文,但 SUM 会忽略行上下文。也许,是SQLBI老师为了让大家简化理解,所以这样描述了结果。在我第一次学习到这里的时候,脑中有四个疑问: 1、刚学完行上下文规则就出现破坏规则的特例,郁闷!好吧!那么除了SUM,还有谁会忽略? 2、如果刚学完的规则马上就被破坏,那规则还有什么用,难道DAX就是这样随意的设计吗? 3、凭什么你说忽略就忽略啊,有什么证据啊? 4、这个疑问最严重,直接导致我可以把书撕了,不是刚才刚说好DAX有两个上下文,一个行上下文,一个筛选上下文,所有的运算都是发生在这两种上下文中,无一例外。(好棒,这个规则太好了),晕,这么快就来一个可以忽略行上下文的东西。那刚刚说的算什么~~~ 非常气愤!!!

如果你第一次学习DAX会有类似的疑问,那您和我是一类人,崇尚完美,不喜欢破坏规则,而也充分相信DAX的设计者不会把DAX设计成一个这么傻的东西。在很多学员中,都记住了这个特殊规则,但这里想告诉你的是:DAX有两个上下文,一个行上下文,一个筛选上下文,所有的运算都是发生在这两种上下文中,无一例外。既然无一例外,那么SUM会不会忽略行上下文呢?当然不会了,那怎么解释这个问题呢?真正的原因在于:在DAX中,是不存在SUM的,任何的SUM在计算时都会转化成SUMX。

也就是说:SUM(T[C])会被无一例外的转化为SUMX( T , T[C] ),因此在DAX实际执行的时候,是没有SUM的。

回到这个问题来看,这里确实不会忽略行上下文,而是将SUM转换成了SUMX,并进入SUMX进行计算,而SUMX将创建新的迭代过程,在表中逐行迭代,进而得到了整列的聚合结果。这个解释完美的解释了这里的问题,同时没有引入特例,没有不完美,非常好。

你是想问 SQLBI 的大师是不是说错了呢?严格意义上说,当然是了。他们引入了特例且用这种生硬的办法来定义规则,不可接受。但我也充分知道他们和我一样是知道SUM会被转换为SUMX的,他们这样描述,我猜想是要避免告诉你这么多底层的内容,就会增加学习难度。

吾爱吾师,吾更爱真理。并不是一个口号,它体现在你所有的学习和工作中。注意,我们给出这个案例绝对不是为了证明意大利人错了,而我们更牛,非也,这毫无意义。我们给出了一种理解和思考问题的方式以及真实的一个学习过程。正相反,我们无数次的研究了意大利人写书的逻辑和顺序,可以发现其编排是精心考虑的,我们能快速顺利的接受DAX,必须承认他们做了巨大的贡献,这里仅仅是一点点脚注来辅助当时和我们有一样困惑的读者。

更复杂的迷思

来看一个更复杂的迷思来挑战你对DAX的认知吧:如何计算积累销售额,其算法是这样的:

其效果如下:

从效果上看,很容易理解,这个是积累求和的典型案例。但如果你用自己学的DAX知识来解释的时候,就会发现发现问题了,我们一起来看:

第8行,在FILTER 第9行,ALL(DimDates[Date])得到了所有的日期,由于FILTER是迭代函数,进行迭代并逐一创建行上下文。 第10行,在创建的行上下文中来比较 DimDates[Date] 与 MAX ( DimDates[Date] ),问题来了,既然是在行上下文中,DimDates[Date] 只有一个值,那么 MAX ( DimDates[Date] ) 将永远等于 DimDates[Date] 本身,那么也就无法筛选到小于当前日期的所有日期。

对于这个问题,我们再看看意大利人曾经的解释是怎样的:

在读到这里的时候,我们首先想到的应该是什么,是不变的规则:DAX有两个上下文,一个行上下文,一个筛选上下文,所有的运算都是发生在这两种上下文中,无一例外。

按照意大利老师的解释,MAX直接运行在了筛选上下文中,我的天,那行上下文呢?至少在SUM的章节还提了SUM会忽略行上下文,到这里干脆连提都不提了,因此,在SUM的那个章节如果你没有真正搞懂的话,到这里说自己懂的,怎么可能呢~~~ 你此时可以有和SUM一样的理解,MAX和SUM一样忽略了行上下文,所以只有筛选上下文了。而真实的情况是:DAX中没有真正的MAX,所有的MAX在实际都会转换为MAXX,也就是MAX(T[C])的本质是MAXX(T,T[C]),因此在第10行到第11行,MAX ( DimDates[Date] ) = MAXX ( DimDates , DimDates[Date] ) ,此时可以继续解读MAXX中的DimDates,而它所处的筛选上下文,正是当月,所以MAXX ( DimDates , DimDates[Date] ) 会得到当月的最后一天。

至此,就搞清楚了这个积累求和模式。目前搞清楚这个积累求和模式的人还非常少。

如果你认为你自己是其中一个位,那么请继续看。

更更复杂的迷思

注意:在积累求和模式中,第9行,这里是 ALL ( DimDates[Date] ),而外部的筛选环境是 DimDates[Month],因此,ALL ( DimDates[Date] ) 虽然可以忽略对 [Date] 的筛选,但却不能忽略对 DimDates[Month] 的筛选,那么,在任何一个月,都只会在当月迭代,是无法出现从历史至今的效果的,一个非常巨大的矛盾出现了。

怎么解?这个问题已经超过了这里讨论的范畴。直接可以给出结论是:由于 DimDates 是日期表,系统会在 CALCULATE 的计算中自动加入 ALL( DimDates )。就不再展开了。只有极少数奇葩分子会研究下去,当然,他们也将是最终掌握DAX的人。

总结

记得在大学学习过C语言,然后有一个奇葩面试题是:在C语言中,有:a ; a ; a = a 1 ; a = a = 1,请问哪个执行效率高? C语言经过了编译器才转换为二进制代码得到执行;在DAX中,类似的是DAX表达式会被转换成对DAX引擎的查询计划才会被执行。 PowerBI的学习者有三类: 1、拖拽可视化的; 2、解决实际问题就行,搞不清楚就记住; 3、追根究底的。

我们并不是说类型3才是正确的选择,因为每个人的定位是不同的,不过,在Excel120,我们覆盖从1到3的全部内容,欢迎大家交流探讨,共同进步。 但是通过这些内容,我们可以知道的是:DAX,simple but not easy。我们在Excel120穷举式的研究PowerBI的所有细节。 学习DAX的阶段是: 1、看了能懂,写不出来 2、基础的写的出来,复杂的写不出来 3、复杂的写出来,但有的坎过不去 4、理解所有细节,性能调优

PowerBI的坑爹就是越坑越着迷越上瘾,真正学会了 DAX,PowerBI就是你的玩具,祝大家玩得开心。

为了彻底攻克Power体系,让更多的人拿来就用,我们正在考虑建立《PowerBI DAX 数据建模设计思想大全》,预览:

感兴趣的欢迎交流

0 人点赞