从数据到信息
从信息到洞察

行上下文嵌套和EARLIER

了解行上下文嵌套

同一张表有多层嵌套的行上下文似乎很少见,但实际上这种情况经常发生。让我们用一个例子来解释这个概念。假设你想针对每个产品计算价格高于它的其他产品的数量。本质上这将根据价格对产品进行排序。

为了解决这个问题,我们使用 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 迭代的当前行的单价,这是最内层的迭代。因此,这个新的行上下文隐藏了计算列引入的原始行上下文。你看到问题了吗?你希望访问单价的当前值,但不要使用最后引入的行上下文。相反,你希望使用之前的行上下文,即计算列中的那个。

DAX 提供了一种使其成为可能的函数:EARLIER。EARLIER 使用前一个行上下文而不是最后一个上下文检索列的值。因此,你可以使用 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

在结束这个示例之前,值得注意的是,如果你想将结果转换为一个更合理的排序(排名从 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。现在需要按天统计完成每个步骤的用户数,给出计算列和度量值两种写法

通过人数 =
CALCULATE (
    COUNTA ( '表 1'[用户 id] ),
    FILTER ( '表 1', [步骤 id] >= EARLIER ( [步骤 id] ) && '表 1'[date] = EARLIER ( [date] ) )
)
通过人数 :=
AVERAGEX (
    ADDCOLUMNS (
        '表 1',
        "通过次数", CALCULATE (
            COUNTROWS ( '表 1' ),
            FILTER ( '表 1', '表 1'[步骤 id] >= EARLIER ( '表 1'[步骤 id] ) )
        )
    ),
    [通过次数]
)

 

小测试

测试表两个计算列的结果

计算列 1 = COUNTROWS(FILTER('测试表',EARLIER('测试表'[月份])='测试表'[月份]))

计算列 2 = COUNTROWS(FILTER('测试表',EARLIER('测试表'[月份])="1 月"))

基于上面的公式,你认为这两列的结果是什么,原因是?

此处为隐藏内容 VIP会员和付费用户可见

 

下载面板

以上隐藏内容查看价格为2G 币,请先
注:加入 VIP 会员可享受全站权益,性价比更高。单独购买的内容长期有效,不受时间限制。

6
说点什么

avatar
1000
 
鼓掌微笑开心憧憬爱你色并不觉得吃瓜doge二哈喵喵思考笑哭捂脸悲伤大哭抓狂汗偷笑打脸捂眼黑线问号晕拜拜闭嘴衰咒骂ok作揖
3 评论数
3 被回复的评论
4 订阅评论的人数
 
查看最近回复
查看最热评论
  订阅本文评论  
最新 最旧 得票最多
提醒
suesu
游客
suesu

那个例子的度量值写法其实不是很明白,为啥里面还是用到了新增列【通过人数】?能具体解释一下吗?

liu
游客
liu

文末作业处两列结果的差异原因是啥?

墨熙
成员
墨熙

文章的频率能否更新的更快些啊,比如一天2篇啥的 微笑 微笑 微笑