BOSS:那个,白茶啊,这个报表刷新有点慢啊,你看,每次我点筛选或者刷新都会让我等很久。
白茶:(认真脸)BOSS,那您再等会就好!

BOSS:白茶!!两小时了!!还没出来!!
白茶:(思考)老板,这个有点难啊,这个问题技术要求比较高。
BOSS:加钱!!!!
白茶:好嘞!

一张好的报表是如何界定的?DAX计算无误、前端展现明了、业务思路清晰、报表响应速度,白茶觉得这些因素就可以界定一张好的报表。

本期我们来聊一聊PowerBI中DAX函数性能优化的问题。

毕竟一张可视化报表需要15分钟刷新才能呈现出来,这对用户来说太不友好了。

先来看看本期的示例文件:

一张产品维度表,一张销售明细表。

需求是什么?

这张是销售明细表中的分店维度信息,为了便于小伙伴理解,白茶单独整理出来。

这张表是需求的计算逻辑图。什么意思呢?就是当Key小于15时,计算每个Key对应的分店,当Key大于14时,根据计算逻辑对不同的分店进行汇总计算。

编写基础的DAX计算代码:

SalesAmount =
SUMX ( 'Fact_SalesDetail', [Quantity] * RELATED ( Dim_Product[SalesAmount] ) )

在不考虑性能的情况下,DAX计算逻辑如下:

SalesAmountByDisplay =
SUMX ('Dim_DisplayDepartment',SWITCH (TRUE (),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 1,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 1 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 2,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 2 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 3,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 3 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 4,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 4 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 5,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 5 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 6,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 6 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 7,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 7 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 8,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 8 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 9,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 9 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 10,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 10 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 11,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 11 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 12,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 12 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 13,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 13 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 14,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 14 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 15,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 13 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 14 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 16,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 1 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 7 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 9 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 17,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 1 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 2 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 3 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 11 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 18,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 9 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 11 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 12 ))- CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 2 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 19,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 4 ))+ CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 7 )),SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] ) = 20, CALCULATE ( [SalesAmount], ALLSELECTED ( Fact_SalesDetail ) ))
)

相信不用白茶多说,小伙伴也能看出来代码的问题,太长了。

这段代码功能基本上是实现了,问题点有哪些呢?

1.SELECTEDVALUE复用度较高,可以使用变量代替

2.多个条件汇总迭代次数较多,可以使用提供List

这也是小伙伴常见的问题,如果DAX的构建可以绕开这两个问题,那么性能会有很大的提升。

简化版写法:

SalesAmountByDisplay2 =
VAR CurrentDepartmentKey =SELECTEDVALUE ( Dim_DisplayDepartment[DepartmentKey] )
VAR CurrentDepartment =TREATAS (VALUES ( Dim_DisplayDepartment[DepartmentKey] ),Fact_SalesDetail[DepartmentKey])
VAR Results =SWITCH (TRUE (),CurrentDepartmentKey = 15,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 13, 14 } )),CurrentDepartmentKey = 16,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 1, 7, 9 } )),CurrentDepartmentKey = 17,CALCULATE ([SalesAmount],FILTER ('Fact_SalesDetail','Fact_SalesDetail'[DepartmentKey] IN { 1, 2, 3, 11 })),CurrentDepartmentKey = 18,CALCULATE ([SalesAmount],FILTER ('Fact_SalesDetail','Fact_SalesDetail'[DepartmentKey] IN { 9, 11, 12 }))- CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 2 )),CurrentDepartmentKey = 19,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 4, 7 } )),CurrentDepartmentKey = 20, CALCULATE ( [SalesAmount], ALLSELECTED ( Fact_SalesDetail ) ),CALCULATE ( [SalesAmount], CurrentDepartment ))
RETURNResults

相较于之前的写法,这个写法通过VAR定义变量,和使用TREATAS来减少代码计算逻辑的书写。

通过提供List来减少迭代遍历的次数。

那么有没有继续可以优化的空间?有的。

优化写法:

SalesAmountByDisplay3 =
VAR CurrentDetail =ADDCOLUMNS (DISTINCT ( 'Dim_DisplayDepartment' ),"@CurrentValue",VAR CurrentDepartmentKey = 'Dim_DisplayDepartment'[DepartmentKey]RETURNSWITCH (TRUE (),CurrentDepartmentKey = 15,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 13, 14 } )),CurrentDepartmentKey = 16,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 1, 7, 9 } )),CurrentDepartmentKey = 17,CALCULATE ([SalesAmount],FILTER ('Fact_SalesDetail','Fact_SalesDetail'[DepartmentKey] IN { 1, 2, 3, 11 })),CurrentDepartmentKey = 18,CALCULATE ([SalesAmount],FILTER ('Fact_SalesDetail','Fact_SalesDetail'[DepartmentKey] IN { 9, 11, 12 }))- CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] = 2 )),CurrentDepartmentKey = 19,CALCULATE ([SalesAmount],FILTER ( 'Fact_SalesDetail', 'Fact_SalesDetail'[DepartmentKey] IN { 4, 7 } )),CurrentDepartmentKey = 20, CALCULATE ( [SalesAmount], ALLSELECTED ( Fact_SalesDetail ) ),CALCULATE ([SalesAmount],'Fact_SalesDetail'[DepartmentKey] = CurrentDepartmentKey)))
RETURNSUMX ( CurrentDetail, [@CurrentValue] )

这种写法,相较于上一种书写量多一些,通过定义虚拟表来减少迭代遍历的次数。从理论上来说,因为定义了虚拟表,无论源代码需要对事实表迭代多少次,这个思路迭代的永远都是虚拟表,优化度很高。

我们来对比一下:

DAX Studio测试:

三种写法的差距很明显。其实这里有一些争议的,简化写法是通过减少资源占用来实现优化,优化写法是通过减少迭代遍历实现优化。

从测试结果上来看,是简化写法优化度较高,但是在实际应用中,测试发现优化写法的方式响应更迅速。

你以为到这里就结束了么?

其实还有第四种优化的思路,只不过这个思路比较难。

SalesAmountByDisplay4 =
SUMX (VALUES ( 'Dim_DisplayDepartment'[DepartmentKey] ),VAR CurDpmKey = 'Dim_DisplayDepartment'[DepartmentKey]VAR TempTable =FILTER (ALL ( 'Dim_DisplayDepartment'[DepartmentKey] ),'Dim_DisplayDepartment'[DepartmentKey] <= 14)VAR AllDetail =ADDCOLUMNS (TempTable,"SalesAmount",VAR CurrentDepartment = 'Dim_DisplayDepartment'[DepartmentKey]RETURNCALCULATE ([SalesAmount],ALL ( 'Dim_DisplayDepartment' ),'Fact_SalesDetail'[DepartmentKey] = CurrentDepartment))VAR FilterContent =CALCULATE (MAX ( 'Dim_ComputationalLogic'[FilterContent] ),ALL ( Dim_DisplayDepartment ),'Dim_ComputationalLogic'[DepartmentKey] = CurDpmKey)VAR Length =LEN ( FilterContent )VAR FilterTable =SELECTCOLUMNS (GENERATESERIES ( 1, Length, 4 ),"DepmKey", MID ( FilterContent, [Value], 3 ) * 1)VAR Result =SUMX (FilterTable,VAR DpmKey = [DepmKey]VAR SalesValue =SUMMARIZE (FILTER ( AllDetail, 'Dim_DisplayDepartment'[DepartmentKey] = ABS ( DpmKey ) ),[SalesAmount])RETURNIF ( DpmKey >= 0, SalesValue, - SalesValue ))RETURNResult
)
--作者:夕枫

这个优化的思路,是**@夕枫**大佬提出来的。通过定义计算表,减少代码书写量,使用ALL减少上下文转换的消耗,减少查询次数,命中缓存。

DAX Studio测试:

总结一下:

1.可以通过变量和定义表来减少代码书写量

2.可以通过减少资源调用优化

3.可以通过虚拟表减少迭代遍历

4.可以通过命中缓存进行优化

比较常用的是前三种,第四种难度系数较高。

往期推荐:

《精品丨CALCULATE进阶》

《精品丨上下文扩展》

《精品丨PowerBI内嵌分页报表》

《精品丨扩展表理论》

小伙伴们❤GET了么?

(白茶:别问我第四种,我不会TAT)

这里是白茶,一个PowerBI的初学者。

精品丨DAX性能优化问题相关推荐

  1. 跨境茶话会8月期丨性能优化的艺术

    大师兄说 众所周知,对于现在国内的互联网环境,不管什么样的系统,一旦等用户的访问量上去之后,我们每增加一个功能实际上都是要考虑它的吞吐量和延迟,在加工上都是要做一个缜密的思考的.所以我相信在这方面许多 ...

  2. 今晚8点直播丨 经典知识库:性能优化那些事

    经典知识库:性能优化那些事-10月14日20:00 数据库性能问题,常常是困扰DBA高效运维的难题之一.如何多角度地帮助DBA,找到"数据库慢"的原因,保证系统高效.稳定.安全地运 ...

  3. 14日晚8点直播丨 经典知识库:性能优化那些事

    经典知识库:性能优化那些事-10月14日20:00 数据库性能问题,常常是困扰DBA高效运维的难题之一.如何多角度地帮助DBA,找到"数据库慢"的原因,保证系统高效.稳定.安全地运 ...

  4. 前端性能优化的三个维度

    前端性能优化可以分为三个level:静态资源优化.接口访问优化.页面渲染速度优化,在操控门槛上依次递增,优化效果上越发没有这么明显,所以很多小团队只会做到了第一个level 追求极致的前端性能体验,提 ...

  5. Android APP性能优化

    转载自:https://www.cnblogs.com/qwangxiao/p/8727229.html Android APP性能优化(最新总结) 导语 安卓大军浩浩荡荡,发展已近十个年头,技术优化 ...

  6. 也许90%的人都没有真正搞懂性能优化

    作为一个半吊子全栈工匠,在20多年的职业生涯里遇到过太多关于软件性能的问题.论证或者证明性能的问题往往很关键,能否通过一次一个小而有逻辑的可证明可审核的步骤来解决性能问题呢? 曾经企图创建一种公理化的 ...

  7. 硬件加速下webview切换闪屏_网页渲染性能优化 —— 性能优化下

    博客 有更多精品文章哟. Composite 的优化 终于,我们到了像素管道的末尾.对于这一部分的优化策略,我们可以从为什么需要 Composited Layer(Graphics Layer)来入手 ...

  8. mysql左连接_面试考MySQL性能优化,一个问题就干趴下了!

    MySQL作为最流行的关系型数据库管理系统,重要性不言而喻.面试时它也是重点考察对象之一,估计大家都有过被MySQL相关问题支配的经历: 如何理解MySQL中加锁原理以及最终死锁形成的原因 ? 介绍一 ...

  9. Android APP性能优化(一)

    Android APP性能优化(最新总结) 安卓大军浩浩荡荡,发展已近十个年头,技术优化日异月新,如今Android 8.0 Oreo 都发布了,Android系统性能已经非常流畅了.但是,到了各大厂 ...

最新文章

  1. iOS8.0 之后指纹解锁
  2. oracle数据库查看等待,Oracle常见等待事件说明(三)-enqueue/free buffer waits
  3. python怎么写文件-python头文件怎么写
  4. IDEA中SpringBoot项目使用@Data要安装Lombok插件
  5. oracle cronb,利用Crontab实现对Oracle数据库的定时备份
  6. shell中的括号(小括号,中括号,大括号/花括号)
  7. 【C语言】用指针描述数组,实现选择法排序
  8. 怎么创建PHP函数,如何创建 PHP 函数
  9. torch将多个tensor张量合并为一个张量,只提高迷你批次的纬度
  10. mobiscroll 插件札记(一)
  11. Callback、Listener、Worker、Manager的命名说明
  12. lua 垃圾回收机制
  13. 关闭Linux 内存地址随机化机制
  14. u8应用服务器设置eai,用友U8+V15EAI用户使用手册.pdf
  15. 测试网速_使用Speedtest CLI测试你的网速
  16. c语言怎么输入加减符号,C语言有符号加减溢出
  17. TCP控制拥塞的四种算法:慢开始,拥塞避免,快重传,快恢复
  18. android环信接收透传,环信透传
  19. 深度学习项目,使用python进行表情识别,pytorch应用
  20. Eclipse小技巧--修改@auther和去掉//TODO

热门文章

  1. 收费企业邮箱有哪些?哪个收费邮箱最好
  2. ps制作视:_制作自己的PS4游戏:“梦想”入门
  3. 《一切都是最好的安排》——加措
  4. HEALTH_WARN 1 filesystem is degraded,一直在rejoin状态
  5. Balanced Array
  6. html发送邮件通过Mailto简单实现-web前端教程
  7. 人工智能ai的有关专业术语_您需要知道的11个人工智能术语
  8. 英语语法基础篇-书写规则
  9. [置顶]Gradle 实现 Android 多渠道定制化打包
  10. Flume使用Spooling Directory Source采集文件夹数据到hdfs