前情回顾
在《PowerBI DAX 重构系列:用1个度量值代替100个 实现 动态多维度动态算法动态总计(上篇)》我们最终来到:
现在的问题是:不光只有销售额同比增长率这一个度量值要顾及,我们要顾及包括:
- 销售额同比增长率排名(按全部,按所选全部,按地区,按类别)
- 销售额同比增长率排名增长(按全部,按所选全部,按地区,按类别)
- 销售额占比(按全部,按所选全部,按地区,按类别)
因此,在PowerBI中会写度量值只是基础,关键是要照顾到这些可能的变化。本篇来详解这些内容。
关于排名
排名,在业务中常用的获取重点的方法,在PowerBI DAX中由 RANKX 函数给出,关于理解 RANKX 的计算逻辑,要求:
- 精通筛选上下文
- 精通行上下文
- 精通上下文转换
如果这三个概念不知道,然后知道了RANKX怎么用,完全属于死记硬背。这里给出对其原理的正确记忆方法:想象如果DAX中没有RANKX函数,你将怎样实现排名,大致思路将是:例如要对产品类别进行排名: 第一步:构建排名临时表如下:
第二步:找到当前排名元素在上述临时排名表中的位置
这里的 > 或 < 和按高低有关,这样就得到了 x 在临时排名表中的位置。也就是实现了RANKX,或者换句话说RANKX就是这么实现的,用RANKX相当于内部写了上述两个过程。最终要用RANKX计算排名,你只需要记住:
某元素的排名 = RANKX( ALL( Items ) , [Measure] )
由于知道了上述了底层逻辑,RANKX就彻底理解了,包括什么时候以及为什么会启用第三个参数。这里不再赘述。(关于 RANKX ,值得单开一篇再次详述,但核心此处已经描述清楚)
本案例的排名计算如下:
效果如下:
其中,总计不应该有排名,因此,将公式中的 0 改为 Blank,如下:
结果仍然不能让人满意,总计应该排在最下面,试试用 “” 代替 BLANK ,如下:
你可能会在这个过程遭遇一个PowerBI DAX的BUG,如下:
IF 函数在DAX中是一个亟待被优化的函数,由于IF可能返回文本或数字,请问这个度量值是什么类型呢,显然我们不要文本类型而是数字类型。修复这个BUG的方法是:把””改成一个数字后回车,再改回””即可。效果如下:
如果问怎么能知道这些呢,答案是用过了所有的细节踩过所有的坑就知道了。关于IF,值得总结的点如下:
- IF 是无法返回表的
- IF 可能返回不同类型的数值时,可用希望返回的类型绕过BUG
- IF 和 SWITCH 本质一样,选项超过 2 个时使用SWITCH
至此,终于得到了完美的带总计排名。
关于销售占比
销售占比,指的是局部销售额与整体销售额的比。来反映某类项目与全局的一种关系。在本案例中的实现如下:
效果如下:
对于销售占比的总计,这里使用了100%,其实总计的含义已经不是简单的相加了,因此使用100%也是值得商榷的,另外更加正确的理解是,总计应该是全局范围的值,这里的另一种合理的结果应该是全局的平均效果,公式修正如下:
效果如下:
数学系的同学会跳出来证明,总计值永远是: 1 / n ,n为参加计算的分类数。没错,所以可以用这种方法来进行优化,但这里有个更深的细节需要注意,随着计算的年份不同,而如果不是产品类别维度,而是产品维度,可能有的产品由于下架,是没有销量的,因此不应该计入分母 n ,n 应该是只发生销售的那些项目。真实的世界就是如此,主体的简单让人忽视了细节的复杂。这里不再延伸,实际用这种做法的效率也是可以接受的,因为PowerBI DAX 的底层引擎Vertipaq在算这类计算时太快了,没必要优化,除非有洁癖。
推广到动态维度和指标
维度建模 理论是PowerBI可以主要参考的一种理论模型,当然在如今完全按照维度建模理论来玩PowerBI是不能完全适应的。(例如:按全局总计这种辅助表算什么,在实际中,它们没有数据意义,却在PowerBI中有巨大的功能性意义),曾尝试归纳结合维度建模思想并更适合PowerBI的玩法,本系列的《重构系列》将是未来正式阐述 自助表建模 的重要基础。
Power BI DAX 在语言级别对于复用的支持是很弱的,导致实际在写 DAX 的时候需要大量的 复制 粘贴,而往往一处改动,又要去全部修改所有类似的度量值,这种纯体力还容易出错,错了还找不到的问题将成为梦魇,从概率的角度,一定会出错的,一定会找不到的,一定会在生产环境某天被最终用户发现的,然后就有得搞了。先来看本案例:
再看下已经开始产生重复的度量值集合:
其中,销售类指标是基础指标集合,其他的分析是基于此的,这种层次结构便于将业务领域的主题映射为Power BI DAX度量值,可以满足工程做法。问题是随着维度的增加,可能出现 度量值爆炸 的现象(度量值海量增加)。由于其中存在大量类似的逻辑,一旦某一处逻辑需要修改,就需要全部调整,出现凭概率就会犯的错误。解决这种问题的最好方法就是:微软继续增强DAX对复用的支持,可惜没有。在此前,则可以采用如下这种套路:
- 【第一步】让用户选择需要使用的维度
- 【第二步】再让用户选择使用的度量值
- 【第三步】进行计算
理论上,通过上述三步可以实现一个度量值来替代海量度量值的方法,并称该方法为:全动态度量值。就是在用户对 维度 和 指标 等参数需要在实时做出选择后的实时计算。这充分体现了Power BI DAX的强大,在此前的多篇标题为《xxx全动态xxx》文章中经常采用这种方法,来将PowerBI的动态性提升到最大限度,但这种极致性通常也可能产生副作用:
- 一旦用户需求产生对某个维度或指标的局部修改,并没有一个针对它可以快速应对的度量值可用。
- 全动态度量值的编写结果很可能超过几十行甚至100行DAX,任何修改都会影响使用该度量值的所有地方。当然,如果有足够好的DAX写作结构,这并不算太大的问题。
- 由于 DAX 本身的限制(如:上述提到的 IF 的限制),返回的结果可能是多类型(销售额是数字,而增长率是百分比)时,难以应对。
这里已经将这种理论及可行性分析清楚,经实践也的确可行。而在这里的实际中,我们并不准备一次性达到这种极致,我们将按照某个 特定指标 做实践。感兴趣的伙伴可以自行做更深入的实践。
我们为了编写这样的度量值,通常我们需要一个良好的结构,有点像作文模板,把框架搭建后,然后再填入具体的内容。如下:
这里使用了 ERROR 这个函数作为占位符,它可以保证度量值可以顺利编写,如果万一忘记什么,这个ERROR也会在运行时提醒你。如下:
这个巧妙的设计,是编写复杂度量值必须的技巧,它可以帮我们做很多事情,这里仅仅是抛砖引玉。
然后,要做的就是在上述的模板中,进一步补齐 DAX,如下:
仔细观察上述的结构,大有学问:
- RETURN 单独一行,并可以随时进行测试
- 继续使用ERROR占位度量值
- 使用 VAR 进行预计算以进行简化
- 通过有效的使用注释及缩进结构极大增强可读性
这种被优化了的结构就像被收拾了的房间,你要找任何东西都可以随心所欲。
按照 SQLBI Macro的说法,没有结构化缩进的DAX公式不是DAX公式,极为赞同。所以,有人也许在写DAX,但其实根本没在写。正如老师告诉小学生写字要写在田字格里,他就非要用自己最烂最懒的方法来,有什么办法。
Power BI 的公式编辑器有些优点也有些限制,优点包括:
- 自动缩进对齐,即:按Shift Enter可以换行,输入 ) 会自动按照合理的缩进对齐。
- 智能提示,在某处能用哪些函数会列出。(当想用没有智能列出的函数时,只能说明这里不能用该函数,一定有某种错误,请仔细检查)这个功能是很强的。
- 支持 Tab 进行缩进以及 Shift Tab 进行负缩进。
问题包括:
- 可以输入的范围太有限
- 中文的提示有乱码
例如:
弥补这些问题,可以使用DAX Studio,如下:
仔细看看这个结构,只能用一个字来形容:美。(如果可以欣赏到的话) 至此,就可以用一个度量值处理关于 销售同比增长率 的所有问题了。与此类似,可以进行扩展到其他指标,读者可自行实践。
总计行的产生
一般情况下,在原始数据是不会有总计行这个行的,本案例在数据准备阶段使用查询编辑优化了这点,如下:
进入到 Power BI 数据模型以后,我们一般都遵循无侵入式设计原则。(见此前文章)通过强大的度量值体系来构建与业务指标对应的结构。
总结
《用1个度量值代替100个 实现 动态多维度动态算法动态总计》,分为了上下两篇。上篇重点描述了总计行与全部;下篇则重点描述如何实现全动态的度量值来提升可复用。在实际中,仍然涉及远超过文章中记录的复杂细节。让我们不仅想到:Power BI , Simple but not easy.
在结束时,百度了下重构:重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。可以看出,PowerBI可以拿来做做图,也可以打造复杂而优美的系统。如果有人说:这两篇充分看出PowerBI是程序员做的。非也,即使如此,程序员也是必要非充分条件。只是,创造某种美的过程,有时必须按照严谨的逻辑去探索美背后的规律以及用有限的智力来看懂这种美。