美文网首页CALCULATE
CALCULATE之庖丁解牛系列 - CALCULATE专解 (

CALCULATE之庖丁解牛系列 - CALCULATE专解 (

作者: PowerBI非官方 | 来源:发表于2018-09-25 11:00 被阅读552次

        第32式  CALCULATE与SUMIFS函数的行为对比

          参阅《DAX圣经》第4、5章相关部分 

          你是否记得曾经使用过Excel函数SUMIF( ),或者它的扩展版新函表SUMIFS()?由于DAX本身也是一种函数式语言,某种情况下:
          CALCULAT ( )的行为类似于SUMIF/SUMIFS
           
    只所以要讲一下SUMIF/SUMIFS函数,我们关注的是它们之间相同的函数功能所携带的行为,这对于理解DAX(特别是列表筛选)有莫大的帮助。同时,也开始了我们研究CALCULATE()用于计算的关键主题。

            Excel中的SUMIF()和SUMIFS()函数功能是:它们聚合指定的一个列,但是在公式中筛选掉不符合定义的筛选条件的那些行。例如,你可以使用SUMIF( )来汇总某一列的销售数据,但只针对年份列中仅包含2012年的表中的那些行。
            你不觉得这听起来很熟悉吗?这很像前面我们提到的DAX黄金法则 --- 先筛选,再计算(先列表,再值列表)”。这个相似的行为,在CALCULATE ()中被继续。
          但是,CALCULATE()又优于SUMIF()/SUMIFS()的三个基本方式:

          1、它有X系列函数的语法。这是三个优势中最小的一个,但感觉也很棒。该优势(迭代)可以帮助你创建一个更快捷的公式。
            2、它是一个“函数系列”,而不限于SUM/COUNT/AVERAGE。例如,在Excel中一直困扰着我们的是:没有MAXIF()/MAXX函数,也没有MINIF(),当然更加没有STDEVIF()。CALCULAT ()实际上的条件计算是无限的——它允许你使用任何聚合函数(甚至是一个复杂的多函数表达式),并快速生成一个多IF版本。
            3、它可以用于多个透视表 (作为度量的一部分),正常的SUMIF()则不能。

          我们知道, CALCULAT ()函数参数为:CALCULATE([ ],<filter1>,<filter2>,…)
          例如单条件的筛选计算:
          CALCULATE(SUM(Sales[Margin]),Sales[Year]=2001)

          例如多条件(and)筛选计算:
          CALCULATE([Sales per Day],
          Sales[Year]=2002,Sales[ProductKey]=313)

          让我们再从一个简单的透视表开始。例如一年来的[TotalSales]度量。好的,让我们添加一个总是被筛选到年份=2002的新度量:

          [2002Sales] = CALCULATE([Total Sales],Sales[Year] =2002)

          1、我们用一个<measure expression>的名称来表示CALCULATE <计算列表>参数。任何合法的表达式都是可以的---包括预先定义的度量值名称,或者可以用来定义度量的任何公式表达式;
          2、请注意,在<筛选>参数中,2002没有使用引号。这是因为年份列是数值型的。如果它是一个文本列,则需要使用:= " 2002 ";
         
    3、这一次我们只使用了一个<筛选>参数,但是我们可以在一个CALCULATE公式中使用我们想要的多个(N)参数;
          4、新度量在每一种情况下都与2002年对应的值相匹配!
          5、之前的结果:在2002年每个月分别返回对应的2002年全年的总销售结果。这正是我们想要的结果!

          CALCULATE()是如何工作的?

          我们之前已经学习了多个CALCULATE( )的例子,让我们来看看CALCULATE()是如何真正起作用的?因为这将在第一个示例中消除一些意料之外的结果。
          关于CALCULATE() 有三个要点,特别是关于<FILTER筛选器>参数:

          1、<filter-筛选>参数在度量CALCULATE的“筛选器”阶段操作。它们修改了透视表提供的列表筛选——这发生在筛选器引用源列表之前,因此也就在聚合计算阶段之前。
          2、如果<filter>参数作用于已在主元列上,它将重写该列的主筛选。在第一个例子中。 上面,透视表定义为Sales[Year]=2001(透视表筛选),但是,由于我们在CALCULATE ()内部公式中定义有Sales[Year]=2002,所以2001年透视表的“值”完全被CALCULATE ()覆盖,并成为2002年(其实是被公式过滤掉了)。这就是,为什么在第一个例子中,2001年和2003年的CALCULSTE()以及小汇总值都返回了2002年的销售数据。
            3、如果<filter>参数作用于在透视表上没有的列,则<filter>将完全添加到列表筛选。在我们的第二个例子中,我们在透视表上放置有Sales[MonthNum],而不是Sales[Year], Sales[Year]=2002的筛选器被应用于进入透视表中的当前月份。因此,我们得到2002年销售的第1个月,2002年销售的第2个月......等所有与2002年交叉的月份。即先筛选的是年份,然后才是该年份下的所有当前月份(透视表筛选)。
          因而,是时候在该DAX评估步骤中填写第2步了,以解释CALCULATE插入本身的位置,从而允许更改筛选器筛选:

          DAX公式步骤2,修订后的解释:CALCULATE()对筛选筛选的影响。

          先看看CALCULATE()的两个有用的例子:到目前为止,我们使用的[2002年销售]度量是向你展示CALCULATE()如何工作的好方法,但它似乎并不是很有用。所以需要再观看两个更广泛、适用的例子。

            示例1: 特定类型的事务计算

          这是我们在零售业务中经常遇到的:实际业务场景中,不是所有的交易都是正常的销售,一些企业记录了许多不同的交易类型,包括“正常交易”、“退款”、“促销交易”交易方式。
          假如我们的数据库有这样一个列,因此,我们将其导入到Sales表中(使用表属性)。这里,我们看到它有三个值:我们现在想写四种新方法,这里用英语定义:

    “Regular” Sales “常规”销售  ——  交易类型为1。
    “Promotional” Sales “促销”销售—交易类型为3。
    “Refunds”“退款”表示为负数,    —交易类型为2,

    “Net Sales”--净销售= 常规销售+ 促销销售 - 退款。
    [Regular Sales]    = CALCULATE([TotalSales],Sales[TransType]=1)
    [Promotional Sales]=CALCULATE([Total Sales],Sales[TransType]=3)[Refunds]=CALCULATE([Total Sales],Sales[TransType]=2) * -1)
    [Net Sales]=[RegularSales]+[Promotional Sales]+[Refunds]

          请注意,我们对(Refunds)的处理假定退款被记录在我们的销售表中有积极的价值。如果它们被记录为负值,你可以从[Refunds]度量中去掉*-1的乘法计算。这个思路是不是很简洁呢?
          然后,继续沿着实际的道路,让我们看看销售额有多少是由于促销活动引起的(即促销销售的占比情况),公式如下:

    [Pct Sales onPromo]=[Promotional Sales] / ([Regular Sales]+[PromotionalSales])[Pct Saleson Promo] 度量告诉了我们,销售收入来自促销活动的百分比。

          示例2:自创建一段时期以来的增长。

          我们将定义一个新的“基础”元度量,它跟踪在给定的时间段内有多少客户处于活动状态:

        [ActiveCustomers] =DISTINCTCOUNT(Sales[CustomerKey])

          “基本度量”是指不涉及其他度量的计算度量。即该度量是一个纯聚合定义的计算,就像上面的一样。我们提过这个概念:称之为“最简单的DAX(度量)”。
          现在,一项总是能告诉我们2001年有多少客户活跃的指标(第一年的业务):

          [2001 Customers]=CALCULATE([Active Customers],Sales[Year]=2001)

          [2001 Customers]结果是2001年的活跃客户。然后,还有一项用来衡量自2001年以来客户增长百分比基数的指标:
            [Customer Growth Since 2001] =
            DIVIDE ( [Active Customers] – [2001 Customers],[2001 Customers] )

            4、CALCULATE中的<FILTER--筛选器>的“=”操作符的替代。

          在CALCULATE ()的<filter>筛选参数中,不限于“=”操作符。你还可以使用:
    < 、>、<=、>=、<>等。

            并且,其实我们已经知道:在单个CALCULATE ()中所有的<filter>参数都表现得像是被封装在一个AND()函数中。换句话说,最后的当前结果行必须同时满足每个<filter>参数条件,以便被包含在CALCULATE中。你可以理解为:CALCULATE ()中所有的<filter>参数其实是一组筛选器,它们共同作用于计算列表,只有在所有筛选作用结束后,才输出计算(被公式引擎接收)。
            还有一种需要使用“OR(或者)” 操作情况,你可以使用“||” 操作符代替。例如: =CALCULATE([Total Sales],Sales[TransType]=1 || Sales[TransType]= 3)

          当在一个计算筛选器参数中使用 || 操作符时,它只可以在单个列- 如[TransType]列的比较中使用。在不同的列(如TransType和Year)之间的比较中,不能使用||。

          这解释了[Active Customers]这类度量在透视表里的Grand Total (总计值)是如何工作的。让我们再看一下这个透视表:

    我们再次使用ALL

        [ Sumof all years] 所有年份的实际总和比[GrandTotal要高得多。

          这一个很好的例子,说明了为什么要考虑对源表进行评估,而不是在透视表本身中进行评估。另外,我们已经讨论了很多关于列表筛选的内容,但是到目前为止,我们还没有讨论过整个数据模型里的列表筛选。
          其实很简单:内部数据模型表里代表没有任何筛选器(总在一个全局性的数据模型里)。在这个前提下,就好像这一年的列值都不在某个行列上。为了排除这种情况,让我们把从透视表中移走:从行里移除年份,结果与当年的[Grand Total]值相匹配。这不是意外!

          因为这是有道理的:2001年的一些客户在2002年(后来)买了一些东西,同样,2002年的一些客户在2003年有过购买行为,依次类推。如果我们汇总每年的客户总数(包含2001-2003三年的某些客户数),这会不止一次地(重复)计算那些“carryover”--客户(最终得到24,376)。
          但是,当清除[年度]--年度筛选器时:DISTINCTCOUNT (Sales[CustomerKey])运行在一个未经筛选的表上计算,并且只计算每个客户一次!(执行的是不重复列值的计算)结果是18,484,这是正确答案。

          不要跳过上面的段落。它比一般的更值得关注。并不是每个[Grand Total]都是完整的(甚至于部分)。为了说明它,让我们把年份拖到列上,并将月数增加到行:

          当前筛选有三种不同的总计数:年、月总计数以及两者的总和。
          在一个透视表中,每一个数都是一个或多个筛选(一组筛选器)共同作用的结果—那么,透视表中一个或多个没有结果的地方,其筛选作用同样存在,并没有消失。也就是说,虽然是没有结果值,其筛选是存在的!即筛选的作用实际上表现为一种布尔值列表性质:筛选掉不符合条件的行,留下符合条件的那些行......。
          我们后面还将学习到这种行为以及这种行为的运用。

          当在行和列中添加更多字段时,马上会得到许多不同的总数变化。例如,当你在另一个字段下嵌套时,没有什么真正的改变。让我们在每年的下个月里的行为例:

          层级并不能真正改变任何东西。注意,2002年(2677)的合计是一个大的汇总值,汇总的并不是前面的值的和。 2001年是在列上(在之前的行中)单独计算。在透视表度量单元的物理位置并不重要。只有它的“坐标”是重要的。一个筛选器筛选的年份=2002,月=ALL与DAX里引擎完全相同,无论在哪里,年和月数字段都将位于-行、列、报表筛选器,或切片器上。

        第33式  CALCULATE的列表在计算列与度量中的行为

          还记得我们在第一部分中,针对计算列、度量中的CALCULATE隐式与显式的问题。这里用引用列表与定义值列表的概念再次解释一次,这是学习DAX的一个理解的坎。

          我们新建一个[销售列]的[计算列]。定义公式:= SUM('订单'[销售额]) ,即针对[销售额]计算。

          结果并不是我们期待的,这是一个每行结果相同的值。当然,我们知道该公式可用于度量公式。
          如果需要作为计算列,则在公式 = SUM('订单'[销售额])前面加CALCULATE,我们称之为:显式定义列表筛选。即公式前面加上一个CALCULATE:
    = CALCULATE ( SUM('订单'[销售额]))
          这时候,只要显式定义了列表筛选,那么,引擎会自动创建该显式列表筛选对应的隐式行筛选(这是CALCULATE的行为之一)。如图所示,计算列的公式能计算出正确的值:

          前面我们采用:引擎的隐式与显式列表的概念来理解的。在学习完DAX的列表引用与定义值列表的概念后,我们试着运用它来解释这个问题,然后后期运用它进行DAX的书写。
            我们知道:SUM('订单'[销售额]) 是一个值列表(具有行行为),一个DAX公式需要先有列表,然后再有值列表,也就是说任何一个DAX计算(甚至是其中的一个计算单元)都需要一个计算环境(列表范围)。将:SUM('订单'[销售额]) 用于计算列计算,则这时候其列表环境是它使用的某个函数引用的列表(这里是'订单'[销售额]列),SUM('订单'[销售额]) 计算的总是该列表(因为没有任何其他的筛选!或者只有该列表筛选)。
            SUM('订单'[销售额])作为度量能够计算正确,是因为它所在的计算列表要不是整个数据模型,要不就是无限可能的计算列表集(可能被无数可能的列表筛选),它并不会针对其中的一个列表。
            将SUM('订单'[销售额]) 用于计算列计算时,我们期望它能产生不同的行值,相当于告许引擎说:请你按这个行为来计算。不过问题是,引擎的内部行为并不是由我们来定义的:即Sum( )中的计算列,引擎知道是该列表,也总是会使用它计算,结果是同样的某你个值。
          也就是说,引擎自动识别该列表计算而不需要你刻意的定义。既然如此,只需要直接引用列表即可。

            如图,直接引用 =[销售额]列,结果相当于复制了某个列表。

          当然,这里还有一种情况:如果我们已定义了一个度量:[销售额]=SUM('订单'[销售额]) ,那么,直接引用该度量名称,也能正确计算。我们马上说明原因:
          用列表与值列表概念理解:Sum( )是一个值列表,你不能直接定义一个值列表来做为列表计算!你只有定义一个值列表,然后将其作为列表引用。
            我们知道:要实现引用值列表作为列表参与计算,这有两种方式:一种是使用列表函数迭代(即嵌套列表函数),但一旦嵌套了其他函数,则计算将会改变(嵌套函数的功能也会发生作用)。
            另一种是,使用CALCULATE(),这其实也是在原定义的值列表前嵌套了一个函数,从而起到将原值列表引用为列表参与计算的功能,而不该表原值列表定义的计算!本列中,即CALCULATE(SUM('订单'[销售额])) ,这就是CALCULATE()的将隐式行行为(行筛选)转换为相同的显式化列表(列表筛选)的功能,更好的理解是:
          CALCULATE( )的引用"值列表"为显式列表的行为。

          如果是这样,那么,只要是针对某个表的一个或多个计算列操作,该表格里所有的列都可以被定义为显式列表参与计算。比如,我们可以直接定义两个或多个标量值列表之间的计算。如下图的两个时期列表的差异计算(相减)。

          反过来,如前所说,上图中的公式运用在度量中,则会得到一个“错误号”提示。原因当然是:你不能直接引用列表作为值列表计算(这将缺少隐式的行筛选)。

          所以,我们使用聚合函数的值列表行为特性,分别对两个列加上聚合计算(所有迭代函数都具有隐式行筛选行为,这将针对两列都创建了各自的行筛选)。如图:结果正确。

          当然,如果你实在是还没有理解,作为结论,你只需要记住:度量是一个需要定义的值列表,而计算列只需要直接引用列表--列表或结果列表,例如已定义了的某个度量:[销售额]=SUM('订单'[销售额]) ,那么,直接引用该度量名称,相当于直接引用该度量值列表的结果列表,也能正确计算。
          这时候,你可以理解为任何一个度量其实就是一个省略了CALCUALTE的值列表。因此,凡是CALCUALTE([度量],筛选1...)形式的公式(CALCULATE的第一参数为度量)时,可以将公式简写:
            CALCUALTE([度量],筛选1...)= [度量] (筛选1...)

            例如:CALCULATE([销售额], Sales[城市]="上海") 可写成:
                      [销售额] (Sales[城市]="上海")

        第34式  CALCULATE的列表筛选行为(中级)

          现在我们使用一个官方的DAX实例,来加深对CALCULATE()的列表引用与定义值列表以构成DAX筛选+计算的理解。
          业务描述:我们希望求得财务时期表中的以5天或6天(非正常周数7天)定义的财务周数列--[周序号]列,并期望按此周数列条件计算每周的订单数量。以下是提供的公式:

    财务周:=CALCULATE(SUM('业务表'[需求量]),
    FILTER(ALL('日历'),'日历'[周序号]=MAX('日历'[周序号])
    &&'日历'[日期]<=MAX('日历'[日期])
    &&NOT(ISBLANK(MAX('业务表'[交货日期])))))

          因为公式采用的是完整的财务日历表,可能那些当前时期之后没有订单数据的未来日期,仍可能有数据存在。因而,在FILTE()函数中增加一个条件:NOT(ISBLANK(MAX('订单'[交货日期]),即排除那些当前没有数据(交货时期)的时期筛选行。

          要想理解该公式,关键是对公式中FILTER()函数参数的理解,但这并不是一个简单的问题,它涉及到FILTER、ALL两个函数的组合所触发的DAX内部的计算逻辑。
          我们有种感觉:CALCULATE似乎总习惯于FILTER函数搭档,而FILTER()的参数中有时常出现ALL().....。
          CALCULATE与FILTER函数似乎总是被提起,有人称CALCULATE(...FILTER(...))的方式为DAX的万能组合公式,虽然有点夸张,但也说明了这种DAX方式是很重要的书写DAX的形式(也可以说是DAX的一种定式)。
          其根本原因大概是因FILTER的特点决定的,也就是前面我们所说的:FILTER()作为列表结果而执行着标量值列表的功能。换句话说,FILTER() 总以值列表形态(具有行行为--你通常理解的遍历表)存在着,但它首先是一个值列表结果(即作为列表可以被引用,度量也有这个功能)。你可以回过头看看第6部分的内容。这里把其中例举的两个例子拿出来再说说:

          1) = COUNTROWS ( FILTER (Sales, Sales[Unit Price] > 100))
        2) = COUNTROWS ( ALL (Sales), Sales[Unit Price] > 100))

            我们前面提供的是一个很通俗的区别方法:公式 1),对于FILTER函数,有一个通常的理解:它是一个行筛选器,具有遍历表的每一行的行行为,因而,它的结果表是按定义的值列表条件一行一行判断并找出来的,即内部引擎一行一行扫描--遍历表。而公式 2)则是直接“引用”一个列表筛选得出的。直观上,你回话觉得公式 2)似乎要运行得快一些(这要视数据模型等具体情况,讨论这个还有点过早)。

          出于重要性,我们再看一个已经出现的公式:
          CALCULATE([销售量],FILTER('时期表', [销售量]<100))

          该公式中,我们知道,对了,还是简单分下步骤,以便于理解:
          1)先有列表。FILTER引用时期表(FILTER的第一参数);
          2)再有值列表。然后,第二参数定义了一个值列表:[销售量]<100(布尔值筛选);
          3)该布尔值条件对引用的时期表进行筛选,得到一个时期表的子集表。也就是:时期表的范围已发生改变!。
          请记住,这里是关键之处,由于时期表的范围已被改变,这时候的时期表应该是一个值列表(具有行的行为),FILTER()在执行着标量值列表的功能;但同时,该值列表又被CALCULATE作为列表“引用”(使用的是该值列表的全集列表),以作为CALCULATE的筛选列表,由此时期表的子集列表筛选[销售量]度量所在的计算列表。这是FILTER()同时具有的作为列表结果的功能。
            这里还需要理解的是:该公式中,FILTER()首先筛选的是时期表,而不是计算所在的事实表,由于事实表与时期表之间的列表关系,通过时期表的筛选,进而筛选出计算列表集,然后,度量在该结果列表集下计算。
          这就是该公式中发生的整个事情。我们现在回过头来再看看前面提到的那个公式,它看起来好像要复杂一些,但理解方式是一样的:

    财务周:= CALCULATE(SUM('业务表'[需求量]),
    FILTER(ALL('日历'),'日历'[周序号]=MAX('日历'[周序号])
    &&'日历'[日期]<=MAX('日历'[日期])
    &&NOT(ISBLANK(MAX('业务表'[交货日期])))))

            这里的FILTER部分是:=FILTER(数据,筛选条件),本例中,第一参ALL('日历表'),根据ALL函数的功能,表示为:"取消"施加在"日历表"的所有筛选,也就是说取整个日历表的所有数据(这点上,相当于直接引用一个表,请注意,这里的问题是前面留下来的一个问题:既然与直接引用一个表结果相同,为什么还要使用ALL())。

          本来,我们知道:"业务表"的"交货日期"和"日历表"的"日期"字段建立有两个表格之间的关联(存在关系),但是在这里,因为ALL()函数的作用,这让原来"日历表"与"业务表"之间建立的"字段关系"被"暂时"取消了,这时候,两个表格将重新执行如下方式的操作(当然,这个操作是在我们给FILTER()函数下了ALL('日历表')指令后,由引擎内部自动完成的)。
          当在 FILTER()函数中使用了ALL() 后(其实这时候,我们很为难:是说FILTER引用了列表,还是说由ALL定义了值列表?这就是前面讲到的,FILTER的两个同时具备的特别行为)。这时,两个表格--"业务表"和"日历表"原有的关系暂时被拆开,然后,两个表格之间又建立了如下的以一对多关系(即左连接关系)。
          也就是说,左边表格的每一条记录与右边表格的每一条记录建立了关系,形成一条条新的记录。我们假设左表有15条记录,右表有15条记录(当然两个表格的记录数不必相等),那么,最后形成的大表格的记录数可能是15*15=225条行记录...。
            好了,关于 FILTER()函数的第一个参数的作用我们讲完了,现在我们理解一下FILTER()函数的第二个参数的作用,FILTER()函数的第二个参数如下:
          '日历'[周序号]=MAX('日历'[周序号])
          &&'日历'[日期]<=MAX('日历'[日期])
          &&NOT(ISBLANK(MAX('业务表'[交货日期])

          这里,FILTER()第二个参数的功用是在第一个参数所获得的数据的基础上作进一步的筛选。你已经知道,通过FILTER()第一个参数ALL("日历表")获得了一个225行大表格,这将被第二个参数进行筛选。
          在这里,FILTER()函数的第二个参数由三个条件构成。这三个条件需要同时满足,即必须同时为TRUE的行才保留在筛选结果中。请自行理解就不做过多说明。
          我们来小结一下:FILTER(ALL('日历'), ...)的行为,因为可以摆脱原有列表关系的影响,并使用最大的全集列表来创建一个新的关系列表(左连接宽表),可能拥有有更大的事实行计算,并避免错失某些行值的计算。而FILTER('日历', ...)的直接引用列表的行为,由于始终受原有列表关系的影响,由于在度量计算中,你不能直接引用一个列表计算,它将被一个或多个值列表筛选。其灵活性不如采用ALL()的方式。
          当然,我们只就列表与值列表的概念来分析,实际的更多情况,在后续的内容中还会讲到。
          这里,还有一个问题,那就是公式中的MAX()函数的作用,根据前面所说:凡是结果为一行一列(相当于Excel中的一个单元格)的函数都是标量值列表。只有这样才能写成如下形式的代码:
          '日历表'[五天序号]=MAX('日历表'[五天序号])
          '日历表'[日期]<=MAX('日历表'[日期]))
            即:“列表=某个值列表”的形式,以实现一个多行一列的数据表格中的所有数值和单个或某个范围的数值进行比较。
            另外,我们已经知道:CALCULATE()函数的语法是:
            = CALCULATE(计算列表,筛选条件1,筛选条件2,…)
            使用FILTER()函数,该函数的语法可以变化为:
            = CALCULATE(计算列表,FILTER(数据列表,筛选条件(值列表)))
            长期以来,关于这两个DAX代码的方式与行为,是不是了解得多了一些?

        第35式  CALCULATE的列表筛选行为(中高级)

            (请参考《DAX圣经》中的第4、第5章相关内容)

          既然已经了解了CALCULATE函数许多的基础知识,或者至少了解了它为什么如此有用,下面的内容将专门讨论其用法的各种示例。深入研究和理解它是很重要的。

          事实上,CALCULATE本身是一个非常简单的函数。其复杂性此我们给予它的重要性)来自这样一个事实:使用CALCULATE,你将被迫考虑列表筛选,并且可能在一个公式中会有多个筛选,这使得很难遵循其计值流。根据经验,通过实例学习是理解CALCULATE工作流和列表筛选的最佳方法。

        1、单列的筛选

          使用CALCULATE的最简单方式是筛选单个列。例如,假设要创建一个始终返回Black--黑色产品销售的度量,而不管在颜色上的筛选是什么。这个公式很容易写成:

    [SalesAmountBlack] =
            CALCULATE(SUM( Sales[SalesAmount] ),
                Product[Color] ="Black")

          该度量[SalesAmountBlack]总是显示Black--黑色产品的销售情况,而不受当前的筛选器的筛选影响。可以看到,[SalesAmountBlack]显示的总是黑色产品的销售情况,即使在筛选器的筛选里选择了不同颜色的行中也是如此。
            如果关注的是第三行 :Product[Color] ="Black",这就是所发生的事情:公式在筛选器筛选中启动了CALCULATE (),其中计算所在的颜色行的唯一值是Blue。然后,CALCULATE定义一个新的条件(Color=Black),当强制将它应用到新的筛选器筛选时,它替换了现有的筛选条件,删除了Blue上的其他筛选器,并将其替换为列值=“Blue”的筛选器,而且,在所有行(即所有颜色行)上都会如此,这也是为什么看到所有行为相同编号的原因。
          显然,由于CALCULATE覆盖所选内容的唯一列是颜色,所以其他列将继续维护它们的筛选器。例如,如果将[日历年]列放在列上,则可以看到所有颜色行上的结果也都是相同的,不同年份的结果也是相同的,如图所示:

          图中[ SalesAMountBlack]只覆盖掉颜色:它仍然服从对其他列(年份)的筛选。
          筛选单个列很简单。一个不太明显的事实是:如果使用条件作为筛选器进行计算,一次只能筛选一个列。例如,可能希望创建一个度量,该度量只计算单价至少是单位成本两倍的产品的销售额。可以试试这个公式:

    [HighProfitabilitySales]=
    CALCULATE(SUM( Sales[SalesAmount] ),
          Product[Unit Price] >= Product[Unit Cost]*2)

            可以看到,这一次,条件涉及到两列:Unit Cost和 Unit Price--单位成本与单位价格。即使DAX可以轻松地计算出每个产品的条件,它也不允许这种语法。
          原因是:在计算过程中CALCULATE无法确定是应该替换单价、单位成本上的任何现有筛选器条件,还是不替换它们中的任何一个?实际上,如果试图编写上面的公式,则会得到一个错误:该表达式包含多个列,但只有一个列可以用于True/False表达式,即用作表筛选器表达式。
          当然,我们已经不需要这样去理解,如前面所说的:“列表=列表”的方式是无法生成这样的公式的(错误的),只能使用:列表=“值列表”的方式来定义这种布尔语法(正确的)。
          如果需要在条件中使用多列来调用计算,则需要使用不同的语法,该语法需要定义一个值列表,而不是一个条件。编写前一个表达式的正确方法是使用以下语法:

    [HighProfitabilitySales] =
    CALCULATE(SUM( Sales[SalesAmount] )
    FILTER(Product,Product[Unit Price] >= Product[Unit Cost] *2))

          与前一个公式不同的是,这一次,我们没有直接使用布尔表达式,而是使用了FILTER()的列表语法功能,来作为CALCULATE的筛选器参数。这时,不仅筛选了两个列,还筛选了整个Product表。在图中,可以看到[HighprofitabilitySales]度量的执行结果:

          这种情况下,CALCULATE的计算条件:FILTER()的结果是包含多个列的表(它包含Product表的所有列)。
          当新的条件插入到筛选器环境中时,实际上全部现有的Product--产品条件都将被这个新的筛选器取代。换句话说,使用实际表作为FILTER()函数的第一个参数,可以有效地替换该表中所有列的所有条件。
          在阅读了前面的解释之后,你应该注意到这里有些东西并不完全清楚:FILTER中的筛选器表达式计算替换了Product表上所有以前存在的筛选器,因为FILTER返回的表包含Products的所有列。然而,我们的公式返回的值对于每一行都是不同的。
          就我们到目前为止所了解到的情况而言,要么DAX没有替换掉颜色行筛选器,要么发生了更复杂的事情。因为,我们已经知道DAX取代了颜色筛选器,所以,我们需要更多的研究来理解CALCULATE的确切流程。下面的代码是度量的公式:我们对行进行了编号,以便更容易地引用公式的各个部分。

    1,CALCULATE(
    2,SUM( Sales[SalesAmount] )
    3,FILTER(
    4,Product ,
    5,Product[UnitPrice] >= Product[Unit Cost] *2
    6  )
    7 ,
    8 )

          第一个是CALCULATE函数。我们知道,作为CALCULATE的第一步:它首先定义筛选器参数。在执行任何其他操作之前,CALCULATE将从第3行的FILTER表达式开始。 我们已经很熟悉:FILTER是一个迭代器,它遍历Product表(参见第4行)。然而,FILTER将不会遍历所有Product表:它将只能遍历在当前筛选器筛选中可见的那些行。现在的问题是:
          该“DAX在第4行引用(或定义)Products表的FILTER()筛选环境是什么?”
          请记住,这时,CALCULATE()仍然没有创建其新的计算筛选。稍后,它是在对FILTER进行计算之后,才会执行筛选。
          这可以推断出:CALCULATE()的筛选器是在原始筛选器筛选下计算的,而不是在FILTER创建的筛选下计算的。虽然这看起来很明显,但这种简单的考虑却是许多DAX公式中错误的主要来源之一。

          在第4行的产品Product表,是指在原始筛选器筛选中可见的那些产品。然后CALCULATE将删除颜色的现有筛选器,但是,这样的筛选器现在已经包含在FILTER筛选器的结果中,从而导致当前透视表的行为。正确理解筛选的流动顺序是很重要的:在CALCULATE的表达式中,颜色的筛选器会被CALCULATE替代,而不是在CALCULATE里的FILTER。
          换句话说,CALCULATE中的筛选参数在之前的列表筛选中进行了计算。稍后,才会创建其新的列表筛选,并在此筛选下,计算其表达式参数。
          如果你对于前面的一段解释很迷茫,那么,你需要使用前面关于FILTER的行为方式的解释,你只需记住一句话即可:FILTER()的结果是一个值列表,该值列表被CALCULATE()引用为列表筛选(筛选计算列表,而不是FILTER引用的数据列表!),要获得完整的理解,我们再次比较一下前面提到的ALL方式的公式:

    [HighProfitabilityALLSales] :=
    CALCULATE(SUM( Sales[SalesAmount] )
    FILTER  (ALL(Product),
    Product[Unit Price] >= Product[Unit Cost] *2))

          这一次,没有使用FILTER(Product),而是FILTER(ALL(Product)。这时,FILTER() 不只是迭代某个颜色的产品,而总是迭代所有Product-产品,而且,由于CALCULATE()替换了颜色上的筛选器。

          图中[HighProfitabilityALLSales]度量显示,在CALCULATE()中有效地替换了颜色上的筛选器。即[HighProfitabilityALLSales]总是显示所有高利润产品的销售,有效地忽略了原有的颜色筛选器,让我们开始从第一个例子中得出一些结论:

          1)你可以在CALCULATE()中使用布尔值条件,但使用它们,需要表达式中只针对某一个列引用,否则会出现语法错误:
          2)可以在引用中使用FILTER() 或任何其他表函数作为筛选器参数。本例中,表中所有列都是新筛选器筛选的一部分。这意味着CALCULATE()将取代这些列上的任何现有筛选器:

          3)如果使用FILTER筛选器,则CALCULATE()在原始筛选器筛选中计算其筛选器。另一方面,如果使用布尔条件,则CALCULATE将替换现有的筛选器筛选,但只作用于受影响的列。

          有人问:那么,CALCULATE()里面如果有多个列表筛选,这些筛选有没有顺序问题?其实是有的,一般,尽可能首先将能筛选出列表范围少的列表子集的那个筛选放在前面要好(因为后面筛选的范围跟着少一些,特别是数据量较大时很明显)。

          2、多列表条件下的筛选

          使用多个筛选器时,CALCULATE在创建新筛选器筛选时执行逻辑条件AND连接的一组筛选条件。所以,如果想筛选所有由"Tailspin Toys"制造、并且(AND)产品颜色--黑色的产品,可以使用这样的表达式:

    [Calculate Version] :=
    CALCULATE(SUM( Sales[SalesAmount] ),
    Product[Brand] ="Tailspin Toys",
    Product[Color] ="Black")

          因为CALCULATE将这两个条件放入其中,所以,你可能会认为表达式与这个表达式是等价的:

    [FILTER Version] :=
    CALCULATE(SUM( Sales[SalesAmount]),
    FILTER(Product,
    AND(Product[Brand] ="TailspinToys",Product[Color] ="Black")))

          实际上,这两种表达方式是不同的,前面我们解释了原因。你应该已经了解了这一点,但由于主题的复杂性以及后面讨论的概念的重要性,这里值得重复一遍。在包含布尔表达式的公式中,brand-品牌和color-颜色都忽略了现有的列表筛选(即直接定义值列表筛选):而在带FILTER()的公式中,在应用公式之前,对于两个列都考虑了预先存在的筛选器。
            因此,[CalculateVersion]总是返回black的、TailspinToys的销售额,而[FILTER

    Version]只在它们已经存在于预先存在的筛选器筛选中时才返回销售:否则它返回一个空值。可以在下图中观察到这种行为:

          很明显,这两个公式导致了不同的计算,即使它们看起来非常相似,不同之处在于:FILTER()筛选器迭代了由外部当前筛选筛选的表,请记住以下公式:

    [Sales of Tailspin Toys] :=
    CALCULATE(SUM( Sales[SalesAmount] ),
    Product[Brand] ="Tailspin Toys")

    相当于下一个:

    [Sales of Tailspin Toys] :=
    CALCULATE(SUM( Sales[SalesAmount] ),
    FILTER(ALL ( Product[Brand] ),
    Product[Brand] ="Tailspin Toys"))

          在第二个公式中,通过使用ALL(Product[brand]),来明确要求DAX:仅针对er[制造商]列而忽略当前的筛选器筛选。我们再怎么强调理解这些公式的行为的重要性也不为过。即使这里使用的表达式只是用于教育目的,在编写自己的表达式时也会遇到类似的情况,并确保最终不会看到一些奇怪的结果。为了理解公式的行为,你必须了解DAX中的筛选行为。
          筛选在一列上工作,上面所述的示例能工作得很好。如果涉及多列,我们可能会尝试通过尝试以下公式将等效扩展到多个列表的场景:

    FilterAll Version :=
    CALCULATE(SUM(Sales[SalesAmount]),
    FILTER(ALL(Product),
    AND(Product[Brand]="Tailspin Toys",Product[Color]="Black") ) )

          这个公式并不符合在本部分中看到的第一个公式所解决的要求。
          如果使用ALL(Product),它将通过忽略整个表上的筛选器,来达到忽略其中两列的当前筛选(AND定义的两列)。但是,通过忽略整个表上的当前筛选,仍然可以得到不同的行为。为了看到效果,需要使透视表更加复杂一些。在下图中,我们在行中添加类别名称:

        图中,使用FILTER和Product表中的ALL内容仍然不能解决这个问题。

    如所见,[Filter All Version]会忽略整个Product表上的FILTER筛选,甚至显示了Computers category---计算机类别的值,而[CalculateVersion]会显示一个空白值。原因是[CalculateVersion]只忽略color 和model--颜色和模型名称的当前筛选,而[Filter All Version]则忽略整个表上的筛选器,当然,这同时会忽略category--类别列。

          为了找到正确的公式,必须考虑列而不是表。我们既不能提供Product表做筛选(因为它包含完整的原始筛选器:行、列筛选,即使用的值全集的列表),也不能提供ALL(Product)(因为这将忽略所有的筛选器)。
          正确的思路是:我们需要引用一个Product表来计算,并在该表中,删除制造商的筛选器,但是,其他列表中现有的任何筛选器仍需要处于活动状态。我们已经知道,只有CALCULATE函数具有该功能。

      未完待续

    相关文章

      网友评论

        本文标题:CALCULATE之庖丁解牛系列 - CALCULATE专解 (

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