首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >彻底理解 PowerBI DAX 函数 EARLIER

彻底理解 PowerBI DAX 函数 EARLIER

作者头像
BI佐罗
发布2021-02-08 21:07:19
发布2021-02-08 21:07:19
2.6K10
代码可运行
举报
文章被收录于专栏:PowerBI战友联盟PowerBI战友联盟
运行总次数:0
代码可运行

很多业务背景的伙伴进入 DAX 世界后,第一个拦路虎就是 EARLIER。

因为这是我们业务人员平时不用的思维逻辑:迭代。

迭代,是区分文科与理科;业务与 IT 的标志性思维逻辑。

迭代,在传统的编程领域又叫循环,迭代是循环的等价。

本文让您彻底理解 EARLIER 以及迭代,不管你是什么背景。

什么叫迭代

迭代,简单说就是数数;而精确说,就是:对已知集合中元素的遍历。

例如:我们有一个集合:{ 1, ... , 10 },对这个集合的遍历,就是挨个看一遍。

例如:我们有一个集合:{ 苹果,鸭梨,橘子,香蕉 },对这个集合的遍历,就是挨个看一遍。

从这个意义上来说,迭代本身是很傻的,光看不做事。

所以,伴随着着迭代,一般都得做点坏事,不然不白迭代了,白看了。

理解 DAX 中的 SUM

在 DAX 中,SUM 的用法如下:

代码语言:javascript
代码运行次数:0
运行
复制
[Sales] := SUM( Order[Amount] )

它完全等价于:

代码语言:javascript
代码运行次数:0
运行
复制
[Sales] := SUMX( Order , Order[Amount] )

其含义为:

对 Order 表进行迭代,并把遍历到的数据行的 [Amount] 列的值提取出来全部加起来。

从逻辑上来讲,SUMX 有两个重要动作:

  • 在遍历的元素的时候提取元素
  • 最后在遍历完成时全部加起来

注意:实际 DAX 引擎的物理执行可能与此不同,但逻辑上可以这么理解。

SUMX 中隐藏的迭代器

SUMX 的执行如下:

代码语言:javascript
代码运行次数:0
运行
复制
SUMX( Order , Order[Amount] ) = 

【 迭代开始{ 】

    row1 , 取出当前行 [Amount]
    row2 , 取出当前行 [Amount]
    row3 , 取出当前行 [Amount]
    row4 ,取出当前行 [Amount]
    ...
【 } 迭代完成 】

【【将所有取出的值相加并返回。】】

其中:

  • 【迭代开始{ ... } 迭代完成】为隐藏的内置迭代器
  • 【【..】】为迭代完后的内置操作

由于这两步逻辑被 SUMX 隐藏了,很多业务背景的伙伴就不得理解:

  • 到底迭代器长成啥样
  • 迭代里面发生了啥
  • 迭代完了发生了啥

以上图示为了更加形象地说明这个过程。

迭代中的行上下文

在 DAX 中,表可以是这样的:

代码语言:javascript
代码运行次数:0
运行
复制
{ 1 , 2 , 3 }

// 或者这么写
{
    1,
    2,
    3
}

这会得到:

由于没有给这个表的列起名字,这一列默认叫 Value,那么来考察:

代码语言:javascript
代码运行次数:0
运行
复制
    SUMX (
        {
            1,
            2,
            3
        },
        [Value]
    )

可以得到:

在迭代中,可以从[Value]列取出值必须要能锁定当前的行,就是行上下文。

也就是说,在迭代中,遍历的每个元素就是行上下文,在行中可以取出列值,否则无法做到。

注意 很多初学者问:什么叫行上下文?其实更应该换一个问法。 一个更好的问题应该是: 在迭代一个集合的时候,DAX 是否有什么机制来让用户可以操作正在遍历的元素? 回答: DAX 有这种机制,并起名叫:行上下文,用来取出迭代中正在遍历的元素。

这很好,正如我们的预期。

进入烧脑阶段开始。

多层迭代 - 同名覆盖

请考察:

代码语言:javascript
代码运行次数:0
运行
复制
    SUMX (
        { 1, 3 },
        SUMX ( { 1, 2 } , [Value] * [Value] )
    )

对于 { 1 , 3 } 来说,它有一列叫:[Value];

对于 { 1 , 2 } 来说,它也有一列叫:[Value]。

注意 由于两个 SUMX 的出现,也就出现了两套嵌套的迭代器,这就形成了在 迭代 中的 迭代,也就是:多层迭代。

那么,此处的 [Value] * [Value] 到底指的是:谁的[Value] * 谁的[Value] 呢?

其执行细节如下:

代码语言:javascript
代码运行次数:0
运行
复制
[Value]    [Value]
    1
            1        1 * 1 
            2        2 * 2
    3
            1        1 * 1 
            2        2 * 2

这里用 TAB 的缩进表示了不同的层次。

请仔细观察上述内容,理解其规律。可以知道:[Value] 指的是 { 1, 2 } ,这是合理和自然的。也就是说:

代码语言:javascript
代码运行次数:0
运行
复制
迭代     迭代
[Value]    [Value]
    1
            1        当前行上下文[Value] 1 * 当前行上下文[Value] 1 = 1
            2        当前行上下文[Value] 2 * 当前行上下文[Value] 2 = 4 
    3
            1        当前行上下文[Value] 1 * 当前行上下文[Value] 1 = 1
            2        当前行上下文[Value] 2 * 当前行上下文[Value] 2 = 4

是实际执行的逻辑结构。其结果如下:

不难发现,其结果是预期的,同时发现:

根本没有用到 { 1 , 3 } 以及其中的元素,在更复杂的场景中,业务上需要:在内层可以访问到外层同名的列。

多层迭代 - 内外跨层穿越

考察上面已经有的结构:

代码语言:javascript
代码运行次数:0
运行
复制
迭代     迭代
[Value]    [Value]
    1
            1        当前行上下文[Value] 1 * 当前行上下文[Value] 1 = 1
            2        当前行上下文[Value] 2 * 当前行上下文[Value] 2 = 4 
    3
            1        当前行上下文[Value] 1 * 当前行上下文[Value] 1 = 1
            2        当前行上下文[Value] 2 * 当前行上下文[Value] 2 = 4

如果我们希望这样呢:

代码语言:javascript
代码运行次数:0
运行
复制
迭代     迭代
[Value]    [Value]
    1
            1        当前行上下文[Value] 1 * 上一层迭代的行上下文中的[Value] 1 = 1
            2        当前行上下文[Value] 2 * 上一层迭代的行上下文中的[Value] 1 = 2
    3
            1        当前行上下文[Value] 1 * 上一层迭代的行上下文中的[Value] 3 = 3
            2        当前行上下文[Value] 2 * 上一层迭代的行上下文中的[Value] 3 = 6

在 DAX 中就需要这样编写,如下:

可以看出:

EARLIER( C , X ) 的精确语义是:

上 X 层迭代的行上下文中的列 C 的值。

若 X = 1,可以忽略,将 EARLIER( [Value] , 1 ) 简写为 EARLIER( [Value] )。

那么,函数 EARLIER 就起到了跨层穿越的效果。

多层迭代 - 默认穿越

在理解上述内容后,来看一道测试题吧:

代码语言:javascript
代码运行次数:0
运行
复制
    VAR A = SELECTCOLUMNS( { 1, 3 } , "A" , [Value] )
    VAR B = SELECTCOLUMNS( { 1, 2 } , "B" , [Value] )
    RETURN SUMX (
        A,
        // 这里需要放一句话
    )

这里需要放一句话,请选择:

  • 选项 1 - SUMX ( B , [B] * [A] ),且选项 2 语法错误
  • 选项 2 - SUMX ( B , [B] * EARLIER( [A] ) ),且选项 1 语法错误

在不做实验的情况下,请选择 A 还是 B 呢?

如果选项 1 对,那么说明:[A] 的访问,由于无同名列的遮挡,不需要跨层,也不能跨层,所以不能用 EARLIER;

如果选项 2 对,那么说明:[A] 的访问,虽然无同名列的遮挡,由于不同层,必须要跨层,所以必须用 EARLIER。

而实际的结果是:

在这个场景中,SUMX ( B , [B] * [A] ) 与 SUMX ( B , [B] * EARLIER( [A] ) ) 完全一致。

结果如下:

以及:

也就是说,在这个场景下的 SUMX ( B , [B] * [A] ) ,如下:

代码语言:javascript
代码运行次数:0
运行
复制
   迭代     迭代
   [A]       [B]
    1
            1        当前行上下文[B] 1 * 当前行上下文[A] 1 = 1
            2        当前行上下文[B] 2 * 当前行上下文[A] 1 = 2 
    3
            1        当前行上下文[B] 1 * 当前行上下文[A] 3 = 3
            2        当前行上下文[B] 2 * 当前行上下文[A] 3 = 6

完全等价于这个场景下的 SUMX ( B , [B] * EARLIER( [A] ) ) ,如下:

代码语言:javascript
代码运行次数:0
运行
复制
   迭代     迭代
   [A]       [B]
    1
            1        当前行上下文[B] 1 * 上一层迭代的行上下文中的[A] 1 = 1
            2        当前行上下文[B] 2 * 上一层迭代的行上下文中的[A] 1 = 2 
    3
            1        当前行上下文[B] 1 * 上一层迭代的行上下文中的[A] 3 = 3
            2        当前行上下文[B] 2 * 上一层迭代的行上下文中的[A] 3 = 6

这让我们得到以下结论:

  • DAX 提供了迭代中需要访问当前元素的机制,叫做:行上下文。
  • 迭代是可以嵌套的。
  • 在嵌套的迭代中,内层可以访问外层。
    • 若列不遮挡,也就是使用不同层的不同名列,则可以直接访问,也可以使用 EARLIER 显式指定要访问的相对第 X 外层。
    • 若列有遮挡,也就是使用不同层的相同名列,则默认使用内层,这必须使用 EARLIER 显式指定要访问的相对第 X 外层。
  • 不论是内层或者外层,都处于(或有自己)相应的行上下文。

总结

要理解 EARLIER 就要知道行上下文;

要理解 行上下文就要知道迭代;

迭代是遍历大量元素的逻辑手段;

在数据模型中,大量元素以逻辑行的形式存在于表中;

遍历表的元素,就是表的行;

从正在遍历(迭代)的行中取出值需要一个机制来框住当前行,称为:行上下文;

迭代是可以多层嵌套的;

从更内层迭代中的行上下文可以访问相对外层迭代中的行上下文,这时使用 EARLIER 即可。

EARLIER,顾名思义,更早的,意思为更早创建出的行上下文。

用 EARLIER 实现的这种牛叉特性可以起一个牛叉的名字:跨层穿越。

BI佐罗讲解的 DAX 是从本质层面进行的,本质不表示大而全,而是逻辑的完备和简洁,学习 EARLIER 竟不需要任何一个业务表,因为基本数学知识足以。学习《BI真经》,窥见更多本质。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PowerBI战友联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么叫迭代
  • 理解 DAX 中的 SUM
  • SUMX 中隐藏的迭代器
  • 迭代中的行上下文
  • 多层迭代 - 同名覆盖
  • 多层迭代 - 内外跨层穿越
  • 多层迭代 - 默认穿越
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档