DAX学习之理解上下文

作者: 徐胥 | 来源:发表于2020-02-06 22:53 被阅读0次

    DAX圣经第二版出世后,一直想要用心读一遍查漏补缺。事实证明体系化的阅读是非常有必要的。读完第四章,对于计算上下文的理解既轻松又简单。本文会阐述当前我对DAX计算上下文的理解,做此记录,希望共有裨益。

    DAX中存在计算上下文、行上下文、筛选上下文,初学者往往晕头转向不知众博客所云。这些上下文是DAX后续计算的脉络,如人体动静脉,不懂上下文就无法理解函数的运行奥秘,只能死记硬背背了又忘。

    所谓计算上下文,依我理解就是DAX的计算环境,我有一个数据模型,数字们在函数中是如何组队形成最终结果的,这整个一环境就是计算上下文。而计算上下文主要就分为行上下文和筛选上下文两种。也就是说,一个函数怎么计算,关注其和行上下文的关系,和筛选上下文的关系,计算环境就大致告成了。

    一、谈谈行上下文

    谈到行上下文,有些同学就会想到,在报表前端拉一张数据表或是矩阵表,指着其中的行列就说,这是行上下文。对吗,当然不对。

    从数据角度来说,一切报表前端的内容都是表象,我们真正在使用的是我们的基础表,是数据模型中的表,是构成表关系的表。

    所以计算的本质,在于计算我们的基础表。行上下文作为计算上下文,也就是计算环境中的一大组成部分,其针对的对象自然也是我们的基础表。行上下文就是对基础数据表的遍历。给没学过变成的同学解释一下什么是遍历:我有一百行数据,我每行数据都读了一遍,这就是遍历。

    什么时候会出现行上下文呢?有两种情况。

    ①列计算的时候,行上下文就会自动出现。其实也很好理解嘛,你增加了一个新列,可不得每行都走一遍,才能说新建了一个列。一个新建列中,不能出现一个“黑洞”行吧:)

    ②度量值计算的时候,需要我们加入迭代器,行上下文就会出现。迭代器,可以理解为把基础表每一行都遍历一遍的工具。举个例子,基本所有带X的函数,本质上都是个带特殊技能的迭代器。比如SUMX:

    Sales Amount = SUMX (Sales, Sales[Quantity] * Sales[Price] )

    --注释:Sales表中, 存在Quantity列和Price列。对每行数据都进行 [Quantity] * [Price]的计算,此时存在一个新的虚拟列,用于存放每行[Quantity] * [Price]的结果值。将这个虚拟列进行求和。

    在上面的这个例子中,我引进了一个虚拟列的概念,便于各位理解。其实和①中相似了对吗,只要需要对行数据产生新值或是计算(计算值也是新值),那么不管你是真实列还是虚拟列,都产生了行上下文。

    值得注意的是,行上下文只和单表对象有关。如果A表和B表是一对多的单向关系,A表发生行上下文,也就是A表被遍历和迭代了,这对B表有影响吗?

    答案是没有。因为遍历存在于一张表中。举上面的例子,假设A表中有Quantity列和Price列,我对这两列进行SUMX的遍历求和,虽然B表和A表存在关联关系,但针对列的遍历是无法跳到B表上去执行的。

    那如果A表有Price列,B表有Quantity列,我们有需求对A表和B表进行同时遍历怎么办?一种同学会想,那我在一开始导入数据时,对A表和B表做left join的关联查询呀,把B表的Quantity列取到A表中,就又可以在一张表中执行遍历了。这是一种方案,但如果我们在建模时已经做好了A表和B表的一对多单向关系,不妨直接用DAX函数进行关联取列。

    我们引入Related和Relatedtable这两个函数,解决该问题。这两个函数通常也是为了实现多表间的行上下文存在的,即实现多表间的同时迭代。从函数名的字面理解看,我们大致也能猜到,Realtedtable返回的是一张表,那么相对应的,Realted返回就是一个单值。这是为了匹配我们关系中的一对多和多对一而存在的。

    在上面的例子中,我们已经假设A表和B表是一对多的单向关系,A是一端,B是多端。也就是说,一个A表值可能对应到多个B表值。那么从A表出发,我们使用关系函数的时候,B表应该返回多值,是一张表的形式,所以在这个例子中,我们应该使用Relatedtable(B)。

    二、谈谈筛选上下文

    筛选上下文其实也很好理解,就是筛选嘛。你这计算环境中存在筛选吗,这也是DAX计算时需要考虑的事情。

    PBI表格

    大家可能觉得,筛选嘛,还能不认识啊。别说,真有人不认识,以上PBI表格中,有哪几个筛选?筛选上下文中的筛选,和我们狭义的筛选并不一致,除了切片器,页面级筛选、报表级筛选,还存在行筛选、列筛选。如上图所示,2016、2017、苹果、三星、小米,这都是筛选器。我们的度量值只有一个sum,但我们的结果却被这些行和列分割得七七八八。

    值得注意的是,筛选上下文是早于DAX计算的。也就是说,在公式计算之前,筛选上下文就已经发挥作用了。比如上图中第一个计算格中的15574.7是怎么来的?在“年”=2016年和“品牌”=苹果的数据中,我去计算销售总和,得到15574.7。

    上面我们讲了行上下文只作用于单表对象,作用到其他表时,我们需要用到关系函数才可以实现。筛选上下文的作用对象是什么呢?答案是数据模型。也就是说,只要A表和B表存在关系,且关系可以正向流通,那么筛选就会自动传递。

    三、一个实例(嵌套行上下文)

    一个非常好玩的例子,如果我们不会用Rank函数,我们该如何求出销售经理的销量排名。思路是,找出比我销量高的销售经理,他们的人数+1就是我的排名。以下是我们的DAX demo:

    Rank =

    VAR CurrentPersonAmount = Sales[Amount]

    VAR PersonBeforeMe = CountRows(Filter(Sales[Amount],Sales[Amount]>CurrentPersonAmount))

    Return PersonBeforeMe+1

    这边涉及两个迭代,一个是新增Rank列时,PBI会自动产生一个行上下文,得到这个行上下文作用的是变量CurrentPersonAmount,也就是说,变量CurrentPersonAmount中的Amount值,会自动遍历一遍Sales表。

    而第二个迭代,其实是嵌套在第一个迭代中的,由Filter产生。Filter是个带特殊功能的迭代器,他会对Sales表中的Amount列,逐个进行遍历。

    可能有些同学没听懂,我把内部发生的事情再阐述一遍:当CurrentPersonAmount处于Sales表的第一行时,执行一次变量PersonBeforeMe的计算,此时变量PersonBeforeMe中CurrentPersonAmount的值,依旧是第一行的Amount,但PersonBeforeMe中的Sales[Amount],将会遍历一遍Sales表,这是Filter这个迭代器发挥的作用。这边会依次选出大于第一行Amount值的所有Amount值,以便返回第一行Amount前面有几个比他大的Amount。

    这样,第一个Amount的排名就基本确定了,接下来,CurrentPersonAmount跳到Sales表的第二行,再次执行变量PersonBeforeMe的计算,以此类推。

    总结一下,被嵌套的行上下文,如果需要引用外部行上下文的内容,可以用变量的形式进行传递。

    一个小检测,此时的你,能理解earlier函数的含义了吗?提示,earlier函数就是为了引用外部行上下文的内容而产生的函数。。

    相关文章

      网友评论

        本文标题:DAX学习之理解上下文

        本文链接:https://www.haomeiwen.com/subject/qvxpxhtx.html