Power BI之DAX神功:第4卷第4回 使用虚拟关系在DAX中转换筛选器
一、区间范围处理方案
需求:每个促销品在促销日期范围内的销售情况

如上图所示,每个商品都有它的促销期,根据相关规定一般情况下(不排除有特例),商品促销期不得超过14天,且促销期过后必需恢复原价卖至少7天,才能再次促销。假设:一台游戏机原价3899,促销价格2899,2021/1/1~2021/1/14促销14天后,2021/1/15要恢复原价3899,如果我还想继续促销,那我的促销价格必须低于2899,例如促销价2898,并且要注明原价是2899,不能写3899。否则涉嫌价格欺诈。假设2021/1/15此商品正常价格跌至1999元,可以修改价格,但是不能使用促销标志。此规定暂时不适用于电商。
很多商家并没有按此规定执行,买方懂的人少,就算买家懂去举报的也是少数。你分析,当一个商家卖的价格比别人都便宜时(前提是正品),那举报他的肯定不是正常的消费者。
上面两张表,促销表列出了四种商品的促销日期。但是我们无法直接建立关系。
【1】数据清洗
导入促销表时,先进入PowerBI的PowerQuery模块,使用M函数增加两个列
如果你的表格本身就是有开始日期和结束日期这两列,那么你就可以省略数据清洗的步骤,我一直告诉大家数据清洗不是必需的,是表格不规范造成的。

【2】使用商品编码,建立关系

【3】编写度量值
《DAX神功》第1卷第14回讲到,var x=SELECTEDVALUE('表'[列]) 代表当前行
《火力全开》笔记27.3.3 DATESBETWEEN函数取区间范围
【4】返回结果

二、按促销类别计算方法同上

【1】对促销表利用M函数进行数据清洗
Ps:如果促销表中有开始和结束日期这两列,数据清洗可以省略。

【2】按下图建立多表关系

【3】写度量值
【4】返回结果

其实就是修改一下字段名称,方法是没有区别的。但是《权威指南》本节课提供的案例使用了CONTAINS函数,声明人家案例对,但是有局限性,也就是说你的促销品或类别必须是整月促销。
三、CONTAINS函数

每个国家都有自己的法律法规,也许国外没有促销时间的限制,人家就整月做促销。《DAX权威指南》是高飞老师翻译的,并不是作者,所以案例也不会考虑我们国家的情况。
《火力全开》笔记24.01 我们讲了CONTAINS函数
语法:CONTAINS(表,列名1,查找的值1,列名2,查找的值2,...)
如果第1参数表至少存在一行满足所有列名都有对应的查找的值,返回 true,否则返回 false。

注意:列名必须属于第一参数或第一参数的扩展表。(扩展表原理就不再重复了)

因为要考虑年和月,所以我们需要更多颗粒度,需要新建日期表:
【1】新建动态日期表
【2】日期表与销售表建立关系

【3】从促销表中多条件查找
CONTAINS('促销','促销'[年],'日期表'[年],'促销'[月],'日期表'[月],'促销'[类别],'商品表'[类别])
但是CONTAINS返回的是True和False
现在我问你,销量在哪张表中?答:在销售表中,现在应该用上面的条件筛选哪张表?答:筛选销售表。
FILTER('销售表',CONTAINS('促销','促销'[年],RELATED('日期表'[年]),'促销'[月],RELATED('日期表'[月]),'促销'[类别],RELATED('商品表'[类别])))
// 因为Filter创建的是行上下文逐行迭代,销售表是多端,日期和商品表是一端表,多端找一端使用关系函数RELATED
【4】最后写度量值
方法一:Calculate([总销量],filter创建的表)
如果你感觉filter遍历整个销售表太慢了,也可以利用summzrize+扩展表原理,得到你需要的行和列。详见本文章:五、利用扩展表与交集的特性公式tb1 来代替销售表
方法二:Sumx(filter创建的表,'销售表'[销量])
返回结果:

《权威指南》给出的建议,它认为这样写速度慢,因为Filter迭代的整个销售表如果数据量大会影响性能。Sumx又重复迭代每一行,数据量大了以后,这是非常可怕的事情。
四、利用数据沿袭特性
《DAX神功》第2卷第21回 我们详细讲解了数据沿袭
【1】获取促销表中的所有列
如果在新建表里面,我们可以使用SUMMARIZECOLUMNS,但是要写到度量值中,由于SUMMARIZECOLUMNS不支持大部分度量值,我们选择使用SUMMARIZE。
tb1 = SUMMARIZE('促销','促销'[类别],'促销'[年],'促销'[月])
他返回的就是促销表,与我们使用表构造函数写出来的{"张三","李四","王五"}是一个意思
【2】数据沿袭打标记要换SUMMARIZE顺序来
tb2 = TREATAS(tb1,'商品表'[类别],'日期表'[年],'日期表'[月])
按常理来说,SUMMARIZE这一步是多余的,我直接使用促销表不香吗?问题就在数据沿袭这里,他需要按你提取出来的列顺序去在相关表上打标签。
【3】写出完整度量值
《DAX神功》第2卷第21回 我们证明了
TREATAS({"张三","李四"},Sheet1[姓名]) 相当于 'Sheet1'[姓名] in {"张三","李四"}
在Calculate的筛选器中使用这种布尔筛选时,相当于指定列取消筛选,恢复筛选的方法可以使用Keepfilters。
返回结果:

五、利用扩展表与交集的特性
《DAX神功》第3卷第8回 INTERSECT函数

销售表是多端表,商品表和日期表都是一端表,并且连线成一对多关系,那么,销售表的扩展表就是销售表、商品表、日期表中的所有列。我可以使用SUMMARIZE取我需要的列:
tb1 = SUMMARIZE('销售表','商品表'[类别],'日期表'[年],'日期表'[月])
让这张表与
tb2 = SUMMARIZE('促销','促销'[类别],'促销'[年],'促销'[月])
取共有部分

这两张表在INTERSECT中的位置一定要注意:
《DAX神功》第3卷第8回 我们提到INTERSECT函数交换参数位置影响数据沿袭

我们需要保留tb1中列的数据沿袭(列名虽然一样,但是不来自一张表,tb2都来自己促销表,而tb1来自销售表的扩展表),但又需要tb2中的数据,这时使用INTERSECT取交集,保留第1参数数据沿袭是最好的方法。
返回结果:

六、总结
从速度上讲利用数据沿袭特性和利用扩展表与交集的特性,会比CONTAINS函数快,但是国内企业利用率更高的还是我第一个就讲的DATESBETWEEN函数。

《孙兴华讲PowerBI火力全开》PowerBI必学课程
https://www.bilibili.com/video/BV1qa4y1H7wp
《DAX神功》文字版合集:
https://www.bilibili.com/read/readlist/rl442274
《DAX神功》视频版合集:
https://www.bilibili.com/video/BV1YE411E7p3
《DAX神功番外篇》文字版合集:
https://www.bilibili.com/read/readlist/rl478271
PowerBI(DAX函数)、PowerQuery(M函数)、Python办公自动化、Python爬虫、Python数据分析、ExcelVBA、WordVBA、AccessVBA、MySQL等等

