DAX设计模式(第2版)

978-7-115-57726-9
作者: 阿尔贝托•法拉里(Alberto Ferrari)马尔科•鲁索(Marco Russo)
译者: BI佐罗团队
编辑: 武晓燕

图书目录:

详情

Power BI 自 2015 年 7 月发布后,极大地改变了商业智能市场的格局,连续多年被评为商业智能产品的领导者。本书集合了 20 多套运用 Power BI 的 DAX 引擎来处理、分析常见商业问题的即用型解决方案。 DAX 设计模式是由 BI 领域专家——阿尔贝托·法拉里和马尔科·鲁索总结并提出的。本书同时使用 Power BI 和 Excel 作为工具进行编写,并对应用模式进行了大幅更新,内容涵盖了时间智能、分组、ABC 分类、客户分析、购物篮分析等常用模式。本书所包含的每一套设计模式都经过不断的实践,被提炼为易用的数据模型和优雅的 DAX 公式。 本书适合 Excel 高级用户、商业智能分析人员、使用 DAX 和微软分析工具的专业人士阅读。

图书摘要

版权信息

书名:DAX设计模式(第2版)

ISBN:978-7-115-57726-9

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [意] 阿尔贝托•法拉里(Alberto Ferrari)

     [意] 马尔科•鲁索(Marco Russo)

译    BI佐罗团队

责任编辑 武晓燕

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e57726”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


Copyright ©SQLBI Corp. 2022.

First published in the English language under the title ‘DAX Patterns second edition – (9781735365206)’.

本书中文简体版由SQLBI Corp.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。


Power BI自2015年7月发布后,极大地改变了商业智能市场的格局,连续多年被评为商业智能产品的领导者。本书集合了20多套运用Power BI的DAX引擎来处理、分析常见商业问题的即用型解决方案。

DAX 设计模式是由BI领域专家——阿尔贝托•法拉里和马尔科•鲁索总结并提出的。本书同时使用Power BI和Excel作为工具进行编写,并对应用模式进行了大幅更新,内容涵盖了时间智能、分组、ABC分类、客户分析、购物篮分析等常用模式。本书所包含的每一套设计模式都经过不断的实践,被提炼为易用的数据模型和优雅的DAX公式。

本书适合Excel高级用户、商业智能分析人员、使用DAX和微软分析工具的专业人士阅读。


2019年至2020年,我从一名Excel的成熟爱好者发展成为Power BI的初级爱好者,这是一件自然而然的事情。以我现在的水平,我还无法清晰且准确地将Power BI的神奇之处全部讲出来,但我相信随着Power BI功能的日益强大和应用范围的日益扩大,会有越来越多的人发现其中的奥妙。

Power BI的世界充满了诱惑力,于是我四处寻找可以掌握它的途径。寻寻觅觅中,我关注到罗叔(BI佐罗)的公众号“PowerBI战友联盟”。该公众号中有罗叔对Power BI的特有阐述、对DAX底层的特有解析和对Power BI应用场景的详细说明。

偶然间了解到罗叔有意翻译这本很棒的图书,我意识到这是一个将充满魅力的Power BI推荐给更多人的机会,并且,我可以系统地学习DAX的神奇之处。于是,我毛遂自荐,希望能成为译者团队的一分子。

感谢罗叔给我这样的机会,一方面可以让我深入地学习DAX,另一方面使我可以综合应用个人能力。

感谢翻译团队的小伙伴,他们中的每一位都是我学习的榜样,每一位都是优秀的协作者,每一位都是乐于助人的好老师。

感谢我的女儿。她看到深夜里、台灯下、写字台前码字的我,而写了一篇《妈妈,我想对您说》的作文。这篇作文流露了对我满满的关心和理解。希望我们这代人,可以为下一代提供更丰富、更优良的知识财富。

王薇(第1~5章译者)


Power BI不仅是一个工具,更是一套先进的数据分析方法论和驱动企业数字化管理的强大引擎。

在翻译本书的过程中,让我感受最深的是DAX在各种业务场景中的灵活运用,本书所涵盖的DAX模式能解决企业90%以上的复杂业务问题,这不得不让我再次惊叹DAX的伟大!

最后,我想对读者说,数字化时代已经到来,微软作为Gartner分析和BI平台魔力象限的领导者,用Power BI为我们个人赋予了上亿级数据的分析能力,当你真正接触这个恐怖的“外挂”后,你就会明白什么叫作“科学技术是第一生产力!”

周舟(第6~9章译者)


Power BI是我所遇到的最适合非IT科班出身却从事数据分析的人士使用的专业数据分析工具之一。它能够在不依靠 IT 的前提下,把复杂的业务指标和业务逻辑精准地在数据模型中展现,真正实现一个人、一台计算机、一天时间、玩转一亿数据,并进行个人商业分析。

DAX语言入门简单,融会贯通却不易,需用户不断地实践、领会、重读、再实践,如此往复才能真正学懂、学通。

本书正是运用优雅的DAX语言,将商业问题中使用极为广泛的多种业务模式完整透彻、条理清晰地展示在读者面前,既让读者领略了高级数据大师如何分析商业问题,又帮助读者进一步学习、巩固DAX语言。本书是Power BI领域非常好的集业务、技术于一体的佳作。

在翻译本书的过程中,书中的很多场景、模式给了我很大的启发,很多公式的写法也让我对DAX语言的认知有了新的提升,并且这些模式和公式也成功地运用在了我日常工作的分析模型中。

所以,对于想在Power BI领域深入学习,运用Power BI解决商业问题的小伙伴们,本书一定是你无悔的选择!愿越来越多的小伙伴能够加入Power BI的学习,我们共同勉励、共同成长进步!

郑志刚(第10~14章译者)


我和DAX邂逅于2015年,那一年恰逢Power BI诞生。彼时的我正在用Excel Power Pivot笨拙地摸索数据建模并苦于不谙DAX原理而匍匐前行。在辗转学习了不同的BI工具,走了不少的弯路后,作为非技术出身的业务人员,我最终被Power BI所折服。在这条学习的道路上,有幸跟随SQLBI的两位意大利专家(本书作者)和BI佐罗不断学习探究DAX的原理和应用,借助Power BI洞察业务的同时一窥自助式商务智能的魅力。

成为优秀的DAX开发人员将会是一段奇妙之旅,从按业务场景搭建数据模型、理解DAX基本原理、撰写正确的度量值到驾驭复杂的动态解决方案,每一个里程碑都会让你感到无比欣喜。本书即是这段旅途的必备之选,它吸取了作者的两本著作Analyzing Data with Microsoft Power BI and Power Pivot for Excel(中译名《Power BI建模权威指南》)和The Definitive Guide to DAX(中译名《DAX权威指南》)的精华,用20多套经典商业数据分析场景下的数据模型以及解决通用业务需求的高级DAX设计模式令你大开眼界。

本书收录了作者从多年从事咨询工作和教授DAX解决方案经验中提炼出的最佳实践,提供给读者站在巨人肩膀上登高望远的契机。书中的每一种模式无不凝聚了细致的构建,缜密的逻辑,精妙的算法和丰富的创造力。除此以外,作者孜孜不倦地优化DAX方案性能,愈发让这一整套设计模式精益求精。在翻译的过程中,我再一次领略了DAX之美,感受到它的威力是无穷的。更难能可贵的是我结识了BI佐罗翻译团队中来自各行各业的优秀伙伴,承蒙诸位一路的帮助和相伴,让本书在如此短的时间里与中文读者见面!

“利不百,不变法;功不十,不易器。”Power BI以其强大的建模分析功能在数据时代极大地改变了不计其数的企业和个人通过数据快速获得见解的方式。今天它才6岁,仍然在茁壮地成长,它未来的前景会更为开阔。而创造这个盛世的人群里,一定少不了你,请相信过程。

在此,还要特别感谢我的太太和女儿小西瓜,谢谢你们在整个过程中给予的无条件支持,让我能够将业余时间投入翻译工作。你们的陪伴是本书中文版面世的极大贡献。

陆文捷(本书介绍及第15~19章译者)


在过去(更准确地说是在2015年7月以前),一个不会写代码的业务人员想做些分析,除了使用Excel,似乎没有太多选择。他可能先开个会,向IT伙伴说明自己头脑中还不太确定的想法;接下来耐心配合内部流程的步调,被动地等待项目推进;中间还隐藏着客户或老板提出来的大幅度更改需求而导致分析方向完全转弯的风险。

当他有机会认识了Power BI,才发现:啊!原来我也可以独立完成复杂的分析。这样一来,他很难不被DAX所吸引。他会学习这个不同于以往思维习惯的语言,尽可能地去应用它,遇到瓶颈就想知道更多方法,并提出各式各样的问题……

本书就是在这样热切的学习氛围中逐渐成形的。

两位意大利老师总结了他们历年来在咨询与培训中被无数DAX学习者反复询问的问题,归纳出目前可知的最佳解。不同行业与职能的分析人士参考借鉴这些最佳解,并对其加以适当调整,就能做出立即可用的最小可行产品(Minimum Viable Product,MVP)。有了MVP,他们就可以为自己的想法提供初步的验证,与利害关系人进行沟通,甚至将其直接作为启动后续大型项目的原型。

这实际上是一个令人振奋的过程:带着对业务的深刻理解,我们思考真正的需求,找寻各种可能的解法,提取数据的最大价值。书中介绍的 20 多个模式,加速了整个过程,让我们得以在坚实的基础上深入发挥所学。

本书的英文版帮助我掌握了在数据分析时会遇到的关键场景,也让我深深感受到DAX语言的魅力。参与本书翻译工作,如果能让更多中文读者体会到运用DAX的乐趣和成就感,那真是一件让我感到非常开心的事情!

你可以轻松地翻开本书,先大致浏览目录,再挑选那些吸引你的章节开始阅读。

欢迎各位与我们一起学习!

蔡至洁(第20~24章译者)


在SQLBI,我们有着一份不错的工作:我们是全球范围的培训师和顾问,每年都会结识来自世界各地成千上万的人—— 一群非同寻常、对商业智能和DAX怀揣热忱的人。我们的学员和客户会询问各种复杂场景下的解决方案。

如果有学员因为要计算他们报告中新客户的数量而寻求帮助,你一而再、再而三地解决了这样的问题,那么总会在某个时刻,你会觉得下一次又要回答相同的问题时,如果有一个现成的解决方案就太好了。这就是我们于2013年开始建设DAXPATTERNS网站的原因。我们自此开始收集常用的模式,并创建了一套DAX公式,旨在解决常见的问题。当时的目标并不是写一本新书。相反,我们的目标是建立某种记忆库,以便搜寻解决方案。我们原以为自己会是这个网站的主要用户。

计划往往赶不上变化。而这次,我们是更上了一层楼。DAXPATTERNS网站取得了巨大的成功。用户下载了示例文件并实现了两个不同的目标:他们找到了现成的解决方案,并根据我们编写的公式提高了他们的DAX技能。为了满足更多用户的需求,我们提供了用于Excel 2010和Excel 2013的示例文件——后者仍然适用于更高版本的Excel。最终,我们把网站的内容整理成一本书。这就是DAX Patterns第1版,那是发生在2015年年底的事。当时,我们还没有出版Definitive Guide to DAX的第1版。因此,我们在这一版的DAX Patterns中包含了对DAX的简短介绍。

在接下来的5年里,许多事情发生了变化。DAX拓展了许多有用的特性。最重要的是Power BI面世了,使用DAX的用户数量呈指数级增长。如今大多数DAX用户使用Power BI创建解决方案。而在我们出版本书的第1版时,Power BI甚至还没有发布。

在这5年里,模式收集这件事仍在继续。我们遇到了更多的学员,解决了更多的问题,我们的DAX技能也愈发精进。另外,我们现在有成千上万的用户能够对以前的模式提供反馈。通过研究用户评论,我们可以更好地了解读者的需求。同时,我们还出版了Definitive Guide to DAX [1]。所以,我们已经没有理由在一本关于设计模式的书中教授DAX了。

长话短说,建立全新的DAX设计模式网站和编写一本新书是很有意义的,因此我们撸起袖子加油干,完成了这本你正在阅读的图书。

我们没有采用第1版的任何内容。我们想要一个新的开始。所有代码都是从头开始编写的,本书使用了最新的[2]DAX和Power BI特性,并在必要时将代码和Excel 2019做了适配。

[1] 本书作者撰写的介绍DAX的著作的中文译本《DAX权威指南(第2版)》已上市。——译者注

[2] 截至2020年6月。——译者注

在新版中,我们做了一些改变。

增加了大量的关于时间智能计算的内容。时间智能是迄今为止研究最广泛的主题。因此,增加与时间相关的计算和模式的数量是有意义的。

同样,新客户和回头客户模式也相当重要。我们在书中对该模式着墨较多,并增加了计算新客户和回头客户的公式和模型的数量。

增加了模式的总数,根据经验,添加了一些可能对读者有用的模式。

(相较第1版)我们决定去掉一些模式。例如,有关统计计算的内容在2015年还有用,因为那时DAX缺少统计功能。自此以后,DAX引入了许多新函数来计算与统计相关的公式。到2020年就不需要这些内容了。

不再提供代码片段。第1版图书展示了大量的代码,包括读者可能更改的列的占位符。我们不再这么做了。我们展示了有效的代码,因为你经常需要调整数据模型和公式中的其他细节。我们认为这将使代码更具可读性,更易于使用和适配你的模型。

对每一个公式都做了优化。你在模式中看到的所有代码都经过了仔细的性能检查。这并不意味着这些模式就是最好的,它们仅是我们能想到的最优的。如果你能使代码执行得更好、更快,请让我们知道!可以在DAXPATTERNS网站的评论区提供反馈。

我们为每个示例文件都创建了Power BI和Excel的版本。在本书中,我们提供了Power BI报告的截图,其显示了代码运行的结果,但是在供你下载的示例文件中,Power BI和Excel版本都有。

你在这本书里会发现什么?每一章都包含独立的模式,你可以在没有阅读其他章的情况下直接阅读任意一章。例如,你可以在不曾阅读过第22章或任何与时间相关的计算的章节的情况下直接阅读第23章。

与设计模式相关的章节以对应的业务场景的简述开始,然后详述解决方案并给出为解决业务场景而部署的所有DAX代码。我们保留了简短的代码描述,必要时在代码中添加了注释(注释内容以“--”开头)来解释度量值的含义。

本书内容需和示例文件配套使用。你可以从异步社区下载Power BI和Excel版本的示例文件。

本书希望成为读者的工具参考书。当你想要实现一个模式时,你不会想去阅读冗长的描述:你希望看到代码及其结果。因此,我们尽可能地保持内容的紧凑,将焦点放在DAX代码上。

也就是说,如果你想实现一个模式,我们强烈建议你在实施任何代码前阅读整个章节。原因是我们有时会为某些场景提供多种解决方案,你需要从中选择最符合你特定场景需求的代码。对于每个模式,我们还提供了Power BI和Power Pivot for Excel中的示例文件。有时两个版本中的代码略有差异。本书始终采用Power BI的解决方案,并使用了截至发布时DAX的最新特性。其中一些功能在Power Pivot中是不可用的,比如计算表,这也是造成两者差异的主要原因。

有一个例外:时间相关的计算。正如先前介绍的,本书中与时间相关的计算篇幅最大:有4种不同的模式(第2~5章)。这4种模式都有不少内容,占据了本书篇幅的40%之多。因此,我们专为时间相关的计算创建了一个介绍性章节,旨在帮助你为场景选择正确的模式。如果你需要实现与时间相关的计算,请先阅读第1章,然后阅读你决定使用的模式的完整章节。

给读者的一个忠告:本书不是教你如何使用DAX的。

你应该已经了解DAX并可以充分利用这些模式。大多数模式使用了进阶的DAX技术,欢迎学习并在你的解决方案中运用。本书并不会帮助你从基础开始学习DAX。但如果你已经了解DAX,那么你可能会成为一名更好的DAX开发人员。

我们建议你在最新版本的Power BI或Excel中使用这些模式,因为DAX会随着时间的推移而不断发展和改进。我们在2020年6月发布的Power BI版本、Excel 2019和Excel for Microsoft 365 version 2006上测试了这些模式。大多数模式适用于Power BI和Excel的早期版本,但我们不能保证这一点,因为我们没有对所有早期版本进行完整的测试。

我们最要感谢的是你。这项工作是由我们与读者、用户、客户和像你这样的学员的长期讨论所促成的。因此,即使还未得知你所做的贡献也要说声感谢。如果你在我们的公共论坛上发表评论,那么你的贡献度会更进一步。

另外,还有一些伙伴在本书的整个写作过程中做出了直接贡献:丹尼尔·马斯柳克(Danil Maslyuk)仔细核对了每一种模式,找到其中所有的错误并提供了宝贵的反馈意见;克莱尔·科斯塔(Claire Costa)校对了我们的英语语法和可读性[3],使这本书更准确和易于阅读;塞尔焦·穆鲁(Sergio Murru)构建了示例文件的Excel版本,使得这些模式也适用于Power Pivot for Excel的用户;达妮埃莱·佩里利(Daniele Perilli)是本书和DAXPATTERNS网站能够如此美观的幕后功臣。我们对本书内容和出现的任何错误负责,但如果你能通过英语,在Excel和Power BI华丽的整体呈现中阅读到精确的数字,那就要感谢他们了。

尽情享受DAX吧!

[3] 原书为英文版,两位作者是意大利人。——译者注


本书由异步社区出品,社区(https://www.epubit.com)为您提供相关资源和后续服务。

您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e57726”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。

本书提供配套示例文件。要获得配套资源,请在异步社区本书页面中单击 ,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

扫描下方二维码,您将会在异步社区微信服务号中看到本书信息及相关的服务提示。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/ selfpublish/submission即可)。

如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


本章介绍4种与时间相关的计算模式。其目的是帮助你根据自己的特定需求选择正确的模式。确实,在进行时间相关的计算时,模式的选择是个难题。

首先,什么是与时间相关的计算呢?与时间相关的计算是指任何涉及时间的计算。比如期初至今(年初至今、季初至今或月初至今)相关的一组计算。这些计算从一个时间段(年、季度、月)开始并返回自该时间段开始到报告中所示日期的度量值的聚合。时间段的定义会根据你使用的是公历日历还是会计日历而存在差异。在图1-1中,你可以看到一个期初至今计算的示例,其中,YTD代表年初至今、QTD代表季初至今。

图1-1 期初至今计算的示例

这些模式还包括将特定时间段内的参数和另一时间段内的参数进行比较。例如,你可以将当月的销售额与去年同月的销售额进行比较。与时间相关的计算的另一个示例是一段时期内的移动平均,例如,12个月的移动平均可以让折线图平滑并消除对计算结果的季节性影响。4个与时间相关的模式会执行相同的一组计算。

对日历的不同定义会使模式产生不同的结果。通过查看图1-1,你已经可以理解年初至今计算的不同定义。

根据使用的是公历日历还是会计日历,日期的数值会有差异。由于对日历的定义不同,事情处理起来很容易变得非常复杂。

例如,你可能有遵循ISO标准或你自己定义的基于周的日历。在基于周的日历中,每个月从一周的同一天开始,这一年也是如此。因此,基于周的日历中的一年可能在上一个公历年开始,也可能在下一个公历年结束。此外,出于会计目的,某些日历会将一年分为13个时间段,而不是12个月。不同的日历需求是导致对与时间相关的模式进行选择的主要原因。

4个与时间相关的模式(依复杂程度递增的排序)如下所示:

标准时间相关的计算;

与月相关的计算;

与周相关的计算;

自定义时间相关的计算;

标准时间相关的计算模式利用常规的DAX时间智能函数来实现。该模式的实现基于你的日历是常规公历日历,并且你的会计日历起始于公历季度。例如,如果你的会计日历从7月1日(公历日历第三季度的开始日期)开始,则DAX时间智能函数可以正常工作。但是,如果你的会计日历从3月1日开始,则可能会出现出乎意料的计算结果。这既因为3月不是一个公历季度的开始,又因为会计日历在处理闰年方面的历史缺陷。尽管有这些限制,该模式还是易于使用和执行的,因为它依赖于标准DAX函数,并且可以与常规日期表配合使用,几乎没有要求。

其他的3个模式不使用DAX时间智能函数计算。它们使用基本的DAX函数编写,这就给以季度、月份和周定义的日历提供了更大的灵活性。这些模式需要你创建一个Date表,DAX度量值需要通过该表中的列来识别该年度的时间段。例如,你需要一个包含年份的列、一个包含季度的列、一个包含月份的列,以及其他用于简化计算的列。

此外,在检测和筛选时间段时需要考虑许多细节。事实证明,许多看起来对于人类而言很容易的计算,而对于计算机就非常复杂。当你将一个季度与上一个季度进行比较时,你需要为两个季度选择不同的天数:1月至3月的季度比4月至6月的季度短。月份也是如此:1月比2月长,但是如果要逐月进行比较,则你所需要的两个日期选择会具有不同的长度。

如果标准时间智能函数不能满足你的需求,则你需要执行其他3种模式中的一种。这3种模式都需要创建你自己的Date表。

与月相关的计算模式是最简单的。该模式所执行的所有计算均假设你并不在意日常细节。例如,如果你需要生成两个月份之间进行比较后的计算和报告,该模式非常适用。该模式不支持选择子月份。如果你要比较一个季度中的3天与上一季度中相同的3天,则超出了这个模式的能力:它不起作用。尽管其分析能力存在很大局限性(仅限于月粒度),但与月相关的模式的分析速度仍非常快速且易于执行。此外,对于数据超过12个月的情况,它也可以无缝处理。它具有定制模式的灵活性,并且比标准时间相关模式简单。如果月粒度这个局限不会影响你的计算,那么非常建议你选择使用这个模式。

在与周相关的计算模式中,星期是日历的基础。尽管很多国家采用不同的国家标准来标识年份、季度和星期,但ISO 8601是提供星期日期系统定义的标准之一。一年有52或53个星期,每个季度有13个星期,每个季度又分为5+4+4个星期、4+5+4个星期或4+4+5个星期。如果一年中有53个星期,则其中一个季度有14个星期。因为一个星期不一定完整地包含在一个月份中,所以应将一个季度中的一组星期称为“时间段”,虽然我们通常称之为“月份”。因此,在以下描述中,我们会将月份名称称为“时间段”。

由于星期是主要单位,因此公历日历中的一年与基于周的日历中的一年之间没有对应关系。基于周的日历始终在相同的星期几开始,例如星期一或星期日。因此,偶尔才会在1月1日发生这种情况。对于基于周的年,一年起始于上一年的12月29日,还是当前年份的1月3日,都没有关系。尽管有些不同,但是基于周的日历具有显著的特点:一个季度中的每个“月”都包含相同数量的星期几。将一个季度与另一个季度进行比较,意味着比较相同的天数和相同的星期几。

基于周的日历需要一个专用的Date表。该表包含进行DAX计算的若干列。此外,由于没有现成的可用于对基于周的日历进行计算的DAX函数,所以使用自定义DAX代码来执行此类计算。与周相关的模式的复杂程度要高于与月相关的模式,因为与周相关的模式可让你实现筛选任何时间段,直到“天”的级别。如果你使用基于周的日历,则必须采用基于周的计算模式。

自定义时间相关的计算模式是最灵活(且最复杂)的。作为最后一个模式,它提供与标准时间相关模式相同的计算。它的特点在于整个模式是使用基本的DAX函数编写的,而不使用任何DAX时间智能函数。该模式极为灵活,因为你可以随意变动计算的执行方式。当然,更高的灵活性也会带来更高的复杂性。

那么,你应该选择哪种模式呢?

如果常规的公历日历能满足你的需求,那么显而易见,选择标准时间相关的计算模式。

如果月粒度足以满足你的报告需求(通常是这种情况,而且比预期的要多得多),那么与月相关的计算模式就是最佳选择,因为它快速而简便。

如果你使用基于周的日历,则需要与周相关的计算模式。

如果以上情况均不足以满足需求,并且你确实需要完全的灵活性,请准备加入一场漫长而有趣的旅程,深入了解复杂的筛选上下文,然后直接进入自定义时间相关的计算模式。

切记:对于商业智能项目,越简单越好。选择最能直接满足你需求的模式。不用多说,如果你对各种模式之间的差异感到好奇,那么在做出选择之前快速通读所有章节可能会对你很有帮助。

微信扫码关注【异步社区】微信公众号,回复“e57726”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


在此模式中,我们向你展示如何使用标准日历来实现与时间相关的计算,例如年初至今、上年同期,以及百分比增长。标准日历的最大优势是,你可以依赖几个内置的时间智能函数。内置函数可以为常见的需求提供正确的结果。

如果内置函数无法满足你的需求,或者如果你使用的是非标准日历,则可以使用常规的(与时间无关的)DAX函数来达到相同的目的。以这种方式,你可以控制代码运算的结果。也就是说,如果你需要自定义计算,则还需要使用DAX公式来操作筛选器所需的一组列以丰富日期表。这些自定义计算包含在自定义时间相关的计算模式中。

如果你使用常规的公历日历,则此模式中的公式是生成时间智能计算的最简单、最有效的方法。请记住,标准的DAX时间智能函数仅支持常规的公历日历——由12个月组成、每个月都有其公历天数、3个月组成一个季度、具备人们普遍使用的所有常规日历特征的日历。

为了使用所有时间智能计算,你需要一个格式正确的日期表(Date表)。该Date表必须满足以下要求。

必须具备年份中的所有日期。该Date表必须始终始于1月1日、终于12月31日,并包括此日期范围内的所有天。如果报告仅采用会计年度,则Date表必须包含从会计年度的第一天到最后一天的所有日期。例如,如果2008会计年度始于2007年7月1日,则Date表必须包括从2007年7月1日到2008年6月30日的所有天。

必须具有包含数据类型为“日期/时间”或“日期”的不重复值的列。该列通常命名为Date。尽管Date列通常用于定义与其他表的关系,但这不是强制的。不过,Date列必须包含不重复值,并应为其应用“标记为日期表”功能。如果该列还包含时间部分,则不应使用时间——例如,时间应始终为12:00 am。

模式中的Date表必须标记为Date表,以防没有基于Date而与其他表(如示例中的Sales)建立联系。[1]

[1] 对于符合Date表要求且位于一对多关系一端的Date表,不将其标记为Date表,它也会自动具备Date表的特性。——译者注

创建Date表的方法有以下几种。只要Date表满足要求,创建Date表的方式就不会影响你使用标准时间智能计算。如果你已经有一份符合报告需要的Date表,则只需在确保该表可以满足最低要求后,便可将其导入并将其标记为Date表。如果没有Date表,则可以使用DAX计算表来创建(后面会做介绍)。

最佳做法是对用于时间智能计算的Date表应用“标记为日期表”设置。“标记为日期表”设置会在每次对Date列应用筛选器时,在Date表上增加一个REMOVEFILTERS修饰符。用于CALCULATE的所有时间智能函数都会执行此操作(对Date列应用筛选器)。如果你使用Date列定义Sales表和Date表之间的关系,则DAX会执行相同的操作。尽管如此,将“标记为日期表”设置应用于Date表依然是最佳做法。如果你有多个Date表,则可以将它们全部标记为日期表。

如果你没有采用“标记为日期表”设置,而且也没有使用Date列来建立关系,那么每当你在CALCULATE中使用时间智能函数时,都必须在Date表上增加一个REMOVEFILTERS。SQLBI官网中的“Time Intelligence in Power BI Desktop”一文对此进行了详细描述。

标准时间智能函数是表函数,该函数返回在CALCULATE中用作筛选器的日期列表。通过编写更复杂的筛选器表达式可以获得时间智能函数的结果。例如,DATESYTD函数可以返回筛选上下文中从显示的日期所在年份的第一天到显示的最后一天之间的所有日期。下方的表达式:

DATESYTD ( 'Date'[Date] )

对应下方的FILTER表达式:

VAR LastDateAvailable = MAX ( 'Date'[Date] )
VAR FirstJanuaryOfLastDate = DATE ( YEAR ( LastDateAvailable ), 1, 1 )
RETURN
    FILTER (
        ALL ( 'Date'[Date] ),
        AND (
            'Date'[Date] >= FirstJanuaryOfLastDate,
            'Date'[Date] <= LastDateAvailable
        )
    )

时间智能函数有很多,大多数时间智能函数是以这种方式呈现的。请注意:时间智能函数应用作CALCULATE的筛选器参数,有时你会通过使用变量来实现这一点。在迭代器中使用时间智能函数是很危险的,因为会触发隐式上下文转换,从而导致从筛选上下文中检索有效日期。DAX指南文档中提供了更多详细信息。

以下是使用时间智能函数时最佳做法的快速指南。

仅在CALCULATE / CALCULATETABLE的筛选器参数中使用诸如DATESYTD之类的时间智能函数,或给变量分配筛选器。

在返回值的DAX公式中使用EDATE和EOMONTH之类的标量函数(也称为标量表达式)。这些函数不是时间智能函数,可以用于以行上下文执行的表达式中。

使用CONVERT将日期转换为数字,反之亦然。

有关时间智能函数的完整最新列表,请访问DAX GUIDE网站。

DAX初学者经常将时间智能函数与常规(标量)时间函数混淆。这种混淆导致出现常见错误,可以通过遵循以下建议来避免。

不要使用DATEADD来返回前一天或后一天。可以使用简单的数学运算符来做到这一点。

不要使用PREVIOUSDAY来计算标量表达式中的前一天。从日期中减1,即可获得标量表达式中的前一天。

不要将EOMONTH用作筛选器,而应使用ENDOFMONTH。EOMONTH是标量表达式。ENDOFMONTH是时间智能函数。请始终注意函数的返回类型:只有表函数是时间智能函数,因此不应当用于标量表达式中。

Power BI可以自动将Date表添加到数据模型。但是,强烈建议禁用Power BI自动创建Date表的功能,而采用导入或用户自行创建的方式生成显式Date表。更多详细信息,请参见SQLBI官网中的文章“Automatic Time Intelligence in Power BI”。

Power BI自动创建的Date表还会启用一种特定语法——列变化。列变化的表达方式会在Date列的后面跟着一个点,点的后面跟着自动创建的Date表的列:

Sales[Order Date].[Date]

Power BI快速度量值用于其自动创建的Date表时,会大量使用列变化。我们不要依赖Power BI自动创建的Date表,因为我们希望保持最大的灵活性和对模型的最大控制。列变化的语法不适用于作为模型一部分的Date表,因此不会自动创建。

标准时间智能函数可以在常规的公历日历上使用。本节会列出它们的几个局限性。当你的需求与这些局限性不兼容时,你需要采用其他模式(请参见第4章和第5章)。

年度起始于1月1日。对于从不同日期开始的会计日历,该函数起不到太大作用。但是,由于会计日历在处理闰年方面的时间处理缺陷,所以每个会计年度的第一天必须是相同的,且不能是3月1日。

季度总是起始于1月、4月、7月和10月的第一天。季度的范围不能修改。

月份始终是一个公历月。

标准时间智能函数可能无法正确地支持其他列的筛选器,例如Day of Week或Working Day。有关可能的解决方法的更多详细信息,请参见2.8节。

因此,标准时间智能计算不支持许多高阶计算,如,对数周的计算。这些高阶计算需要自定义日历。

DAX时间智能函数可在任何标准公历日历表上使用。如果你已经有一个Date表,则可以将其导入并使用,而不会出现任何问题。如果没有Date表,则可以使用DAX计算表来创建。例如,以下DAX表达式定义了本章中使用的简单Date表。

计算表

Date = 
VAR FirstFiscalMonth = 7    -- 会计年度的第一个月
VAR FirstDayOfWeek = 0      -- 0 = 周日, 1 = 周一, ...
VAR FirstYear =             -- 自定义使用的第一个年份
    YEAR ( MIN ( Sales[Order Date]  ))
RETURN
GENERATE (
    FILTER (
        CALENDARAUTO (),
        YEAR ( [Date] ) >= FirstYear
    ),
    VAR Yr = YEAR ( [Date] )             -- 年份编号
    VAR Mn = MONTH ( [Date] )            -- 月份编号(1-12)
    VAR Qr = QUARTER ( [Date] )          -- 季度编号(1-4)
    VAR MnQ = Mn - 3 * (Qr - 1)          -- 季度中的月份编号(1-3)
    VAR Wd = WEEKDAY ( [Date], 1 ) –1    -- 星期几(0 = 周日,1 = 周一,...)
    VAR Fyr =                            -- 会计年度编号
        Yr + 1 * ( FirstFiscalMonth > 1 && Mn >= FirstFiscalMonth )
    VAR Fqr =                            -- 会计季度(字符串)
        FORMAT ( EOMONTH ( [Date], 1 - FirstFiscalMonth ), "\QQ" )
    RETURN ROW (
        "Year", DATE ( Yr, 12, 31 ),
        "Year Quarter", FORMAT ( [Date], "\QQ-YYYY" ),
        "Year Quarter Date", EOMONTH ( [Date], 3 - MnQ ),
        "Quarter", FORMAT ( [Date], "\QQ" ),
        "Year Month", EOMONTH ( [Date], 0 ),
        "Month", DATE ( 1900, MONTH ( [Date] ), 1 ),
        "Day of Week", DATE ( 1900, 1, 7 + Wd + (7 * (Wd < FirstDayOfWeek)) ),
        "Fiscal Year", DATE ( Fyr + (FirstFiscalMonth = 1), FirstFiscalMonth, 1 ) - 1,
        "Fiscal Year Quarter", "F" & Fqr & "-" & Fyr,
        "Fiscal Year Quarter Date", EOMONTH ( [Date], 3 - MnQ ),
        "Fiscal Quarter", "F" & Fqr
    )
)

你可以自定义前3个变量以创建满足特定业务需求的Date表。为了获得正确的结果,当列不是文本格式,而是具有标准或自定义格式的“日期”数据类型时,必须按以下方式在数据模型中对列进行设置。

Date:m/dd/yyyy(8/14/2007),用作标记为日期表的列。

Year:yyyy(2007)。

Year Quarter:文本(Q3-2008)。

Year Quarter Date:隐藏(9/30/2008)。

Quarter:文本(Q1)。

Year Month:mmm yyyy(Aug 2007)。

Month:mmm(Aug)。

Day of Week:ddd(Tue)。

Fiscal Year:\F\Y yyyy(FY 2008)。

Fiscal Year Quarter:文本(FQ1-2008)。

Fiscal Year Quarter Date:隐藏(9/30/2008)。

Fiscal Quarter:文本(FQ1)。

此模式中的Date表具有两个层次结构。

日历:年(Year)、季度(Year Quarter)和月(Year Month)。

会计:年(Fiscal Year)、季度(Fiscal Year Quarter)和月(Year Month)。

不管来源如何,若要使用此模式的公式,Date表必须包括一个隐藏的DateWithSales计算列。

Date表中的计算列

DateWithSales = 
'Date'[Date] <= MAX ( Sales[Order Date] )

如果日期早于或等于Sales表中的最后交易日期,则Date[DateWithSales]列是TRUE,否则为FALSE。换句话说,“过去”日期的DateWithSales为TRUE,“未来”日期的DateWithSales为FALSE,这里的“过去”和“未来”均是相对于Sales中的最后交易日期来定义的。

大多数时间智能计算不应显示最后有效日期之后的日期值。例如,年初至今的计算也可以显示未来日期的值,但是我们想要将其隐藏。这些示例中使用的数据集在2009年8月15日结束。因此,我们将月份“August 2009”、2009年第三季度“Q3-2009”和年份“2009”视为数据的最后时间段。2009年8月15日以后的任何日期都被视为未来,我们想要隐藏未来值。

为了避免显示未来日期的结果,我们使用ShowValueForDates度量值。

如果所选的时间段不是在数据的最后一个时间段之后,则ShowValueForDates返回TRUE。

Date表中的度量值(隐藏)

ShowValueForDates :=
VAR LastDateWithData =
    CALCULATE ( 
        MAX ( 'Sales'[Order Date] ), 
        REMOVEFILTERS () 
    )
VAR FirstDateVisible =
    MIN ( 'Date'[Date] )
VAR Result = 
    FirstDateVisible <= LastDateWithData
RETURN
    Result

ShowValueForDates度量值是隐藏的。这是一项技术措施,目的是在许多与时间相关的不同计算中实现重复使用同一逻辑,并且用户不应直接在报告中使用ShowValueForDates。

本节介绍用来引入时间智能计算的命名约定。一种简单的命令分类方式可以有效地表明一种计算是否:

推移一段时间,例如上一年的同一时间段;

执行聚合,例如年初至今;

比较两个时间段,例如今年与去年相比较。

部分命名约定如表2-1所示。

表2-1 命名约定

首字母缩写

描述

推移

聚合

比较

YTD

年初至今

 

X

 

QTD

季初至今

 

X

 

MTD

月初至今

 

X

 

MAT

移动年度总计

 

X

 

PY

上一年

X

 

 

PQ

上一季

X

 

 

PM

上一月

X

 

 

PYC

上一整年

X

 

 

PQC

上一整季

X

 

 

PMC

上一整月

X

 

 

PP

上一时间段(自动选择年、季或月)

X

 

 

PYMAT

上一年移动年度总计

X

X

 

YOY

比上个年度的差异

 

 

X

QOQ

比上个季度的差异

 

 

X

MOM

比上个月度的差异

 

 

X

MATG

移动年度总计增长

X

X

X

POP

比上个时间段的差异(自动选择年、季或月)

 

 

X

PYTD

上个年度的年初至今

X

X

 

PQTD

上个季度的季初至今

X

X

 

PMTD

上个月度的月初至今

X

X

 

YOYTD

比上个年度年初至今的差异

X

X

X

QOQTD

比上个季度季初至今的差异

X

X

X

MOMTD

比上个月度月初至今的差异

X

X

X

YTDOPY

年初至今比上个年度

X

X

X

QTDOPQ

季初至今比上个季度

X

X

X

MTDOPM

月初至今比上个月度

X

X

X

年初至今、季初至今和月初至今的计算会修改Date表的筛选上下文,并将一系列日期用作筛选器,以覆盖所选时间段的筛选器。

所有这些计算都可以使用带有时间智能函数的常规CALCULATE或某一个TOTAL函数(例如TOTALYTD)来实现。TOTAL函数只是CALCULATE版本的语法糖(语法替代)。尽管我们更喜欢CALCULATE版本,CALCULATE会使公式逻辑更明显,并且比TOTAL函数具有更大的灵活性,但我们仍将TOTAL函数作为参考。在以下示例中,使用TOTAL函数的公式会被标记为(2)。展示TOTAL函数的目的仅仅是显示它们可以返回与CALCULATE版本相同的值。

“年初至今总计”始于该年度1月1日的数据,如图2-1所示。

图2-1 Sales YTD (simple)显示了任何时间段的值,而Sales YTD和Sales YTD (2)
隐藏了数据的最后一个时间段之后的数据

年初至今总计的度量值可以通过DATESYTD函数来实现,如下所示。

Sales表中的度量值

Sales YTD (simple) := 
CALCULATE ( 
    [Sales Amount], 
    DATESYTD ( 'Date'[Date] ) 
)

DATESYTD函数返回一个日期集,它包含筛选上下文中从显示的日期所在年份的第一天到显示的最后一天之间的所有日期。因此,Sales YTD (simple)度量值甚至会显示该年份未来日期的数据。通过仅当ShowValueForDates度量值返回TRUE时才返回结果,就可以避免在Sales YTD度量值中出现这种情况。

Sales表中的度量值

Sales YTD := 
IF (  
    [ShowValueForDates],  
    CALCULATE (  
        [Sales Amount],  
        DATESYTD ( 'Date'[Date] )  
    )  
)

如果报告不是基于公历年度,而是基于会计年度,则DATESYTD函数会需要增加一个参数来识别会计年度的最后一天。下面以图2-2中的报告为例。

图2-2 Sales Fiscal YTD和Sales Fiscal YTD (2)显示了基于会计年度的年初至今

Sales Fiscal YTD度量值在DATESYTD函数的第二个参数中指定了会计年度的最后一天和月份。以下度量值将6月30日用作会计年度的最后一天。DATESYTD函数的第二个参数必须是与Date表中会计年度的定义相对应的固定值(也称为常量)。它不能动态计算。

Sales表中的度量值

Sales Fiscal YTD := 
IF (  
    [ShowValueForDates],  
    CALCULATE (  
        [Sales Amount],  
        DATESYTD ( 'Date'[Date], "6-30" )  
    )  
)

TOTALYTD函数可以替代DATESYTD函数。

Sales表中的度量值

Sales YTD (2) := 
IF (  
    [ShowValueForDates],  
    TOTALYTD (  
        [Sales Amount],  
        'Date'[Date] 
    )  
)

Sales表中的度量值

Sales Fiscal YTD (2) := 
IF (  
    [ShowValueForDates],  
    TOTALYTD (  
        [Sales Amount],  
        'Date'[Date], 
        "6-30" 
    )  
)

“季初至今总计”始于该季度第一天的数据,如图2-3所示。

图2-3 Sales QTD显示了季初至今的金额,2009年显示为空白,因为2009年4季度没有数据

季初至今总计的度量值可以通过DATESQTD函数来计算,如下所示。

Sales表中的度量值

Sales QTD := 
IF (  
    [ShowValueForDates],  
    CALCULATE (  
        [Sales Amount],  
        DATESQTD ( 'Date'[Date] )  
    )  
)

TOTALQTD函数可以替代DATESQTD函数。

Sales表中的度量值

Sales QTD (2) := 
IF (  
    [ShowValueForDates],  
    TOTALQTD (  
        [Sales Amount],  
        'Date'[Date] 
    )  
)

“月初至今总计”始于该月度第一天的数据,如图2-4所示。

图2-4 Sales MTD显示了月初至今的金额,CY 2009和Q3-2009显示为空白,
是因为2009年8月15日之后没有数据

月初至今总计的度量值可以通过DATESMTD函数来计算,如下所示。

Sales表中的度量值

Sales MTD := 
IF (  
    [ShowValueForDates],  
    CALCULATE (  
        [Sales Amount],  
        DATESMTD ( 'Date'[Date] )  
    )  
)

TOTALMTD可以替代DATESMTD函数。

Sales表中的度量值

Sales MTD (2) :=
IF ( 
    [ShowValueForDates], 
    TOTALMTD ( 
        [Sales Amount], 
        'Date'[Date]
    ) 
)

一个常见的需求是将一个时间段与上一年、上一个季度或上一个月的相同时间段进行比较。上一个年、季、月可能不完整,因此,为了实现合理的比较,比较时应考虑一个等效时间段。由于这些原因,本节中显示的计算会使用Date[DateWithSales]计算列,如SQLBI官网中的“Hiding future dates for calculations in DAX”一文所述。

“比上个年度”是将一个时间段与上一年的等效时间段进行比较。在此示例中,有效数据截至2009年8月15日。因此,Sales PY显示的2008年数字,仅考虑2008年8月15日之前的交易。图2-5显示,2008年8月的Sales Amount为721,560.95,而2009年8月的Sales PY返回值为296,529.51,是因为度量值仅考虑了截至2008年8月15日的销售额。

图2-5 2009年8月的Sales PY显示的是2008年8月1日~15日的金额,
因为2009年8月15日之后没有数据

Sales PY使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。“比上个年度增长”的计算,数字形式表现为Sales YOY,百分比形式表现为Sales YOY %。这两个度量值都使用Sales PY来确保仅计算截至2009年8月15日的数据。

Sales表中的度量值

Sales PY := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, YEAR ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales YOY :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PY]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod ) && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales YOY % := 
DIVIDE (  
    [Sales YOY], 
    [Sales PY] 
)

用户也可以使用SAMEPERIODLASTYEAR函数编写Sales PY。SAMEPERIODLASTYEAR函数更易于阅读,但没有任何性能上的优势。这是因为,从本质上讲,该函数在前面的公式中已被翻译为DATEADD函数。

Sales表中的度量值

Sales PY (2) := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        CALCULATETABLE (
            SAMEPERIODLASTYEAR ( 'Date'[Date] ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

“比上个季度”是将一个时间段与上一季度的等效时间段进行比较。在此示例中,有效数据截至2009年8月15日(2009年第三季度的前半部分)。因此,2009年8月(第三季度的第二个月)的Sales PQ显示截至2009年5月15日(上一季度第二个月的前半部分)的销售情况。图2-6显示,2009年5月的Sales Amount为1,067,165.23,而2009年8月的Sales PQ返回值为435,306.10,是因为仅考虑截至2009年8月15日的销售额。

Sales PQ使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。“比上个季度增长”的计算,数字形式表现为Sales QOQ,百分比形式表现为Sales QOQ %。这两个度量值都使用Sales PQ来确保对相同时间段进行合理比较。

图2-6 2009年8月的Sales PQ显示的是2009年5月1日~15日的金额,
因为在2009年8月15日之后没有数据

Sales表中的度量值

Sales PQ := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, QUARTER ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales QOQ :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PQ]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod ) && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales QOQ % := 
DIVIDE (  
    [Sales QOQ], 
    [Sales PQ] 
)

“比上个月度”是将一个时间段与上一月度的等效时间段进行比较。在此示例中,有效数据截至2009年8月15日。因此,Sales PM仅考虑2009年7月1日~15日这一时期的销售额,以便返回与2009年8月进行比较的值。以这种方式,它仅返回上一个月相应时间段的数据。图2-7显示,2009年7月的Sales Amount为1,068,396.58,而2009年8月的Sales PM返回值为584,212.78,这是因为仅考虑了截至2009年7月15日的销售额。

图2-7 2009年8月的Sales PM显示的是2009年7月1日~15日的金额,
因为在2009年8月15日之后没有数据

Sales PM使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。“比上个月度增长”的计算,数字形式表现为Sales MOM,百分比形式表现为Sales MOM %。这两个度量值都使用Sales PM来确保对相同时间段进行合理比较。

Sales表中的度量值

Sales PM := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, MONTH ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales MOM :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PM]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales MOM % := 
DIVIDE (  
    [Sales MOM], 
    [Sales PM] 
)

“比上个时间段增长”会根据当前的可视化选择,来自动选择本节先前介绍的度量值之一。例如,如果可视化显示月度级别的数据,则会返回“比上个月度增长”的度量值;如果可视化显示年度级别的总计,则返回“比上个年度增长”的度量值。预期结果如图2-8所示。

图2-8 Sales PP在月度级别上显示上个月的值,在季度级别上显示上一个季度的值,
在年度级别上显示上一年的值

Sales PP、Sales POP和Sales POP%这3个度量值,会根据报告选择的级别,定向到相应的年、季度和月的度量值来进行计算。ISINSCOPE函数会检测报告中所使用的级别。传递给ISINSCOPE函数的参数是图2-8的矩阵视图的行所使用的属性。度量值的定义方法如下。

Sales表中的度量值

Sales POP % := 
SWITCH (
    TRUE,
    ISINSCOPE ( 'Date'[Year Month] ), [Sales MOM %],
    ISINSCOPE ( 'Date'[Year Quarter] ), [Sales QOQ %],
    ISINSCOPE ( 'Date'[Year] ), [Sales YOY %]
)

Sales表中的度量值

Sales POP := 
SWITCH (
    TRUE,
    ISINSCOPE ( 'Date'[Year Month] ), [Sales MOM],
    ISINSCOPE ( 'Date'[Year Quarter] ), [Sales QOQ],
    ISINSCOPE ( 'Date'[Year] ), [Sales YOY]
)

Sales表中的度量值

Sales PP :=   
SWITCH (
    TRUE,
    ISINSCOPE ( 'Date'[Year Month] ), [Sales PM],
    ISINSCOPE ( 'Date'[Year Quarter] ), [Sales PQ],
    ISINSCOPE ( 'Date'[Year] ), [Sales PY]
)

“期初至今增长”的度量值是指将“期初至今”度量值与基于特定偏移量的等效时间段的同一度量值进行比较。例如,你可以将年初至今的聚合结果与上一年的年初至今进行比较,偏移量为一年。

这组计算中的所有度量值均需考虑不完整时间段。示例中有效数据截至2009年8月15日,因此这些度量值可确保上一年的计算不包括2009年8月15日之后的日期。

“比上个年度年初至今增长”将特定日期的年初至今与上一年等效日期的年初至今进行比较。图2-9显示,2009年的Sales PYTD仅考虑截至2008年8月15日的交易。因此,Q3-2008的Sales YTD为7,129,971.53,而Q3-2009的Sales PYTD较低,为5,741,502.86。

图2-9 Q3-2009的Sales PYTD显示的是2008年1月1日~8月15日的金额,
因为在2009年8月15日之后没有数据

Sales PYTD使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。Sales YOYTD和Sales YOYTD %通过Sales PYTD来确保对相同时间段的数据进行合理比较。

Sales表中的度量值

Sales PYTD := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales YTD],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, YEAR ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales YOYTD := 
VAR ValueCurrentPeriod = [Sales YTD]
VAR ValuePreviousPeriod = [Sales PYTD]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales YOYTD % := 
DIVIDE (    
    [Sales YOYTD],   
    [Sales PYTD]   
)

Sales PYTD使用DATEADD函数将日期筛选器回移一年。使用DATEADD函数可以轻松地推移两年或两年以上。但是,也可以使用SAMEPERIODLASTYEAR编写Sales PYTD将日期回移一年。如下面这个示例,其本质用法就是上例中的DATEADD函数。

Sales表中的度量值

Sales PYTD (2) := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales YTD],
        CALCULATETABLE (
            SAMEPERIODLASTYEAR ( 'Date'[Date] ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

“比上个季度季初至今增长”将特定日期的季初至今与上一季度等效日期的季初至今进行比较。图2-10显示,2009年8月的Sales PQ仅考虑截至2008年5月15日的交易,仅获得上一季度的前半部分的金额。因此,2009年5月的Sales QTD为1,746,058.45,而2009年8月的Sales PQTD较低,为1,114,199.32。

图2-10 2009年8月的Sales PQTD显示的是2009年4月1日~5月15日的金额,
因为在2009年8月15日之后没有数据

Sales PQTD使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。Sales QOQTD和Sales QOQTD %通过Sales PQTD来确保对相同时间段的数据进行合理比较。

Sales表中的度量值

Sales PQTD := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales QTD],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, QUARTER ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales QOQTD := 
VAR ValueCurrentPeriod = [Sales QTD]
VAR ValuePreviousPeriod = [Sales PQTD]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales QOQTD % := 
DIVIDE (    
    [Sales QOQTD],   
    [Sales PQTD]   
)

“比上个月度月初至今增长”将特定日期的月初至今与上一个月等效日期的月初至今进行比较。图2-11显示,2009年8月的Sales PMTD仅考虑截至2009年7月15日的交易,以获得上一个月的相应时间段。因此,2009年7月的Sales MTD为1,068,396.58,而2009年8月的Sales PMTD较低,为584,212.78。

图2-11 2009年8月的Sales PQTD显示的是2009年7月1日~15日这一时期的金额,
因为在2009年8月15日之后没有数据

Sales PMTD使用DATEADD函数并筛选Date [DateWithSales]列,以确保与上一时间段的数据进行合理比较。Sales MOMTD和Sales MOMTD %通过Sales PMTD度量值来确保对相同时间段的数据进行合理比较。

Sales表中的度量值

Sales PMTD :=
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales MTD],
        CALCULATETABLE (
            DATEADD ( 'Date'[Date], -1, MONTH ),
            'Date'[DateWithSales] = TRUE
        )
    )
)

Sales表中的度量值

Sales MOMTD :=
VAR ValueCurrentPeriod = [Sales MTD]
VAR ValuePreviousPeriod = [Sales PMTD]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales MOMTD % := 
DIVIDE (    
    [Sales MOMTD],   
    [Sales PMTD]   
)

当将上一个完整时间段作为基准时,将期初至今聚合结果与上一个完整时间段的数据进行比较是很有用的。一旦当前年初至今达到上一个完整年度的100%,这意味着我们已经有望以更少的天数达到与上一个完整时间段相同的绩效。

“年初至今比上个完整年度”是将年初至今与上一个完整年度进行比较。如图2-12所示,2008年11月的Sales YTD几乎达到2007年全年的Sales Amount。Sales YTDOPY%体现年初至今与上个年度总额的直接比较;当该百分比为正数时,表示增长超过上一个会计年。本案例中,从2008年12月1日起实现超过上一个年度的增长。

图2-12 Sales YTDOPY %从2008年12月1日起显示为正数,
表示Sales YTD开始大于2007年的Sales Amount

“年初至今比上个年度增长”是通过Sales YTDOPY和Sales YTDOPY %度量值来计算的;通过Sales YTD度量值来计算年初至今的值,通过Sales PYC度量值来获得上一完整年度的销售额。

Sales表中的度量值

Sales PYC := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        PARALLELPERIOD ( 'Date'[Date], -1, YEAR )
    )
)

Sales表中的度量值

Sales YTDOPY := 
VAR ValueCurrentPeriod = [Sales YTD]
VAR ValuePreviousPeriod = [Sales PYC]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales YTDOPY % := 
DIVIDE (    
    [Sales YTDOPY],   
    [Sales PYC]   
)

也可以使用 PREVIOUSYEAR 函数来编写 Sales PYC 度量值,其执行方式类似于PARALLELPERIOD函数(此示例不涉及二者差异):

Sales表中的度量值

Sales PYC (2) := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        PREVIOUSYEAR ( 'Date'[Date] )
    )
)

如果进行比较时使用的是会计年度,则必须使用 PREVIOUSYEAR 函数,因为PREVIOUSYEAR函数可以接收用于指定会计年度最后一天的第二个参数。参见图2-13所示的报告,该案例按会计年度对度量值进行了分割。

图2-13 Sales Fiscal YTDOPY %将Sales YTD与上一会计年度的Sales Amount进行了比较

该案例中使用的度量值定义如下。请注意Sales Fiscal PYC中PREVIOUSYEAR函数的第二个参数。

Sales表中的度量值

Sales Fiscal PYC := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        PREVIOUSYEAR ( 'Date'[Date], "06-30" )
    )
)

Sales表中的度量值

Sales Fiscal YTDOPY := 
VAR ValueCurrentPeriod = [Sales Fiscal YTD]
VAR ValuePreviousPeriod = [Sales Fiscal PYC]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales Fiscal YTDOPY % := 
DIVIDE (    
    [Sales Fiscal YTDOPY],   
    [Sales Fiscal PYC]   
)

“季初至今比上个完整季度”是将季初至今与上一个完整季度进行比较。如图2-14所示,2008年5月的Sales QTD超过了Q1-2008的Sales Amount总额。Sales QTDOPQ%体现季初至今与上个季度总额的直接比较;当该百分比为正数时,表示增长超过上一季度。在本案例中,从2008年5月起实现超过上一季度的增长。

图2-14 Sales QTDOPQ %从2008年5月起显示为正数,
表示Sales QTD开始大于Q1-2008的Sales Amount

“季初至今比上个季度增长”是通过Sales QTDOPQ和Sales QTDOPQ %度量值来计算的;通过Sales QTD度量值来计算季初至今的值,通过Sales PQC度量值来获得上一个完整季度的销售额。

Sales表中的度量值 

Sales PQC := 
IF (
    [ShowValueForDates] && HASONEVALUE ( 'Date'[Year Quarter] ),
    CALCULATE (
        [Sales Amount],
        PARALLELPERIOD ( 'Date'[Date], -1, QUARTER )
    )
)

Sales表中的度量值

Sales QTDOPQ := 
VAR ValueCurrentPeriod = [Sales QTD]
VAR ValuePreviousPeriod = [Sales PQC]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales QTDOPQ % := 
DIVIDE (    
    [Sales QTDOPQ],   
    [Sales PQC]   
)

也可以使用PREVIOUSQUARTER来编写Sales PQC度量值,只要不是用在超过一个季度的年度级别即可。

Sales表中的度量值

Sales PQC (2) := 
IF (
    [ShowValueForDates] && HASONEVALUE ( 'Date'[Year Quarter] ),
    CALCULATE (
        [Sales Amount],
        PREVIOUSQUARTER ( 'Date'[Date] )
    )
)

“月初至今比上个完整月度”是将月初至今与上一个完整月度进行比较。如图2-15所示,2008年4月期间的总Sales MTD超过了2008年3月的Sales Amount。Sales MTDOPM%体现月初至今与上个月度总额的直接比较;当该百分比为正数时,表示增长超过上一月度。在本案例中,从2008年4月19日起实现超过上一月度的增长。

“月初至今比上个月度增长”是通过Sales MTDOPM %和Sales MTDOPM度量值来计算的;通过Sales MTD度量值来计算月初至今的值,通过Sales PMC度量值来获得上一个完整月度的销售额。

图2-15 Sales MTDOPM %从2008年4月19日起显示为正数,表示此时的Sales MTD
开始大于2008年3月的Sales Amount

Sales表中的度量值

Sales PMC := 
IF (
    [ShowValueForDates] && HASONEVALUE ( 'Date'[Year Month] ),
    CALCULATE (
        [Sales Amount],
        PARALLELPERIOD ( 'Date'[Date], -1, MONTH )
    )
)

Sales表中的度量值

Sales MTDOPM := 
VAR ValueCurrentPeriod = [Sales MTD]
VAR ValuePreviousPeriod = [Sales PMC]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales MTDOPM % := 
DIVIDE (  
    [Sales MTDOPM], 
    [Sales PMC] 
)

也可以使用PREVIOUSMONTH函数来编写Sales PMC度量值,只要不是用在超过一个月的季度或年度级别即可。

Sales表中的度量值

Sales PMC (2) := 
IF (
    [ShowValueForDates] && HASONEVALUE ( 'Date'[Year Month] ),
    CALCULATE (
        [Sales Amount],
        PREVIOUSMONTH ( 'Date'[Date] )
    )
)

聚合几个月数据的一种常用方法是使用移动年度总计,而不是年初至今。移动年度总计包括过去12个月的数据。例如,2008年3月的移动年度总计包括从2007年4月至2008年3月的数据。

Sales MAT(Moving Annual Total,MAT)度量值用于计算移动年度总计,如图2-16所示。

图2-16 2008年3月的Sales MAT聚合了2007年4月至2008年3月的Sales Amount

移动年度总计使用DATESINPERIOD函数来选择上一年。

Sales表中的度量值

Sales MAT := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales Amount],
        DATESINPERIOD (
            'Date'[Date],
            MAX ( 'Date'[Date] ),
            -1,
            YEAR
        )
    )
)

DATESINPERIOD函数返回一个日期集。该日期集包含从第二个参数开始传递的日期,同时应用在第三个参数和第四个参数中指定的偏移。例如,Sales MAT度量值返回筛选上下文中最后有效日期之前的全年日期。通过分别给第三个参数和第四个参数赋值-12和MONTH,可以获得相同的结果。

“移动年度总计增长”(Moving Annual Total Growth,MATG)借由Sales MAT度量值,通过Sales PYMAT、Sales MATG和Sales MATG %度量值来计算。Sales MAT度量值提供首次销售后一年的正确值(当一整年的销售数据都可以获得时);但当可获取的数据所覆盖的时间段不足一整年时,则是另一种情形。例如,2009年全年的Sales PYMAT为9,927,582.99,这与2008年全年的Sales Amount相对应,如图2-17所示。当将2009年销售额与2008年全年销售额进行比较时,只能用到不足8个月的销售数据,这是因为2009年有销售记录的数据截至当年8月15日。类似地,你可以看到2008年开始出现的Sales MATG %数值非常高,一年后稳定下来。前几个数值非常高,是由于上一年没有销售。其设计原理是:移动年度总计通常是以月粒度或日粒度进行计算,以便在图表中显示趋势。

图2-17 Sales MATG%显示了Sales MAT和Sales PYMAT之间增长的百分比

度量值的定义方法如下:

Sales表中的度量值

Sales PYMAT := 
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales MAT],
        DATEADD ( 'Date'[Date], -1, YEAR )
    )
)

Sales表中的度量值

Sales MATG := 
VAR ValueCurrentPeriod = [Sales MAT]
VAR ValuePreviousPeriod = [Sales PYMAT]
VAR Result =
    IF (
        NOT ISBLANK ( ValueCurrentPeriod )
            && NOT ISBLANK ( ValuePreviousPeriod ),
        ValueCurrentPeriod - ValuePreviousPeriod
    )
RETURN
    Result

Sales表中的度量值

Sales MATG % := 
DIVIDE (  
    [Sales MATG], 
    [Sales PYMAT] 
)

也可以使用SAMEPERIODLASTYEAR函数来编写Sales PYMAT度量值。如下面的示例,其本质用法就是上例中的DATEADD。

Sales表中的度量值

Sales PYMAT (2) :=
IF (
    [ShowValueForDates],
    CALCULATE (
        [Sales MAT],
        SAMEPERIODLASTYEAR ( 'Date'[Date] )
    )
)

移动平均通常用在折线图中显示趋势。图2-18展示了包括30天(Sales AVG 30D)、3个月(Sales AVG 3M)和1年(Sales AVG 1Y)的Sales Amount的移动平均。

图2-18 Sales AVG 30D、Sales AVG 3M和Sales AVG 1Y分别显示了30天、3个月和1年的移动平均

Sales AVG 30D(移动平均30天)度量值通过遍历借由DATESINPERIOD获得的过去30天的日期列表,来计算30天的移动平均。

Sales表中的度量值

Sales AVG 30D := 
VAR Period30D =
    CALCULATETABLE (
        DATESINPERIOD (
            'Date'[Date],
            MAX ( 'Date'[Date] ),
            -30,
            DAY
        ),
        'Date'[DateWithSales] = TRUE
    )
VAR FirstDayWithData =
    CALCULATE (
        MIN ( Sales[Order Date] ),
        REMOVEFILTERS ()
    )
VAR FirstDayInPeriod =
    MINX ( Period30D, 'Date'[Date] )
VAR Result =
    IF (
        FirstDayWithData <= FirstDayInPeriod,
        AVERAGEX (
            Period30D,
            [Sales Amount]
        )
    )
RETURN
    Result

这种模式非常灵活。但是对于常规的累加计算,可以使用另一个公式:Result来更快地实现。

VAR Result =
    IF (
        FirstDayWithData <= FirstDayInPeriod,
        CALCULATE (
            DIVIDE (
                [Sales Amount],
                DISTINCTCOUNT ( Sales[Order Date] )
            ),
            Period30D
        )
    )

Sales AVG 3M(移动平均3个月)度量值通过遍历借由DATESINPERIOD获得的过去3个月的日期列表,来计算3个月的移动平均。

Sales表中的度量值

Sales AVG 3M := 
VAR Period3M =
    CALCULATETABLE (
        DATESINPERIOD (
            'Date'[Date],
            MAX ( 'Date'[Date] ),
            -3,
            MONTH
        ),
        'Date'[DateWithSales] = TRUE
    )
VAR FirstDayWithData =
    CALCULATE (
        MIN ( Sales[Order Date] ),
        REMOVEFILTERS ()
    )
VAR FirstDayInPeriod =
    MINX ( Period3M, 'Date'[Date] )
VAR Result =
    IF (
        FirstDayWithData <= FirstDayInPeriod,
        AVERAGEX (
            Period3M,
            [Sales Amount]
        )
    )
RETURN
    Result

对于简单的累加度量值,在“移动平均30天”中介绍的基于DIVIDE的模式,也可以用于计算3个月的移动平均。

Sales AVG 1Y(移动平均1年)度量值通过遍历借由DATESINPERIOD获得的过去1年的日期列表,来计算1年的移动平均。

Sales表中的度量值

Sales AVG 1Y := 
VAR Period1Y =
    CALCULATETABLE (
        DATESINPERIOD (
            'Date'[Date],
            MAX ( 'Date'[Date] ),
            -1,
            YEAR
        ),
        'Date'[DateWithSales] = TRUE
    )
VAR FirstDayWithData =
    CALCULATE (
        MIN ( Sales[Order Date] ),
        REMOVEFILTERS ()
    )
VAR FirstDayInPeriod =
    MINX ( Period1Y, 'Date'[Date] )
VAR Result =
    IF (
        FirstDayWithData <= FirstDayInPeriod,
        AVERAGEX (
            Period1Y,
            [Sales Amount]
        )
    )
RETURN
    Result

对于简单的累加度量值,在“移动平均30天”中介绍的基于DIVIDE的模式,也可以用于计算1年的移动平均。

一旦将Date表标记为日期表,每当CALCULATE筛选Date表的日期列时,DAX会自动删除Date表中的所有筛选器。设计这样的执行方式,其目的是简化时间智能计算的编写。的确,如果DAX不删除筛选器,则每次使用DAX时间智能函数时,需要在Date表上手动添加REMOVEFILTERS,这会带来不好的开发体验。

自动删除Date表中的筛选器可能会给某些特定报告带来问题。例如,如果一份报告计算年初至今销售情况的方式是按星期几对销售额进行切分,则仅通过使用时间智能函数DATESYTD获得的结果会是错误的。图2-19显示了一周中所有日期的Sales YTD,可以看出,一周中每一天的Sales YTD结果略小于或等于行总计。

图2-19 按星期几切分的度量值Sales YTD产生了不正确的结果

值不正确的原因是DATESYTD在Date[Date]列上应用了一个筛选器。因为Date被标记为日期表,所以DAX会自动将REMOVEFILTERS('Date')修饰符应用于筛选器参数中,从而使得DATESYTD在CALCULATE筛选作用中可删除星期几上的筛选器。因此,显示的数字是年初至今,与星期几上的任何筛选器无关。星期几筛选器仅影响报告行中指定的时间段(年或季度)的最后一天。要想获得正确的结果(如图2-20所示)需要使用另一种方法。

图2-20 按星期几切分的Sales YTD (Day of Week)产生了正确的结果

有两种方法可以获取正确的值:①以CALCULATE语句重新遍历星期几上的筛选器;②更新数据模型。

仅在对列进行筛选的情况下,恢复星期几上的筛选器需要添加VALUES(Date[Day of Week]),代码如下。

Sales表中的度量值

Sales YTD (day of week) := 
IF (
    [ShowValueForDates],
    IF (
        ISFILTERED ( 'Date'[Day of Week] ),
        CALCULATE (
            [Sales Amount],
            DATESYTD ( 'Date'[Date] ),
            VALUES ( 'Date'[Day of Week] )
        ),
        CALCULATE (
            [Sales Amount],
            DATESYTD ( 'Date'[Date] )
        )
    )
)

第一种解决方案效果很好,但存在一个重大缺点:根据是否筛选了Date[Day of Week]列,会有两种不同版本的计算。在大型模型上,这可能会对性能产生明显影响。

针对这种情况,还有另一种解决方案,更新数据模型。不通过使用Date表选择星期几,我们可以将星期几存储在一个单独的表中,用该表筛选Sales,与Date无关。这样,自动删除Date上的筛选器不会影响现有的星期几筛选器。例如,可以将Day of Week表创建为计算表。

计算表

Day of Week = 
SELECTCOLUMNS (
    'Date',
    "Date", 'Date'[Date],
    "Day of Week", 'Date'[Day of Week]
)

Day of Week表必须通过'Day of Week'[Date]关联到Sales[Order Date],这意味着模型必须如图2-21所示。

图2-21 新建的Day of Week表关联到Date使用的同一个Order Date列

请注意,我们使用Date中的所有日期新建了Day of Week表,以与现有的Sales[Order Date]列建立联系。通过创建仅包含7个值的表(周日至周六),我的可以获得相同的结果,但是这种选择需要在Sales表中增加一列,这样会使数据模型消耗更多内存。

在新建的表中按Day of Week进行切分,可以兼容任何时间智能计算,并且兼容Day of Week表上的任何筛选器。这是因为两个筛选器(Date和Day of the Week)属于两个不同的表。

依据特定业务规则的需求,其他表可以合并任何属性集。我们使用星期几做了一个示例,你可以使用任何其他属性集(例如工作日、假期、季节),前提是这些属性取决于Order Date。

微信扫码关注【异步社区】微信公众号,回复“e57726”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


相关图书

算者生存:商业分析的方法与实践
算者生存:商业分析的方法与实践
数据结构与算法(Rust语言描述)
数据结构与算法(Rust语言描述)
R语言医学多元统计分析
R语言医学多元统计分析
Python数据分析(第3版)
Python数据分析(第3版)
Python数据分析入门与实战
Python数据分析入门与实战
Python贝叶斯分析(第2版)
Python贝叶斯分析(第2版)

相关文章

相关课程