VAR变量可以在任意计值环境进行定义,并且它仅在定义时的计值环境里计算一次,在后续的引用中都不再计算,因此VAR变量其实是个常量,但为了与别的资料兼容,因此仍然称呼其为变量。
通过VAR变量,可以在计值环境即将发生变化之前,及时地使用当前的计值环境来计算所需要的值或提取所需要的表,并存储进变量里面。因此,使用VAR变量可以把复杂逻辑进行拆解,从而有效地简化DAX表达式的书写过程,甚至提高DAX表达式的性能。所以,善用VAR变量是成为高手的一个标志!
VAR变量的语法
语法主要为:
VAR <name1> = <expression1>
VAR <name2> = <expression2>
VAR <name3> = <expression3>
....
RETURN
<result expression>
其中,一个VAR
关键字对应一个变量,变量可以定义多个,变量的表达式结果可以是标量值或表,最终通过RETURN
语句返回一个结果,这个结果可以是标量值,也可以是表。在result expression
里可以引用之前定义的各个变量,当然不引用也没问题,但不引用的话何必定义变量呢。
此外,VAR变量是可以嵌套的,即在某个变量的表达式中继续使用VAR变量,此时的语法为:
VAR <name1> = <expression1>
VAR <name2> =
VAR <name2_1> = <expression2_1>
VAR <name2_2> = <expression2_2>
....
RETURN
<result expression>
VAR <name3> = <expression3>
VAR <name4> = <expression4>
....
RETURN
<result expression>
上面这个语法是嵌套了两层VAR变量时的大概结构,当然也可以继续嵌套下去,只要语法正确,那么嵌套多少层都行。而且,不仅只是变量的表达式中可以嵌套VAR变量,在RETURN语句后的结果表达式中也是可以嵌套VAR变量的,而且也可以嵌套多层。比如:
VAR <name1> = <expression1>
VAR <name2> =
VAR <name2_1> = <expression2_1>
VAR <name2_2> = <expression2_2>
....
RETURN
<result expression>
VAR <name3> = <expression3>
VAR <name4> = <expression4>
....
RETURN
VAR <name5> =
VAR <name5_1> = <expression5_1>
VAR <name5_2> = <expression5_2>
....
RETURN
<result expression>
RETURN
<result expression>
由此可见,VAR变量的语法是很灵活的,但只要遵循一个最基本的原则就可以确保不出错,即:在每一个层级内,有VAR必有RETURN。
使用VAR变量时的注意事项
虽然VAR变量的语法很简单,但也有许多注意事项需要注意,否则很容易就会出错,具体如下。
1、变量的命名
变量的名称只支持英文字母、数字、以及下划线,并且不能以数字开头。而且,变量的名称也不能与DAX引擎的保留关键字相同,比如各种函数名称等。此外,后定义的变量名称也不能与之前已定义的变量名称相同,但内层嵌套的VAR变量名称可以使用外层的变量名称,但这会使得内层嵌套的VAR变量覆盖外层的同名变量。
如果变量命名不正确,那么DAX引擎将报错,如下图所示:
2、变量的引用顺序
在各个变量的表达式中,后定义的变量表达式中可以引用之前定义的变量,而RETURN语句后的结果表达式则可以引用同一层级内的所有变量。需要注意引用顺序是按变量定义的先后来决定的,否则DAX引擎将报错。
3、变量的作用域
我们已经知道,后定义的变量表达式中是可以引用之前定义的变量的,并且后定义的变量名称也不能与之前定义的变量名称相同,但这只是针对单层VAR变量的语法而言的。当存在VAR变量的嵌套时,内层的VAR变量的名称是可以使用外层的变量名称的,因此想要知道某个变量引用所指代的值具体是哪个,那么就要把变量的作用域搞清楚。如果你有编程语言的基础的话,那么这里的变量作用域,可以类比成编程语言中的全局变量与局部变量。
变量的作用域规则其实很好理解,即内层的变量可以引用外层的先定义变量,并且内层的变量可以覆盖或屏蔽外层的同名变量。因此我就不再赘述,下面直接看示例:
4、表变量中的列引用
从上面的讲解中我们可以知道,变量的表达式返回的结果可以是标量值或表,如果返回结果是表,那么一般称这个变量为表变量。那么此时,如果我们想要引用这个表变量中的某个列,对其进行聚合,又该如何操作呢?
很多人可能会想到聚合基础表中某个列的方式:SUM(TableName[ColumnName])
,从而将其也应用到表变量中。但这是错误的语法,表变量中的列不能通过 TableName[ColumnName]
的方式进行引用。因此在这种情况下我们不能使用语法糖进行偷懒,需要使用聚合函数的对应迭代函数的等价写法,如:SUMX(TableVariableName,[ColumnName])
。
5、变量的计算次数
在本文的开头就已经提到过:“VAR变量可以在任意计值环境进行定义,并且它仅在定义时的计值环境里计算一次,在后续的引用中都不再计算,因此VAR变量其实是个常量。”
需要重点记住,VAR变量只计算一次,在后续的引用中都不会再次计算,哪怕后面计值环境发生变化了,它也是不会再次计算的。即VAR变量在首次计算完后,它就变成了一个常量,这一点需要重点记忆,能否真正理解VAR变量就看能否理解这一点了。下面来看一个帮助理解的例子:
VAR变量的应用场景
1、提高代码可读性
当所写的DAX表达式过长或各种函数的嵌套过多时,此时不妨定义一些VAR变量来存储部分结果,分步骤完成计算,从而提高DAX表达式的可读性,也更优雅。
下面来看一个未使用VAR变量的DAX表达式:
=
SUMX (
SUMMARIZE ( 'T3销售', '日期表'[Date], 'T0大类'[T0大类K] ),
IF (
[Sales] <> BLANK (),
CALCULATE (
[Sales],
LASTNONBLANK (
FILTER ( ALL ( '日期表'[Date] ), '日期表'[Date] < EARLIER ( '日期表'[Date] ) ),
[Sales]
)
)
)
)
然后将其用VAR变量改写,结果如下:
=
SUMX (
SUMMARIZE ( 'T3销售', '日期表'[Date], 'T0大类'[T0大类K] ),
IF (
[Sales] <> BLANK (),
VAR CurrentDay = '日期表'[Date]
VAR PastDate = FILTER ( ALL ( '日期表'[Date] ), '日期表'[Date] < CurrentDay )
VAR LastDay = LASTNONBLANK ( PastDate, [Sales] )
RETURN
CALCULATE ( [Sales], LastDay )
)
)
这里先不管这个DAX表达式的作用,只对比使用VAR变量前后的可读性。可以发现使用VAR变量后,代码被分步骤完成,从而使一个具有多重嵌套的复杂表达式变得容易阅读起来,所以这就是VAR变量的一个应用场景,即提高代码可读性。
2、降低DAX表达式的复杂性
VAR变量的一个特性就是仅在定义时的计值环境里计算一次,在后续的引用中都不再计算。因此,我们可以利用VAR变量的这个特性来降低DAX表达式的复杂性。
那么该如何降低复杂性呢?来考虑这样一个场景,我们已经通过DAX表达式更改初始计值环境,但现在想在更改后的计值环境里使用在初始计值环境下才能计算到的某个值,那么此时是不是要把更改后的计值环境还原回初始计值环境才能计算到那个值?是不是想一想都觉得麻烦?
因此,我们可以利用VAR变量仅计算一次的这个特性,在计值环境即将发生变化之前,及时地使用当前的计值环境来计算所需要的值或提取所需要的表并存储进变量里面,这样哪怕后面计值环境发生变化了,但变量的值是不会变化的,从而方便了后续的计算,进而降低了DAX表达式的复杂性。
3、提高计算效率
由于VAR变量仅在定义时的计值环境里计算一次,在后续的引用中都不再计算。因此当我们需要多次重复计算同一个值时,此时可以将该值定义成VAR变量,从而减少计算次数,进而提高计算效率。
比如下面这个计算销售金额年同比增长率的DAX表达式就多次计算了去年的销售金额,增加了不必要的计算开支:
Sales YoY Growth % =
DIVIDE (
[Sales] - CALCULATE ( [Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) ),
CALCULATE ( [Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
)
下面使用VAR变量对其进行改进:
Sales YoY Growth % =
VAR SalesPriorYear =
CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date]))
RETURN
DIVIDE([Sales] - SalesPriorYear, SalesPriorYear)
改进后的DAX表达式中,去年的销售金额仅计算一次,在保证结果正确的同时,也大大提高了计算效率。
DAX系列文章中涉及到的案例文件,均已上传到QQ群:344353627,若有需要,可自行加群获取。