PowerBI之DAX神功:答网友问04 懒惰计算与迭代函数转换上下文
Ps:函数内层、外层行数与转换问题,我们上节《DAX神功》第2卷第2回:理解迭代函数的行数中做了详细解释。
提问网友的需求:获取每个月份最大销售金额及产生最大金额的销售日期


需求结果:

前景回顾:
上节课我们介绍了两种度量值写法:
销售峰值1 = maxx('日期表',calculate(sumx('销售表',RELATED('商品表'[售价])*'销售表'[销量])))
销售峰值2 = maxx('日期表',sumx(RELATEDTABLE('销售表'),RELATED('商品表'[售价])*'销售表'[销量]))
度量值还可以怎么写?
销售金额 = sumx('销售表',RELATED('商品表'[售价])*'销售表'[销量])
销售峰值3 = maxx('日期表',[销售金额])
因为【销售金额】是度量值,等同于外面套了一个Calculate,度量值将行上下文转成筛选上下文。
至此网友提问的第一问回答完毕:获取每个月份最大销售金额

正片开始:
网友提问:《The Definitive Guide to DAX》中关于求销售峰值发生日期的公式不理解
也是我们今天问题的第2问:产生最大金额的销售日期
发生日期 =
var x=[销售峰值3]
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=x)
return
IF(COUNTROWS(tb)=1,tb,BLANK())

第1步:理解度量值
销售金额 = sumx('销售表',RELATED('商品表'[售价])*'销售表'[销量])
// 销售表每一行的销量与商品表对应的商品售价相乘的结果,这个【销售金额】是度量值,它将sumx从行上下文转换成了筛选上下文。

销售峰值3 = maxx('日期表',[销售金额])
// 大多数人,是因为不理解这个公式,但是他不知道问题出在这里,而是直接研究度量值【发生日期】,造成看不懂。
先写出【销售峰值3】的等价公式:
销售峰值3等价 = maxx('日期表',calculate(sumx('销售表',RELATED('商品表'[售价])*'销售表'[销量])))
内层:

内层的:calculate(sumx('销售表',RELATED('商品表'[售价])*'销售表'[销量]))
其实就是一个值12,但是这个值具有筛选功能。
外层:
销售峰值3等价 = maxx('日期表',calculate(12))

如果我们使用字段【月】,来筛选销售峰值3等价

现在我们需要知道,每个月销售峰值出现在哪一天?
第2步:理解筛选表
// 因为我们需要的最大销售金额发生日期这个度量值是一个将日期表中最大销售金额所在的日期返回成标量值。《DAX神功》第1卷第10回 将表用做标量值,所以用到Values也可以使用DISTINCT。

其实就是找到3和5那两个日期,就是每个月最大金额的产生日期
表1=FILTER(VALUES('日期表'[日期]),[销售金额]=3)

表2=FILTER(VALUES('日期表'[日期]),[销售金额]=5)

表3 = FILTER(VALUES('日期表'[日期]),[销售金额])
// 有销售金额的只有这4个日期,表1和表2就是从这4个日期中找出3或5的日期

表4 = FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额]))

// 因为现在我们只是新建表,并没有参与筛选,所以它显示的是maxx('日期表',[销售金额])的值5所对应的日期2020/2/1。为了证明:我们将表4放到度量值中
证明1 = CALCULATE([销售金额],FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额])))
证明2 = Countrows(FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额])))

所以,我们【发生日期】度量值是这样来写的:
详见《DAX神功》第1卷第10回 将表用做标量值 对此做出了详细讲解
发生日期 =
IF(
COUNTROWS(FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额])))=1,
FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额])),
BLANK()
)
// 例如1月最大值是3,如果只有一行,我们就返回那个日期,否则返回空
// 有些人问?为什么会有否则?假设1月有5天都是3,且最大值为3,那你就不能返回哪天是最大值,这个日期也不只一行,所以返回空。
第一次简化公式:
详见《DAX神功》第2卷第1回 VAR变量
发生日期 =
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额]))
return
IF(COUNTROWS(tb)=1,tb,BLANK())
第二次简化公式:
发生日期 =
var x=[销售峰值3]
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=x)
return
IF(COUNTROWS(tb)=1,tb,BLANK())
// 为什么不能直接写成 tb=FILTER(VALUES('日期表'[日期]),[销售金额]=[销售峰值3])
我们看看这种情况会发生什么?
表5 = FILTER(VALUES('日期表'[日期]),[销售金额]=[销售峰值3])

tb=FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额]))
maxx('日期表',[销售金额])是行上下文,
而[销售峰值3]是筛选上下文,我们可以利用懒惰计算的特性:
<1> 没有被使用过的变量永远不会被计算
<2> 当变量完成了首次计算,则它不会在同一范围内被再次计算。
var x=[销售峰值3]
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=x)
这时只计算,[销售金额]=[销售峰值3],而[销售峰值3]是由maxx('日期表',[销售金额])生成的
其实就是 [销售金额]=maxx('日期表',[销售金额])
如果你写成:[销售金额]=[销售峰值3],那就相当于:
[销售金额]=Calculate(maxx('日期表',[销售金额]))
如果你对惰性计算不熟悉,我建议这样写:
发生日期 =
var x=maxx('日期表',[销售金额])
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=x)
return
IF(COUNTROWS(tb)=1,tb,BLANK())
或者
发生日期 =
var tb=FILTER(VALUES('日期表'[日期]),[销售金额]=maxx('日期表',[销售金额]))
return
IF(COUNTROWS(tb)=1,tb,BLANK())

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