Excel革命!超级数据透视表Power Pivot与数据分析表达式DAX快速入门
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.4 Power Pivot与DAX函数

在数据透视表值区域中的每个单元格中的内容都是其对应的数据透视表筛选环境(行标题、列标题、筛选字段、切片器、日程表等)到数据源中筛选后汇总得到的结果。

在很多场景中,我们需要修改数据透视表筛选环境,从而满足更加复杂的数据分析需求。这时就需要各种用途的DAX函数上场了。

很多DAX函数与Excel工作表函数大不相同。在一般情况下,Excel工作表函数的计算结果是一个数值;很多DAX函数的计算结果不是一个简单的数值,可能是一个表,也可能是改变Power Pivot数据模型的一些筛选设置。

2.4.1 筛选限制移除函数——ALL()函数

在数据分析实战中,经常需要计算各种占比,如在数据透视表值区域中计算每个图书子类销售册数占图书大类销售册数的百分比,此时就要用到筛选限制移除函数——ALL()函数了。

ALL()函数的功能:在数据透视表筛选环境中移除指定字段或整个表上的筛选限制。

ALL()函数的参数可以是一个表(此时只能有一个参数),也可以是一个表中的一列或几列。当ALL()函数的参数是一个表中的一列或几列时,要求所有列必须来自同一个表。

ALL()函数一般会与其他DAX函数一起使用,如与CALCULATE()函数一起使用,用于修改这些函数所处的数据透视表筛选环境。

需要注意的是,当ALL()函数的参数是特定表上的指定列时,它能够移除该表指定列上的筛选限制,并且这种筛选移除效果会影响整个表,就像我们在Excel工作表中移除位于某列上的筛选限制一样。

1.ALL()函数的参数是表中的一列

ALL()函数通常用于计算子类与其所属大类的比值。针对本书中的案例,我们有如下数据分析需求:计算每个图书子类的销售总册数与其所属图书大类的销售总册数的比值。

首先,在Power Pivot数据模型管理界面中的“T3销售”表下方的DAX表达式编辑区中的单元格中添加如下DAX表达式:

上述操作如下图所示。

上述DAX表达式与我们之前所见的DAX表达式的区别是CALCULATE()函数中增加了第二个参数(ALL('区域'[T1子类K])),该参数是一个嵌入CALCULATE()函数内部的DAX表达式,类似于Excel工作表函数中的函数嵌套。

这里,作为CALCULATE()函数的第二个参数(筛选器参数),ALL('区域'[T1子类K])的功能是在CALCULATE()函数所处的数据透视表筛选环境中移除筛选条件[T1子类K],换句话说就是从数据透视表值区域对应的所有数据透视表筛选环境中移除“T1子类K”字段的特定筛选限制,即对数据透视表筛选环境做减法,放松了筛选限制。

将DAX表达式“mySUM移除子类限制”拖曳至数据透视表值区域中,计算结果如下图所示。

观察上图可知,虽然“T1子类K”字段仍然位于数据透视表的行标题中,但它对数据透视表值区域中的DAX表达式“mySUM移除子类限制”的筛选限制消失了,而数据透视表最左侧的行标题“T0大类K”对该DAX表达式的筛选限制依然存在,这是因为ALL()函数移除的只是“T1子类K”字段对该DAX表达式的筛选限制,并没有移除“T0大类K”字段对该DAX表达式的筛选限制。

观察数据透视表值区域中的C5单元格,该单元格中的内容对应的数据透视表筛选环境为“1科技”图书大类、“11机械”图书子类。在没有移除任何数据透视表筛选环境限制时,“1科技”图书大类下的“11机械”图书子类的销售总册数为24 (B5单元格对应的数值);在用ALL()函数在DAX表达式“mySUM移除子类限制”中移除了“T1子类K”字段的筛选限制后,这个数值反映的是它的上一级(“1科技”图书大类)的销售总册数为94。

在掌握了ALL()函数的基本功能后,就可以利用DAX函数计算每个图书子类的销售总册数与其所属图书大类的销售总册数的比值了。

由于我们已经设计好了移除图书子类限制的DAX表达式,因此以这个DAX表达式为分母,以没有移除数据透视表筛选环境中任何筛选限制的图书销售总册数的DAX表达式为分子,即可得到每个图书子类销售总册数与其所属图书大类的销售总册数的比值。

没有移除数据透视表筛选环境中任何筛选限制的图书销售总册数的DAX表达式如下:

计算图书子类的销售总册数与其所属图书大类的销售总册数的比值的DAX表达式如下:

这三个DAX表达式在Power Pivot数据模型管理界面中的呈现形式如下图所示。

在Power Pivot数据模型管理界面,单击“数据透视表”按钮,在弹出的下拉列表中选择“数据透视表”命令,切换到Power Pivot超级数据透视表界面,然后按下图设置Power Pivot超级数据透视表布局,即可在Power Pivot超级数据透视表中得到图书子类的销售总册数与其所属图书大类的销售总册数的比值。

修改Power Pivot超级数据透视表布局,在Power Pivot行标题中移除“T0大类K”字段,得到新的Power Pivot超级数据透视表如下图所示。

现在,在数据透视表筛选环境中移除了图书大类的筛选限制,只剩下图书子类的筛选限制,同时DAX表达式“mySUM移除子类限制”用ALL('区域'[T1子类K])移除了图书子类的筛选限制,相当于在没有任何筛选限制的情况下计算图书销售总册数,因此,我们看到,在“mySUM移除子类限制”字段的每个单元格中显示的都是图书销售总册数251。

现在,再次改变Power Pivot超级数据透视表布局,将“T2书号K”字段拖曳至Power Pivot行标题中,再次观察“mySUM移除子类限制”字段的所有单元格,这次,尽管行标题“T1子类K”对该字段的筛选限制已经被ALL()函数移除了,但是,由于在数据透视表筛选环境中增加了行标题“T2书号K”对该字段的筛选限制,因此,得到如下图所示的Power Pivot数据透视分析结果。

2.ALL()函数的参数是一个表

除了用表中的一列作为ALL()函数的参数,还可以用表作为ALL()函数的参数。如果ALL()函数的参数是一个表,那么它会在数据透视表筛选环境中移除指定表中所有列的筛选限制,如下面的DAX表达式:

上述DAX表达式可以在数据透视表筛选环境中移除“区域”表中所有列的筛选限制。在本案例中,Power Pivot数据模型中只有一个表,即DAX表达式“my移除所有”移除了数据透视表筛选环境中的所有筛选限制,因此,无论Power Pivot的行标题和列标题的布局如何变化,在数据透视表值区域的“my移除所有”字段中,得到的都是整个表中所有图书的销售总册数251。

2.4.2 ALL()函数与ALLEXCEPT()函数

在DAX中有一个与ALL()函数对应的函数,即ALLEXCEPT()函数。我们可以认为ALLEXCEPT()函数是为了在特定应用场景中简化ALL()函数而设计的。

如果在数据透视表筛选环境中需要使用ALL()函数移除筛选限制的字段比较多,使用类似ALL([字段1],[字段2],[字段3]...)的语法比较麻烦,那么可以使用ALLEXCEPT()函数实现。ALLEXCEPT()函数的语法格式如下:

ALLEXCEPT()函数的功能:在数据透视表筛选环境中,在ALLEXCEPT()函数第一个参数指定的表中,第二个参数指定的字段的筛选限制不会被移除,在同一个表中没有在ALLEXCEPT()函数中列出的字段的筛选限制会被移除。

观察下面的DAX表达式:

上述DAX表达式的作用:在数据透视表筛选环境中,只保留“区域”表中“T1子类K”字段的筛选限制,移除该表中其他字段在数据透视表筛选环境中的筛选限制。

如果将该DAX表达式拖曳至Power Pivot超级数据透视表值区域中,那么DAX表达式“my只保留子类限制”的计算结果是所有图书销售总册数251,如下图所示。

在英语单词中,ALL的本意是“包含所有”,但是在DAX中,结合数据透视表筛选环境,以及ALL()函数和ALLEXCEPT()函数的功能,将ALL理解为“移除筛选限制”更合理。

2.4.3 CONCATENATEX()函数与VALUES()函数

下面介绍一个非常有用的函数,即CONCATENATEX()函数。CONCATENATEX()函数的功能是用指定的分隔符连接指定表中特定列中的内容,计算结果是一个长字符串。

在传统Excel数据透视表的值区域中只能显示数值,而不能显示文本。在PowerPivot中,借助CONCATENATEX()函数,这个问题可以得到完美解决。

在英文中,CONCATENATE的含义是连接,在这个单词后面加一个X,表示CONCATENATEX()函数是一个对表进行逐行处理的函数。CONCATENATEX()函数最简单的语法格式如下:

下面结合实例讲解该函数的用法。如果要知道在某个图书大类或图书子类下销售了哪些图书,那么可以使用如下DAX表达式:

将上述DAX表达式拖曳至Power Pivot超级数据透视表值区域中,得到的计算结果如下图所示。虽然这个数据透视表看起来很拥挤,但是它正确地得到了每个图书大类下的每个图书子类所销售的图书的名称列表。

通过观察发现,虽然上面的Power Pivot初步满足了我们的需求,但是缺点也相当明显:图书名称重复且排序杂乱。如果希望重复销售的图书名称只出现一次,并且图书名称能够按照特定的顺序排列,那么单靠CONCATENATEX()函数是不够用的。下面介绍一个新的DAX函数,即VALUES()函数。

VALUES()函数的功能是压缩重复,参数一般为表中的一列,其计算结果是一个基于该列、没有重复值、只有一列的表。使用VALUES()函数优化后的DAX表达式如下:

在Power Pivot数据模型管理界面中输入上述DAX表达式,在返回Power Pivot超级数据透视表界面后,将该DAX表达式拖曳至Power Pivot超级数据透视表值区域中,得到的计算结果如下图所示。

可以看到,重复值确实去掉了,但结果尚未按照图书名称排序,还需要继续优化。其实,排序是CONCATENATEX()函数自带的一个功能。在本案例中,在CONCATENATEX()函数中指定排序的字段和排序的顺序,即可实现优化。优化后的DAX表达式如下:

这里的最后两个参数,'区域'[T2书号K]表示按照该列进行排序,ASC表示按照升序排序。最终的计算结果如下图所示。

2.4.4 筛选函数——FILTER()函数

在介绍CALCULATE()函数时曾经提到,CALCULATE()函数有两个特点,第一个是它能够识别当前的数据透视表筛选环境,第二个是它能够借助筛选器参数修改当前数据透视表筛选环境。

针对CALCULATE()函数的第二个特点,什么内容可以作为CALCULATE()函数的筛选器参数呢?答案是既可以是DAX函数,又可以是DAX表达式。这里我们介绍一种最常见的情况:用FILTER()函数作为CALCULATE()函数的筛选器参数。

FILTER()函数能够对指定的表进行筛选,这个指定的表既可以是Power Pivot数据模型中实际存在的实体表,也可以是由其他DAX表达式计算生成的表。FILTER()函数的计算结果是一个经过FILTER()函数筛选后的新表。FILTER()函数的语法格式如下:

这里的“表或计算结果为表的DAX表达式”是指要筛选的表或计算结果是表的DAX表达式,而“筛选条件”是指要对FILTER()函数指定表中的每行进行测试的判断条件。

FILTER()函数是一个逐行处理函数。逐行处理函数的特点如下:能够对其第一个参数所指定的表进行逐行判断或计算,按行生成中间结果,并且依据这些中间结果再次进行汇总计算,从而得到函数的最终计算结果。

对于FILTER()函数,其内部运算逻辑如下:

FILTER()函数对第一个参数所指定的表中的每行,都会用第二个参数所设置的筛选条件进行测试,将满足测试条件的行留下来,将不满足测试条件的行舍弃。这样,FILTER()函数一行一行地测试下来,会得到一个新的数据表,这个数据表中的每行都满足第二个参数所设置的测试条件。

使用如下DAX表达式统计在数据源中不同的图书分类下,“T4封面颜色”字段值为“蓝”的记录各有多少条(蓝色封面的记录在数据源中出现的次数)。

在Power Pivot超级数据透视表中,汇总计算的最终结果要以数值的形式在Power Pivot超级数据透视表值区域中呈现。我们知道,Power Pivot超级数据透视表值区域中的单元格中只能显示数字和文本,不能显示表。而FILTER()函数的计算结果是一个表,因此为了能在Power Pivot超级数据透视表值区域中看到FILTER()函数的计算结果,只能借助其他DAX函数,将表内容汇总成一个数值,从而验证FILTER()函数的计算结果是否正确。在本案例中,我们使用COUNTROWS()函数来计算FILTER()函数的计算结果的行数,从而验证FILTER()函数的计算结果是否正确。

在Power Pivot数据模型管理界面的DAX表达式编辑区中的单元格中输入上述DAX表达式,如下图所示。

返回Power Pivot超级数据透视表界面,将DAX表达式“蓝色行数”拖曳至数据透视表值区域中,即可看到在数据源中每种图书类别下蓝色封面的记录条数,如下图所示。注意,这里计算的是在数据源中满足'区域'[T4封面颜色]="蓝"的记录条数,不是图书销售册数。

此外,可以按照下图更改Power Pivot超级数据透视表布局。无论是上图还是下图,该DAX表达式都正确地计算出了满足数据透视表筛选环境的结果。如果对这个结果还有怀疑,那么可以到数据源中进行验证。

2.4.5 CALCULATE()函数与FILTER()函数

上节提到,FILTER()函数是一个能够对第一个参数所指定的表进行逐行处理的函数。在DAX函数中,能够对表进行逐行处理的函数有多个,在后面的章节中将引入更多逐行处理函数。在初步了解CALCULATE()函数、FILTER()函数和ALL()函数之后,本书开始研究稍微复杂的案例:在当前数据透视表筛选环境下,计算当前图书子类的销售册数与“12电子”图书子类的销售册数的比值。

为了完成本案例的任务,首先需要计算在当前数据透视表筛选环境下“12电子”图书子类的销售册数。完成这个子任务的DAX表达式如下:

将上述DAX表达式拖曳至Power Pivot超级数据透视表值区域中,该DAX表达式的计算结果如下图所示。

在上图中,数据透视表筛选环境中只有“T1子类K”行标题的筛选限制,在DAX表达式“电子类册数”中,我们用到了CALCULATE()函数。

我们反复强调,CALCULATE()函数具有(借助其筛选器参数)修改其当前数据透视表筛选环境的能力。在本案例中,筛选器参数使用的是FILTER()函数与ALL()函数嵌套的DAX表达式:

在介绍FILTER()函数时,我们了解到,该函数的第一个参数必须是一个表或计算结果为表的DAX表达式。在上述DAX表达式中,FILTER()函数的第一个参数是一个能够生成表的DAX表达式:ALL('区域'[T1子类K])。

ALL('区域'[T1子类K])的计算结果是一个只有一列的表,这里ALL()函数的作用是移除数据透视表筛选环境中“T1子类K”字段对数据源的筛选限制,因此,在数据透视表筛选环境中,在没有其他筛选限制的情况下,ALL('区域'[T1子类K])得到的应该是一个包含所有图书子类的数据源子集。

接下来,使用FILTER()函数对ALL('区域'[T1子类K])的计算结果进行进一步加工:

在上述DAX表达式中,使用FILTER()函数对ALL()函数计算得到的表进行逐行测试,只留下满足'区域'[T1子类K]="12电子"的记录。

最后,借助CALCULATE()函数,在数据透视表筛选环境和筛选器参数(用于修改数据透视表筛选环境)的综合作用下,对“T3销售册数”字段进行求和,即可得到在当前数据透视表布局下,每个图书子类对应的值都是“12电子”图书子类的销售册数。

以DAX表达式“电子类册数”为分母,以当前图书子类的销售册数为分子,最终的DAX表达式如下:

上述DAX表达式的计算结果如下图所示。

在上述DAX表达式中,FILTER()函数与ALL()函数嵌套组成的DAX表达式如下:

可以简写成如下格式:

因此,DAX表达式:

可以简写成如下格式:

综上所述,类似如下格式的DAX表达式:

可以简写成如下格式:

事实上,上述DAX表达式还不是很完美。例如,如果修改Power Pivot超级数据透视表布局,将“T0大类K”字段也加入数据透视表行标题中,那么除了“1科技”图书大类外,其他图书大类下的“当前VS电子类”字段的值都是“#NUM!”,如下图所示。

之所以出现上述问题,是因为虽然使用ALL('区域'[T1子类K])移除了数据透视表筛选环境中“T1子类K”字段的筛选限制,但也只是移除了这一个筛选限制,数据透视表筛选环境中的其他筛选限制(无论是原有的还是新添加的)并不受影响。因此,在将“T0大类K”字段拖曳至数据透视表筛选环境中时,“T0大类K”字段的筛选限制仍然起作用,这时ALL('区域'[T1子类K])就变成了在图书大类限制下的ALL()函数,但是,除了“1科技”图书大类外,“2教育”和“3文艺”图书大类下根本没有“12电子”图书子类,因此FILTER(ALL('区域'[T1子类K]),'区域'[T1子类K]="12电子")的计算结果为空,这就是出现“#NUM!”的原因。

为了解决这个问题,我们将ALL('区域'[T1子类K])修改成ALL('区域'),在数据透视表筛选环境中取消了所有字段的筛选限制,这样,无论将哪个字段拖曳至数据透视表筛选环境中,对DAX表达式“当前VS电子类3”分母部分的DAX表达式都起不到筛选作用了,但对分子部分的DAX表达式SUM('区域'[T3销售册数])的筛选作用依然存在,因为它没有从数据透视表筛选环境中移除任何筛选限制。能够完美地完成本案例任务的DAX表达式如下:

上述DAX表达式的计算结果如下图所示。

2.4.6 DAX表达式与Power Pivot超级数据透视表布局

本节讲解DAX表达式与Power Pivot超级数据透视表布局之间的关系。

设计一个DAX表达式,并且将其拖曳至Power Pivot超级数据透视表值区域中,如果改变Power Pivot超级数据透视表布局,在数据透视表筛选环境中增加或减少筛选限制,那么该DAX表达式的计算结果会随着Power Pivot超级数据透视表布局的变化而重新计算,重新计算的结果也许会出现一些看似难以理解的变化。以前面讲过的一个DAX表达式为例:

Power Pivot超级数据透视表布局如下图所示。观察下图可知,Power Pivot超级数据透视表值区域中有两个DAX表达式,其中DAX表达式“以下项目的总和:T3销售册数”是直接将数据源中的“T3销售册数”字段拖曳至Power Pivot超级数据透视表值区域中得到的(数字字段默认为求和运算)。另一个DAX表达式“电子类册数”就是我们设计的DAX表达式。当数据透视表只有行标题,并且行标题是“T1子类K”字段时,DAX表达式“电子类册数”似乎很完美。

但是,当在数据透视表行标题中加入“T0大类K”字段时,DAX表达式“电子类册数”似乎就不那么完美了,“电子类册数”字段中竟然出现了空值,如下图所示。

关于这个问题的解释上一节已有涉及,为了更加透彻地理解Power Pivot,本节对该问题进行深入探讨。为了便于对比分析,我们再次展示DAX表达式“电子类册数”:

在DAX表达式“电子类册数”中,作为CALCULATE()函数筛选器参数的FILTER(ALL('区域'[T1子类K]),'区域'[T1子类K]="12电子")用ALL()函数移除的只是数据透视表筛选环境中的筛选限制'区域'[T1子类K],对其他字段的筛选限制没有影响。这时,这些包含在ALL()函数中的字段一旦被拖曳至数据透视表筛选环境中,它在数据透视表筛选环境中的作用就会显现出来。例如,当Power Pivot超级数据透视表行标题是“1科技”图书大类的筛选限制时,会得到一个包含图书子类“11机械”、“12电子”和“13网络”的数据源子集。用FILTER()函数的第一个参数ALL('区域'[T1子类K])移除了'区域'[T1子类K]的筛选限制,再用FILTER()函数的第二个参数'区域'[T1子类K]="12电子"对“1科技”图书大类下的数据源子集进行进一步筛选,得到一个图书子类只有“12电子”的更小的数据源子集,最后,用CALCULATE()函数的第一个参数(汇总参数)SUM([T3销售册数])对这个更小的数据源子集进行汇总计算。因此,在“1科技”图书大类下,DAX表达式“电子类册数”对应的数据透视表值区域中的单元格中有相同的数值33。

按照上述逻辑进行推演,在数据透视表筛选环境中图书大类是“2教育”“3文艺”的筛选限制下,不可能得到包含“12电子”图书子类的数据源子集,因此,在“2教育”“3文艺”图书大类中,即使用FILTER()函数的第一个参数ALL('区域'[T1子类K])移除了'区域'[T1子类K]的筛选限制,也不可能得到包含“12电子”图书子类的数据源子集(因为“12电子”图书子类不属于这两个图书大类),这时,用FILTER()函数的第二个参数'区域'[T1子类K]="12电子"对这个本来就不含“12电子”图书子类的数据源子集进行进一步筛选,会得到一个没有任何数据的空集。因此,在图书大类是“2教育”“3文艺”的筛选限制下,DAX表达式“电子类册数”对应的数据透视表值区域中的单元格中没有任何数据(空值)。

现在,如果改变Power Pivot超级数据透视表布局,移除“T3销售册数”字段,那么没有值的字段就不会在数据透视表中显示了,如下图所示。

2.4.7 关于CALCULATE()函数的类比

本节对学过的知识做一个总结,以便温故知新。

对函数的学习,无论是DAX函数,还是Excel工作表函数,都要掌握以下四点:

● 参数个数;

● 数据类型;

● 使用顺序;

● 函数计算结果的数据类型,即函数的计算结果是数字、文本、还是一个表。

这四点是学习函数的四要素。

特别地,对于部分DAX函数,必须增加一点,即了解它的运算逻辑。例如,它是否属于逐行处理函数(如FILTER()函数),函数的参数如何参与运算。

关于函数参数参与运算的顺序,以CALCULATE()函数为例,该函数先处理第二个参数(筛选器参数),然后处理第一个参数(汇总参数)。

我们可以将函数比作一台加工设备。一台加工设备需要按特定的投放顺序投放一种或几种不同的原料(相当于函数的参数),在经过机器加工处理后,产出成品和半成品。成品相当于函数的最终计算结果,半成品则需要用另一种设备继续加工为成品,而这种以半成品为原料,在另一台设备上继续加工的情况就相当于函数嵌套概念。

以家里的豆浆机为例,这台豆浆机就相当于一个DAX函数。豆浆机(函数)需要的原料是豆子和水,豆子和水相当于函数的参数。豆浆机加工产出的产品是生豆浆,生豆浆相当于函数的计算结果。豆浆机用豆子和水作为参数的DAX函数的语法格式如下:

该DAX函数的返回值为生豆浆。

如果豆浆机的使用说明书要求,在使用这台豆浆机时,先放豆子,再放水,投放顺序不能颠倒,那么该要求对应到DAX函数的意思就是,函数的参数必须严格放在其预先定义的位置,顺序不可颠倒,否则函数就有可能计算错误。这里的豆浆机使用说明书就相当于DAX函数的帮助文档。

此外,在使用豆浆机时,不能给豆浆机加错原料(参数类型不能用错)。你不能这样使用豆浆机:

如果函数某个参数位置处需要一个数字类型的参数,你却放置了一个文本数据,那么函数通常会报错,不会得到正确的计算结果。

有些DAX函数有可选参数,类似于豆浆机可以根据个人需要增加一些附加原料。例如,喜欢甜味的豆浆,可以这样:

至于一个函数是否可以使用可选参数,以及可选参数的类型,我们必须查看DAX函数的帮助文档来确定。

DAX函数的计算结果既可以作为最终结果,也可以作为其他DAX函数的参数。正如豆浆机的产品(生豆浆)可以作为最终产品,也可以继续加工,用电热杯煮开作为熟豆浆饮用。如果电热杯也是一个DAX函数,那么该函数的语法格式如下:

这种格式就是函数嵌套。

2.4.8 返回表的CALCULATETABLE()函数

在介绍CALCULATETABLE()函数之前,让我们先回顾一下CALCULATE()函数,这是DAX函数中最重要的一个函数。CALCULATE()函数一般有两个参数,第一个是起汇总作用的DAX表达式(汇总参数),第二个是对数据透视表筛选环境进行修改的DAX表达式(筛选器参数),这两个参数顺序不能颠倒。对于CALCULATE()函数,我们还需要注意它的内部运算逻辑:

CALCULATE()函数在运算时,先执行第二个参数,即先对数据透视表的当前数据透视表筛选环境进行修改,然后在基于修改了的数据透视表筛选环境下,进行由其第一个参数指定的汇总计算。需要注意的是,这里所说的对数据透视表筛选环境的修改,只在CALCULATE()函数的运算过程中有效。

CALCULATETABLE()函数是CALCULATE()函数的兄弟函数,用法和CALCULATE()函数基本相同,不同的是,CALCULATE()函数的第一个参数的计算结果是一个汇总值,而CALCULATETABLE()函数的第一个参数的计算结果是一个表,这也是CALCULATETABLE()函数名称的由来(CALCULATETABLE()函数名称的拼写为CALCULATE+TABLE)。CALCULATETABLE()函数的语法格式如下:

将CALCULATETABLE()函数比作一台加工设备,这台设备需要两种原料,一种是修改当前数据透视表筛选环境的DAX表达式(它的第二个参数,即筛选器参数),另一种是基于修改后的数据透视表筛选环境进行汇总计算的DAX表达式(它的第一个参数,即汇总参数)。这两种原料(参数)的投放顺序为,修改当前数据透视表筛选环境的DAX表达式(筛选器参数)必须放在第二个位置,基于修改后的数据透视表筛选环境进行汇总计算且计算结果为表的DAX表达式(汇总参数)必须放在第一个位置。一定要在函数的正确位置放置正确的参数,否则会导致函数不工作或函数计算结果错误等问题。

之所以先介绍CALCULATETABLE()函数的第二个参数,是因为CALCULATETABLE()函数与CALCULATE()函数一样,其内部运算逻辑都是先执行第二个参数,再执行第一个参数。

CALCULATETABLE()函数的计算结果是一个表,这个表既可以作为最终结果,又可以作为下一个DAX函数的参数,但前提是下一个DAX函数需要这种表数据类型的参数。

观察下面的DAX表达式,该DAX表达式计算的是“2教育”图书大类的销售发生次数(假设数据源中的一条记录表示销售发生一次)。

在Power Pivot数据模型管理界面的DAX表达式编辑区中的单元格中输入上述DAX表达式,并且将该DAX表达式字段“教育图书行数”拖曳至Power Pivot超级数据透视表值区域中,然后调整Power Pivot超级数据透视表布局,如下图所示。这时我们看到,在“教育图书行数”字段中,每个图书大类对应的值都为36。关于这个结果,请自行到数据源中验证。注意,这里算出来的结果不是册数,而是数据源中满足DAX表达式计算结果的记录的条数。

前面在讲解FILTER()函数时提到过,类似FILTER(ALL([字段名]),[字段名]="XXXX")的DAX表达式可以直接写成[字段名]="XXXX",因此,上述DAX表达式可以写成更简短的格式:

注意,之所以在CALCULATETABLE()函数外面包裹一个COUNTROWS()函数,是因为Power Pivot超级数据透视表值区域中的单元格中只能存放数值,而CALCULATETABLE()函数的计算结果是一个表,是无法在Power Pivot超级数据透视表值区域中的单元格中呈现的。因此,这里用COUNTROWS()函数计算出CALCULATETABLE()函数计算结果的行数。

我们知道,CALCULATE()函数可以只有一个参数,同样地,CALCULATETABLE()函数也可以只有一个参数。例如,计算在当前数据透视表筛选环境下图书销售发生的次数(数据源中销售记录的条数),其DAX表达式如下:

对应的Power Pivot超级数据透视表布局如下图所示。

2.4.9 逐行处理汇总函数SUMX()

前面曾经说过,FILTER()函数是一个逐行处理函数。逐行处理函数的内部运算逻辑:对第一个参数所指定的表进行逐行判断或计算,然后将逐行判断或计算的结果以一个表或汇总成一个值的形式呈现。

在DAX函数中,这类具有逐行处理能力的函数有很多,并且这类函数的名称通常以X结尾,如前面学过的CONCATENATEX()函数。CONCATENATEX()函数本质上是一个逐行处理函数,它的功能是逐行取出由其第一个参数指定表中特定列中的内容,然后将该列中的所有内容用指定的分隔符连接成一个长字符串。

本节我们再介绍一个非常有代表性的,并且非常重要的逐行处理函数——SUMX()函数。对于其他逐行处理函数,我们将在以后的章节中逐步引入。

大家可能已经注意到,前面介绍的DAX表达式都是用于计算图书销售册数的,但在现实中,数据分析通常以计算销售金额为主。在本书的数据源中,并没有直接给出图书的销售金额。在Power Pivot中,计算图书销售金额有两种方法,一种是在Power Pivot数据模型管理界面中添加新的计算列,另一种是设计DAX表达式。

用添加计算列的方法计算图书销售金额非常简单,进入Power Pivot数据模型管理界面,在数据表的最后一列任选一个单元格,在公式栏中输入如下计算列公式:

然后双击列标题,将标题名称修改为“销售金额”,如下图所示。

返回Power Pivot超级数据透视表界面,将刚刚添加的计算列字段拖曳至Power Pivot超级数据透视表值区域中,计算结果如下图所示。

上述方法虽然能够完成任务,但是Power Pivot设计人员建议尽量少使用计算列。他们认为,对于超大表来说,使用计算列会消耗较多的计算机软硬件资源。为此,他们专门设计了逐行处理汇总函数SUMX(),将SUMX()函数应用于Power Pivot的DAX表达式中,可以实现同样的计算功能。使用SUMX()函数计算图书销售金额的DAX表达式如下:

在Power Pivot数据模型管理界面的DAX表达式编辑区中的单元格中输入上述DAX表达式,如下图所示。

切换至Power Pivot超级数据透视表界面,将该DAX表达式拖曳至Power Pivot超级数据透视表值区域中,计算结果与采用计算列方法的计算结果是一致的,如下图所示。

下面详细介绍SUMX()函数的使用方法。SUMX()函数是Power Pivot中经常用到的一个函数,该函数的官方说明为,SUMX()函数返回表中“每行计算”的表达式之和,其语法格式如下:

针对SUMX()函数的官方解释,需要强调以下两点。

第一:SUMX()函数是一个逐行处理函数,它能够对第一个参数指定的表中的每行进行逐行处理,每行的处理规则由它的第二个参数决定。理解SUMX()函数逐行处理的运算机制非常重要,这是SUMX()函数区别于非逐行处理函数的特征所在。

第二:SUMX()函数的内部运算逻辑我们是看不见的,我们看到的只是SUMX()函数的最终计算结果。也就是说SUMX()函数能够将其第一个参数指定表的逐行处理结果累加起来,最后作为一个独立的数值呈现给我们。

在讲解CALCULATE()函数时曾经提到,当一个DAX表达式被拖曳至Power Pivot超级数据透视表值区域中时,Power Pivot会隐含地在DAX表达式外面包裹一个CALCULATE()函数,使之能够识别其当前所处的数据透视表筛选环境。

针对本案例,虽然我们只在Power Pivot数据模型管理界面中输入了一个非常简单的DAX表达式:

但是,它却完成了以下逻辑:对DAX表达式“销售金额SUMX”所处的当前数据透视表筛选环境下的数据源子集中的每行,计算出'区域'[T3销售册数]*'区域'[T3销售单价]的结果,并且将这些结果汇总成一个数值。

为了使大家加深对SUMX()函数的理解,我们再举一个例子,顺便复习一下前面学过的ALL()函数,同时引入一个超级简单的算术函数——DEVIDE()函数。

要计算每类图书的销售金额比较简单,用SUMX()函数创建一个DAX表达式,将其拖曳至Power Pivot超级数据透视表值区域中即可。但是,如果要得到当前数据透视表筛选环境下的图书销售金额与图书销售总金额的比值,就需要借助ALL()函数了,其DAX表达式如下:

在DAX表达式中,一般使用DIVIDE()函数代替除号,DIVIDE()函数的语法格式如下:

使用DEVIDE()函数代替除号的特点:当除法的分母为0时,计算结果显示为空,不会显示错误符号。

将DAX表达式“销售额占比01”中的除号用DEVIDE()函数代替后的DAX表达式如下:

DAX表达式“销售额占比01”和DAX表达式“销售额占比02”的计算结果如下图所示。