为了更好地介绍筛选器之间地交互行为,我们需要一些前置知识。所以,本篇文章将介绍CALCULATE与CALCULATETABLE函数的初步应用,等介绍完筛选上下文的全部内容后,再继续深入介绍这两个函数。这两个函数是DAX中最强大也是最重要的函数,对它们的学习与使用贯穿了DAX的整个知识体系,可见其地位。


  CALCULATETABLE函数与CALCULATE函数可以说是完全一致,唯一的区别就是CALCULATE函数的第一参数必须返回标量值,而CALCULATETABLE函数的第一参数则必须返回表。当我们掌握了CALCULATE函数后,自然就能理解CALCULATETABLE函数了,而且CALCULATE函数是最常用的,因此下面将重点介绍CALCULATE函数。

  在正式开始介绍前,我们再来重温一下两个注意事项:

  1. DAX引擎会自动为每一个度量值的引用套上一个CALCULATE函数;
  2. 行上下文遇到CALCULATE函数时,会发生行上下文转换,变成筛选上下文。

  首先,CALCULATE函数的第一参数被称为计算器参数,这个参数是必选的,且第一参数必须是一个返回标量值的DAX表达式。而第二参数则是可选可重复的,这些参数被称为内部筛选器参数。内部筛选器参数除了省略外,也可以接受布尔筛选、表筛选以及筛选调节器,我们可以依靠这些内部筛选器参数来修改现有的筛选器组合,从而得到新的筛选上下文。其中,布尔筛选是表筛选的一种语法糖形式,其本质上仍然为表筛选,而筛选调节器则是一种比较特殊的内部筛选器,它能改变新的筛选上下文的生成方式。CALCULATE函数的内部筛选器可以分为两类,一类是以布尔筛选与表筛选为主的普通内部筛选器,另一类则是筛选调节器,主要由各种调节器函数构成。

  最后,CALCULATE函数的计值流程可以简单概括为:先在外部计值环境下计算各个内部筛选器参数,然后内部筛选器修改外部计值环境,最后计算器参数(第一参数)在修改后的计值环境里计值。其中,多个内部筛选器参数之间的计值互相独立,互不影响。


  无内部筛选器

  先来看一种比较简单的情况,那就是不含内部筛选器时的CALCULATE函数。在这种情况下,主要就是利用CALCULATE函数会发生行上下文转换这一特性来修改外部计值环境,若不存在行上下文,那么CALCULATE函数是可以省略的。此外,由于DAX引擎会自动为每一个度量值的引用套上一个CALCULATE函数,所以下面几个度量值的引用等价:

度量值1 = SUM('Sales'[Amount])       
	 
度量值2 = CALCULATE( SUM('Sales'[Amount]) )
	
度量值3 = CALCULATE(CALCULATE( SUM('Sales'[Amount]) ))

度量值4 = CALCULATE(CALCULATE(CALCULATE( SUM('Sales'[Amount]) )))

...

  上面的几个度量值等价的原因是因为CALCULATE函数不含内部筛选器,因此不会修改外部计值环境,从而使得最外层的CALCULATE函数把其所处的计值环境不断地向内层CALCULATE函数传递,又因为没有修改,所以最终的计算器参数所处的计值环境都是一致的,因此等价。

  布尔筛选

  CALCULATE函数的内部筛选器参数可以接受布尔筛选,即可以接受结果为TRUE或FALSE的表达式,因此我们可以通过布尔筛选来添加筛选器。由于布尔筛选是表筛选的一种语法糖,所以必须遵守一些语法规则:

  • 可以引用单个表中的列(在旧版本的DAX引擎中只能引用单列)
  • 它们不能引用度量值
  • 它们不能使用嵌套的 CALCULATE 函数
  • 它们不能使用扫描或返回表的函数,包括聚合函数

  下面展示了一些使用布尔筛选的度量值表达式:

度量值1 = CALCULATE(SUM('Sales'[Amount]),'Sales'[Order Quantity] > 100)

度量值2 = CALCULATE(SUM('Sales'[Amount]),'Product'[Product Color] = "White")

度量值3 = 
CALCULATE(
	SUM('Sales'[Amount]),
	'Product'[Product Color] = "White" 
		|| 'Product'[Product Color] = "Red"
		|| 'Product'[Product Color] = "Yellow"
)

度量值4 = 
CALCULATE(
	SUM('Sales'[Amount]),
	'Product'[Product Code] = "P1001" && 'Product'[Product Color] = "Yellow" 
)

  在布尔筛选中,由于不能使用扫描或返回表的函数,也不能使用度量值或嵌套CALCULATE函数,因此允许在布尔筛选表达式中使用的函数非常少。对初学者来说,我们就简单的把布尔筛选表达式理解成是某个列的值与标量值的比较与判断,而不要在布尔筛选表达式中使用任何函数,以减少出错的可能,就像上面展示的度量值示例一样。

  在使用旧版本的DAX引擎的工具里(例如Power Pivot),布尔筛选表达式只允许出现单个列的引用,例如上面展示的前三个度量值。而在最新版本的DAX引擎里(例如最新版本的PowerBI),布尔筛选表达式则允许引用单个表上的一个或多个列,如上面展示的最后一个度量值写法。若布尔筛选表达式只引用单列,那么添加的是标准筛选器,若布尔筛选表达式引用了单个表上的多个列,那么添加的则是固化筛选器。为了更好地理解这种区别,下面给出布尔筛选的等价写法:

度量值1 = CALCULATE(SUM('Sales'[Amount]),'Sales'[Order Quantity] > 100)

等价于:

度量值1 = 
CALCULATE(
	SUM('Sales'[Amount]),
	FILTER(ALL('Sales'[Order Quantity]),'Sales'[Order Quantity] > 100)
)

------------------------------------

度量值4 = 
CALCULATE(
	SUM('Sales'[Amount]),
	'Product'[Product Code] = "P1001" && 'Product'[Product Color] = "Yellow" 
)

等价于:

度量值4 = 
CALCULATE(
	SUM('Sales'[Amount]),
	FILTER(
		ALL('Product'[Product Code],'Product'[Product Color]),
		'Product'[Product Code] = "P1001" && 'Product'[Product Color] = "Yellow" 
	)
)

  如果你是初学者,那么很可能会看不懂,这是很正常的。因为DAX的知识体系是互相影响,不可分割的,等学习了后续的知识后,那么这部分的内容自然就可以掌握了。你现在只需要知道以下几点即可:

  • 布尔筛选是表筛选的一种语法糖形式,它的本质仍然是表筛选
  • 布尔筛选与表筛选都是普通的内部筛选器,作用是向外部计值环境添加筛选器
  • 内部筛选器会与外部的筛选器交互,生成新的筛选上下文,计算器参数在新生成的筛选上下文上计值
  • 上面所说的筛选器交互方式会在后续的文章中介绍

  我们再来看一下上面给出的度量值3的写法:

度量值3 = 
CALCULATE(
	SUM('Sales'[Amount]),
	'Product'[Product Color] = "White" 
		|| 'Product'[Product Color] = "Red"
		|| 'Product'[Product Color] = "Yellow"
)

  在上面这个度量值里,使用了布尔筛选增加了一个产品颜色列上的筛选器,筛选颜色为白色或红色或黄色的产品,而这种写法其实是比较繁琐的,我们可以使用表构造器与IN运算符来简化代码的书写量,简化后的写法与上面的写法是等价的,具体写法如下:

度量值3 = 
CALCULATE(
	SUM('Sales'[Amount]),
	'Product'[Product Color] IN {"White","Red","Yellow"}
)

  在继续介绍前,先简单介绍一下表构造器的语法。表构造器,顾名思义就是构造一张表,需要我们手动输入表的每一个数据,用大括号包围,并用小括号分隔多行。当要构造的表只有一列时,小括号可以省略。例如,以下写法等价:

{1,2,3,4,5}

{(1),(2),(3),(4),(5)}

  当要生成的表有多列时,小括号则不能省略,用小括号来分隔每一行,小括号内用逗号分隔不同的列值。具体可见下图:

  在了解了表构造器的语法后,我们再来看一个布尔筛选的例子:

度量值5 = 
CALCULATE(
	SUM('Sales'[Amount]),
	('Product'[Product Color] = "White" && 'Product'[Product Code] = "P1001") ||
	('Product'[Product Color] = "Red" && 'Product'[Product Code] = "P1002") ||
	('Product'[Product Color] = "Yellow" && 'Product'[Product Code] = "P1003")
)

  上面这个例子的筛选条件更为复杂,但也可以使用表构造器与IN运算符进行简化,简化后的写法与上面的写法是等价的,具体写法如下:

度量值5 = 
CALCULATE(
	SUM('Sales'[Amount]),
	('Product'[Product Color],'Product'[Product Code]) 
		IN {("White","P1001"),("Red","P1002"),("Yellow","P1003")}
)

  表筛选

  上篇文章说过,筛选器是以元组来表示的,而表又可以转化成元组,因此我们可以使用表来作为CALCULATE函数的内部筛选器参数,这就是所谓的表筛选。我们可以把基础表以及由表函数返回的表作为内部筛选器参数,简单来说就是可以使用任意来源的表作为内部筛选器参数,但要注意不同表上各列的数据沿袭,不具备数据沿袭的列上的筛选器将不能筛选模型中的数据。数据沿袭等相关知识将在后续文章中介绍。

  当表作为内部筛选器参数时,它会自动转化成筛选器元组,若该表具有多个列,那么它就会转化成固化筛选器,若该表只有单个列,那么就会转化成标准筛选器。但无论是哪种筛选器,它都是用元组来表示的,因此筛选的方式与Excel高级筛选的条件区域定义的类似,即:相同行上不同列的值是且的关系,不同行上的值是或的关系。

  下面展示了一些使用表筛选的度量值表达式:

度量值1 = CALCULATE(SUM('Sales'[Amount]),'Sales')

度量值2 = 
CALCULATE(
	SUM('Sales'[Amount]),
	FILTER(ALL('Sales'[Order Quantity]),'Sales'[Order Quantity] > 100)
)

度量值3 = 
CALCULATE(
	SUM('Sales'[Amount]),
	FILTER('Sales',RELATED('Product'[Product Code]) = "P1001")
)

  如上面所展示的例子一样,我们可以把基础表以及由表函数返回的表作为内部筛选器参数,其中,CALCULATE函数搭配FILTER函数的写法是最经典的写法。

  最后,再给一个建议,那就是在掌握扩展表原理之前,不建议使用基础表作为内部筛选器参数。因为当基础表作为筛选器时,它转化成的筛选器元组中不仅包含了该基础表自身的列,还包含了其扩展表上的其他列,这对初学者来说是很容易出错的,因此最好接受我的建议,等后续掌握了扩展表原理后再使用基础表作为内部筛选器参数,在这之前则尽量使用布尔筛选以及只有单个列的表作为内部筛选器参数。

  筛选调节器

  筛选调节器是一种比较特殊的内部筛选器,它能改变新的筛选上下文的生成方式,它主要由各种调节器函数构成,例如:ALL、ALLCROSSFILTERED、ALLNOBLANKROW、ALLEXCEPT、ALLSELECTED、KEEPFILTERS、CROSSFILTER、USERELATIONSHIP、REMOVEFILTERS等函数。

  上述的某些调节器函数不仅可以用作筛选调节器,还可以用作表函数。那么现在我们只需要简单了解下有哪些调节器函数即可,在后续的文章中会逐步介绍每个调节器函数。


  最后做个总结吧,本篇文章主要介绍了CALCULATE函数的语法结构,以及内部筛选器参数的使用和注意事项,但仅止步于浅层介绍,连一个实际的例子都没有,比较枯燥。因此读完这篇文章后可能对CALCULATE函数仍然是一知半解,连基本的计值流程可能都搞不清楚。这是很正常的,因为CALCULATET函数贯穿了DAX的整个知识体系,很难用一篇文章去介绍清楚,必须要先学习其它知识才能掌握CALCULATE函数,但介绍其它知识时又离不开CALCULATE函数。所以,看完本篇文章后觉得毫无收益是很正常的,你只需要记住以下几点内容,为后续学习其它知识做好铺垫就可以了:

  • 布尔筛选本质上是表筛选
  • 内部筛选器参数是最先进行计算的,且多个内部筛选器参数之间的计值互相独立,互不影响
  • 布尔筛选与表筛选都是普通的内部筛选器,作用是向外部计值环境添加筛选器
  • 内部筛选器会与外部的筛选器交互,生成新的筛选上下文,计算器参数在新生成的筛选上下文上计值
  • 上面所说的筛选器交互方式会在后续的文章中介绍

  DAX系列文章中涉及到的案例文件,均已上传到QQ群:344353627,若有需要,可自行加群获取。

  加入Q群