打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
EARLIER CASE
了解行上下文嵌套
文中:按天统计完成每个步骤的用户数例子文件
date
用户id
最大步骤id
2019/11/1
a
1
2019/11/1
z
1
2019/11/1
c
3
2019/11/1
d
4
2019/11/1
e
5
2019/11/1
f
6
2019/11/2
g
7
2019/11/2
h
8
2019/11/2
i
9
2019/11/2
j
10
2019/11/2
k
11
2019/11/2
w
2
2019/11/2
l
12
2019/11/3
m
13
2019/11/3
n
14
2019/11/3
o
15
2019/11/3
p
16
2019/11/3
q
17
2019/11/3
r
18
同一张表有多层嵌套的行上下文似乎很少见,但实际上这种情况经常发生。让我们用一个例子来解释这个概念。假设你想针对每个产品计算价格高于它的其他产品的数量。本质上这将根据价格对产品进行排序。
为了解决这个问题,我们使用FILTER 函数,FILTER 是一个迭代器,它迭代表的所有行,并返回一个新表,其中只包含满足第二参数的行。例如,如果要检索价格高于 100 美元的产品列表,可以使用:
= FILTER ( Product, Product[UnitPrice] > 100 )
细心的读者会注意到,FILTER 需要具备迭代功能,因为只有当产品表存在有效的行上下文时,才能计算表达式 Product[UnitPrice]>100。否则单价的有效值将是不确定的。FILTER 的确是一个迭代函数,它为第一个参数中的表的每一行创建行上下文,从而可以在第二参数中计算条件。
现在让我们回到原来的问题:创建一个计算列,对那些比目前产品价格更高的产品计数。如果将当前产品的价格命名为PriceOfCurrentProduct,就很容易理解下面的伪 DAX 公式将满足你的需求:
Product[UnitPriceRank] =
COUNTROWS (
FILTER (
Product,
Product[UnitPrice] > PriceOfCurrentProduct
)
)
FILTER 将筛选出那些比当前产品价格更高的产品,且COUNTROWS 对那些由 FILTER 返回的表中的行的数目进行了统计。剩下唯一的问题是如何用有效的 DAX 语法来替换 PriceOfCurrentProduct,来表达当前产品价格(所谓「当前」,意思是计算列的当前行),这可能比你想象的要难。
EARLIER 出场
我们在产品表中定义这个新的计算列。因此,DAX 将在行上下文中对表达式求值。但是,表达式使用了 FILTER 函数在同一个表上创建了一个新的行上下文。实际上,在前一个表达式的第 5 行中使用的 Product[UnitPrice]是由 FILTER 迭代的产品表的当前行的单价,这是最内层的迭代。因此,这个新的行上下文隐藏了计算列引入的产品表的原始行上下文。你看到问题了吗?你希望访问单价的当前值,但不要使用最后引入的行上下文(FILTER 迭代的那个)。相反,你希望使用之前的行上下文,即计算列中的那个。
DAX 提供了一种使其成为可能的函数:EARLIEREARLIER 使用前一个行上下文而不是最后一个行上下文检索列的值。因此,你可以使用EARLIER (Product[UnitPrice])来表示PriceOfCurrentProduct的值。
EARLIER 语法
EARLIER ( <ColumnName>, [<Number>] )
返回<ColumnName>列在外部,第<Number>层行上下文对应的值,其中<Number>是可选参数。
EARLIER 是 DAX 中最特立独行的函数。许多用户之所以对 EARLIER 感到害怕,是因为并未按照行上下文来思考,也没有考虑过行上下文可通过对同一表格创建多个迭代而实现嵌套这一事实。在现实中 EARLIER 是一个简单且有用的函数,且可变得熟能生巧。解决该问题的代码如下:
Product[UnitPriceRank] =
COUNTROWS (
FILTER (
Product,
Product[UnitPrice]
> EARLIER ( Product[UnitPrice] )
)
) + 1
在下图中,你可以看到产品表中定义的计算列,它使用单价的降序排序。
UnitPriceRank 列是演示 EARLIER 如何在嵌套行上下文中导航的示例
因为单价相同的产品有十四种,所以排名都是 1;第十五种产品排名为 15,与其他产品价格相同。建议你仔细研究和理解这个小示例,因为这是一个非常好的测试,可以检查你使用和理解行上下文的能力、如何使用迭代器(在本例中为 FILTER)创建行上下文,以及如何通过 EARLIER 从外部访问自身的值。
EARLIER 第二参数
EARLIER 接受第二参数,即要跳过的层数,这样你就可以跳过两层或多层行上下文。此外,还有一个名为EARLIEST 的函数,它允许你直接访问表的最外层行上下文。老实说,EARLIEST 和 EARLIER 的第二个参数都不经常使用:虽然有两个嵌套的行上下文是常见的场景,但是有三个或更多的行上下文很少发生。
图解多层行上下文     图片:exceleratorbi.com.au
只有在同一种行上下文存在嵌套的时候才需要 EARLIER。如果 A,B,C,D 分别来自不同的表,你可以直接引用它们的列值,不需要使用 EARLIER
在结束这个示例之前,值得注意的是,如果你想将结果转换为一个更合理的排序(排名从 1 开始,之后每个名次加 1,即创建一个序列 1,2,3…),只要对价格计数而不是产品就可以了。这时,你可以借助VALUES 函数:
Product[UnitPriceRankDense] =
COUNTROWS (
FILTER (
VALUES ( Product[UnitPrice] ),
Product[UnitPrice]
> EARLIER ( Product[UnitPrice] )
)
) + 1
UnitPriceRankDense 提供了更理想的排名,因为它计算的是价格,而不是产品
EARLIER 的使用建议
EARLIER 是一个作用比较抽象的函数,当你掌握了变量的用法之后,EARLIER 函数就可以被完全替换掉了。但是从理解多层行上下文的角度出发,我仍然建议你彻底地学习和理解 EARLIER,尤其是初学者。
定义变量(VAR)来代替 EARLIER 的好处是会使代码更易于阅读。例如,你可以使用以下表达式代替之前的计算列:
Product[UnitPriceRankDense] =
VAR CurrentPrice = Product[UnitPrice]
RETURN
COUNTROWS (
FILTER (
VALUES ( Product[UnitPrice] ),
Product[UnitPrice] > CurrentPrice
)
) + 1
在这个示例中,通过定义变量,将当前单价存储在 CurrentPrice 中,并在稍后使用该变量来执行比较。为变量命名,可以使代码更易于阅读,而不必在每次阅读表达式时都通过遍历行上下文层级才能理解计值流。
EARLIER 只能用于计算列吗?
虽然我们通常都是在计算列中使用 EARLIER,但并不意味着 EARLIER 只能用于计算列,实际上只要存在多层行上下文都可以使用 EARLIER。只不过计算列因为自身提供行上下文,只需要再使用一个迭代函数即可实现两层行上下文,而度量值则需要嵌套两层迭代函数才能构建出 EARLIER 需要的环境,操作起来稍显繁琐,但是这种嵌套对于深入理解行上下文很有帮助,让我们通过下面这个案例介绍这两种用法:
案例原始数据
原始表包含 date、最大步骤 id 和用户 id 三列,最大步骤 id 代表完成的步骤数量,值越大说明该用户在当前日期完成的步骤越多,比如最大步骤 id=4 说明用户已经完成了步骤 1,2,3,4。
现在要求按天统计完成每个步骤的用户数,也就是只考虑来自 date 列和最大步骤 id 列的筛选器,
对于计算列使用的公式,需要注意忽略来自用户 id 的筛选
对于度量值,我们默认透视表已经提供了这两列作为外部筛选上下文,只需要在度量值中构建出双层上下文即可
计算列 _EARLIER
计算列 _ 变量写法
度量值 _EARLIER
度量值 _ 变量写法
度量值 _ 推荐写法
通过人数 =
CALCULATE (
COUNTA ( '表 1'[用户 id] ),
FILTER ( '表 1', [步骤 id] >= EARLIER ( [步骤 id] ) && '表 1'[date] = EARLIER ( [date] ) )
)
两种写法结果对比
度量值写法通过两个高亮的迭代函数ADDCOLUMNS 和 FILTER 构建了两层行上下文,使得 EARLIER 可以正常计值,内层度量值[COUNT]为表 1 的每行计算通过人数,由于这里的表 1 已经被透视表的 date 列和最大步骤 id 列筛选,如果筛选后的表存在多行,[COUNT]将得到相同的结果,所以外层使用AVERAGEX 取平均以确保获得准确结果。
度量值的前两种写法是出于演示 EARLIER 的目的,故意将公式复杂化,实际上如果只是解决问题本身,你完全可以用更简单的写法,参考最后一个度量值。需要指出的是,两者在明细行结果相同,总计行稍有不同:前两种写法在总计行计算的是整体的平均值,而最后一个写法只考虑最大步骤 id 一个筛选条件,这通常是没有意义的。
注:你可以在文章末尾下载到这个案例的源文件
小测试
测试表两个计算列的结果
计算列 1 = COUNTROWS(FILTER('测试表',EARLIER('测试表'[月份])='测试表'[月份]))
计算列 2 = COUNTROWS(FILTER('测试表',EARLIER('测试表'[月份])="1 月"))
基于上面的公式,你认为这两列的结果是什么,原因是?
答案
ElvisLee#4412
老师,最后的习题看了历史讨论后,经过一番消化后,我大致描述下我的理解计算逻辑,望指教:
1、在原表每行后的对应单元格生成一个虚拟表,这个虚拟表等同于原表
2、使用FILTER对每个虚拟表进行筛选,筛选的条件为:EARLIER('测试表'[月份])=”1月”,此处因为EARLIER返回的是原表的当前行的”1月”,所以FILTER的结果都是TURE;而进行到原表第二行时,EARLIER('测试表'[月份])的结果变为“2月”,筛选的条件:EARLIER('测试表'[月份])=”1月”,返回结果均为False;以此类推,对原表后12个单元格生成的虚拟表逐一进行筛选;
3、COUNTORWS统计筛选后的虚拟表的行数,返回对于单元格,显示结果
如果原表新增“13月”、“14月”、“15月”,则计算的结果也随之变为:13、14、15
计算列 1:筛选条件是 FILTER 函数的月份值等于当前计算列创建的行上下文的月份值,所以每行返回 1
计算列 2:此时 FILTER 函数的筛选条件是与一个固定的值「一月」做判断,这是一个静态布尔表达式,不会随着外部迭代行的变化而变化,只在计算列的第一行返回 True,其他行均为 False。进一步看,在计算列的第一行,公式内的 FILTER 迭代的表每行都返回 True,所以 COUNTORWS 计算了整张表。这里的关键是理解计算列 2 中 FILTER 的筛选条件其实是个静态的布尔表达式。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
DAX常见函数大全二
DAX 第三篇:筛选上下文 和 Filter函数
如何快速理解一个复杂的DAX?
CALCULATETABLE 函数 (DAX)
12 如何使用Evaluate做Excel数据查询?
使用上下文数据安全地评估JavaScript
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服