CALCULATE之庖丁解牛系列 -- CALCULATE专解(3)
把一些东西写出来,其实是蛮需要时间的。有时候可能是出于爱好、有时候是因为能帮助自己静下心来。
该系列不承诺全部内容连载完整以及即时的错误修订,只是基于对DAX的热爱而不定期更新。本人声明:不对你由此系列造成的理解、操作错误等承担任何责任。本系列最后有关于DAX三板斧(三步曲)的第一次结论性论述(这是用于DAX逻辑书写的通用性归纳。但必须有前面的辅垫。事先申明,DAX三步曲为Power BI非官方独家专有词)。
DAX具有很大的抽象性,也具有本身的一些逻辑规律和特点。但所有关键性的问题几乎都涉及到CALCULATE。它是整个BI业务逻辑建模基础和核心(企业级BI同样需要)。想要理解它,需要时间和实践,学过IT代码的也不会是你成为DAX专家的理由或资本。
因为“DAX语言”的目标是“全民BI”,偏重于解决业务逻辑层面,并以构建自由式业务数据模型为主。
所有这些,都在提示我们,应该以一种学习一门语言的方式来研究它。很多人很在乎一些DAX的内部运行、很迷恋官方“教科书”的措辞或概念。
当然,我们绝不是、也不能说官方的是错的(相反,参考的就是它们)。我们只是想以自己更能理解的简体汉文的方式来认识它(比如读不准某个英文发音时,我们可以使用自己的方言注释一下,理解其意思并能利用它,谁会关心你理解它的方式?)。
所以我们说,学习DAX,你可以以你理解的任何方式了解它。当你的了解不能解释更多的问题时,你需要增加你的词汇量以便能更好的理解并与之沟通。总有人说,我已好久没用过它了,几乎全忘了。这其实是很有问题的说法,既然DAX是一种语言,哪有那么容易忘记的?就像是我们学会了英语,几天不说,难道就全忘了?
走题了,回到主题。上部分我们主要谈到,要以列表的思维方式来理DAX(CALCULATE)的行为。并强调了列表的两种存在形态:值列表与列表,以及对应的值列表(隐式行)筛选与显式列表筛选。也提及到还没有详细讨论的列表基数(唯一值)。
而且,我们说,在数据模型里其实是没有维度的(即没有事实表与查找表之分),只有单列表或具有列基数关系的单列表,它们构成数据模型以及筛选+计算......。
所有这些要了解的内容,都是一些能跟DAX沟通的语言方式,虽然还并不流利,还不完整,但那只是时间与实践的问题。
至少目前,我们试图通过一些简单的理解,来解释一大堆DAX中遇到的困惑问题。但在我们需要写出比元度量更复杂一些的公式前,还需要了解关于值列表和列表的一些基础。
第10式:CALCULATE的列表数据类型与列值格式
我们知道,DAX可以对不同的数值类型进行计算,其中包括:整数、十进制数、货币(其实际还是整数,内部以整数存储)、布尔值 (TRUE/FALSE,真/假)、文本、二进制大对象等7种。
你可以分别设置列表的数据类型(如果对某列中的值的类型有要求,还可以设置需要的列值的格式)。
DAX拥有一个强大的类型处理系统,因此不必过多担心数据类型。
在DAX术语中,布尔值被称为TRUE/FALSE,其实际是值列表(将某列的值定义为:符合条件的与不符合条件的两类列值),我们倾向于遵循事实上的命名标准,将它们称为布尔值。
注意:布尔数据类型常用于表示逻辑条件。而且,由于它将整个列基数分成事实上的两部分(被TRUE / FLASE分割)。因而,布尔逻辑条件的内部执行是相当快的。这将是DAX优化的一种可能选择。
例如,由以下表达式定义的计算列是布尔型:
= Sales[Unit Price] > Sales[Unit Cost]
你可以看到,该布尔型数据类型也可以是:TRUE= 1和FALSE = 0的数字。额外的,这对于排序的时候可能也有用,因为TRUE> FALSE(我们经常会计算那些显式出现在透视表里的行:比如当前有销售的商品,你可以将它定义为TRUE= 1,而那些当前没有出现的无销售商品则定义为FALSE = 0,它可能也是你需要计算的部分)。
DAX中一个比较需要注意的是值列表形式: BLANK() --空值或缺失值
在DAX公式中,BLANK() 代表没有任何值、缺失或空的值。我们可以通过BLANK() 函数(不含参数)以获得DAX的该值。这样的值只能作为DAX表达式的结果,不能在任何比较语句中使用它(请参见ISBLANK)。
在数字表达式中,BLANK()被自动转换为0,而在字符串表达式中,BLANK()将被自动转换为空字符串—在表达式结果中保留空白的某些异常(如产品、除法的分子或空格的总和等)。
在今后的示例中,你将看到DAX在不同的操作中如何处理数字、字符串和布尔数据类型的BLANK()。通常,我们使用BLANK()作为表达式的结果。
注意:一个DAX表达式,其结果的类型是基于表达式中使用的公式所定义的类型。
需要意识到这一点,是因为你需要防止:从DAX表达式返回的类型可能不是预期的类型。那么,你必须重新检查表达式本身中公式指代的数据类型。
如果返回的类型不是你需要的,比如文本格式的数字,例如以一个或多个0开头的文本格式,结果可能是不为零的整数数字,这是内部引擎依据公式定义的类别,而不是元列表类别,你需要在公式中定义其类型。
再例如,如果一个求和项是一个日期,那么,其结果也会是一个日期。然而,如果它同一个整数运算符一起使用(时期类型将被引擎内部隐式处理为整数类型),则结果将是一个整数。
这就是“操作符重载”,可以看到图中的行为示例。
如下图,[时期_7天]列,是通过在[发货时期]列的值中+7来计算的。结果为一个日期类型的列(你定义的是时期类别计算)。 也就是说,将一个整数添加到日期结果中,日期增加了相应的天数(往后 “+”,往前“-”)。
当然,你使用“操作符重载”还可以处理某些特别的字符结果需要。除了“运算符重载”之外,当需要操作符时,DAX会自动将字符串和数字都转换成数字。例如,如果使用& 运算符,将字符串串联起来,则DAX将其参数转换为字符串。你看这个公式:
= 5&4 -- 返回字符串形式“54”;
= "5" + "4" -- 返回值为9的整数结果。
这些产生的值将依赖于操作符定义转换,而不是数据源的列。即使这看起来很方便,但在这些自动转换过程中将会发生什么类型的错误,不得而知。
因此,我们建议你避免自动转换,强调该规则是帮助你理解类型转换的特点。如果确实需要进行某种转换,那么,对其进行控制并将其转换成显式化处理,则会更好。为了更加明确,前面的例子:= "5" + "4" ,应该使用值转换函数来定义它:
= VALUE("5") + VALUE("4")
对于习惯于Excel或其他语言的人来说,DAX数据类型可能是熟悉的。你可以专门就这一部分,去官方了解DAX数据类型的规范。但是,对于每种数据类型,使用通用的方式处理也是很有用的。例如,任何一个计算,只要你定义的列表是数值类型,则结果一定是数值类型列表。
因此,我们将刚刚示例的:一个整数添加到一个时期(时间)值时,该值会增加相应的天数。但是,你可能会习惯于使用专用的转换函数:FORMAT函数从日期列里提取日、月和年等,这会更方便。
Day=DAY( Calendar[Date] )
Month=FORMAT(Calendar[Date],"mmmm")
Year=YEAR(Calendar[Date] )
你需要知道,通常列表类型转换是自动发生的,并且一般不需要你在表达式中调用转换函数。即使在某些情况下,可能你有明确的意图,希望这样做以强制某个特定的行为或使我们的语句更易于阅读。
货币数据类型是一个定点小数,这在财务计算中非常有用。而datetime数据类型使用浮点数存储该值,其中整数对应于天数,例如:
=NOW()+0.5 (如果你在公式里定义该条件,+0.5的形式是告诉DAX,你是将某个值列表的值+0.5,无论原NOW()类型什么,引擎总是以数值来处理它,很简单,因为只有值列表才能加减乘除计算)
如果我们需要一个将业务时间延长12个小时(这正好是半天(表示为+0.5),也可以换算成其他多少小时对应的天数)。但是,你应该考虑使用特定的DAX函数,比如 DATEADD,以便使代码更具可读性。如果只需要DATETIME的日期部分,请始终记住:使用TRUNC来去掉小数部分(而不是乘以某个适当的整数值)。
之外,由于列类型由最后的计算定义的结果决定,当结果类型错误时,你需要仔细检查你是否有改变元列表类型的定义行为。如图: 我们定义某个度量值列表的2.5% 倍计算,然而这种表示会让DAX迷糊,从而出现提示错误:无效的标记。
这应该不是DAX的BUG,如果将其作为BUG修复,估计非改动引擎不可。而我们只需要修改2.5%为0.025即可。原因很简单,DAX认为你定义了两种类别给它:一个是作为值的2.5,一个是作为文本字符串的“%”符号。虽然这个案例有些特别,也是一个不大不少的美丽的误会,但对于你了解列表类型,发现问题的原因有帮助。因为在今后的实践中,你还会遇到各种问题。
第11式:CALCULATE列表的逻辑操作符
与列表数据类型相关的另一个概念是:DAX(CALCULATE)的逻辑操作符。因为操作符在确定表达式类型时具有决定性:DAX表达式的结果类型,是基于公式定义的类型。
下图,显示了在DAX语言中可用的操作符列表:
直至目前为止,你可以将凡是使用操作符构建的列表单元,都理解为CaIcuⅠate的布尔值逻辑条件(或值列表条件),重要的还是老生常谈的那句话:结果总是列表。此外,为了兼容Excel语法,逻辑运算符也有相应的DAX函数可用。例如,你可以编写表示“或”以及“并”关系的操作符:
在DAX表达式中,像这种使用“中国”或“0”这样的标量值,或者,引用列表中的行值,它们都是列值。当我们引用一个列以获取其列值时,我们已经知道可使用以下基本语法:
'表的名称'[列名称]
你发现,这都是在单列表上的操作。
如果需要操作列表的多个列值的逻辑运算符,除了OR之外,还提供了支持IN函数的逻辑运算符,DAX引入的这两个新函数的语法:它对应列表和值列表构造函数(OR或IN),这使得我们可以创建“虚拟”表来比较两个或两个以上的不同列值,而不仅仅是一个单独的列值(基数)。
例如:
CALCULATE ([销售额],
商品信息[颜色] ="红色" || 商品信息[颜色] = "黑色") -- “或”的关系
有了IN的语法,你可以这样写:
CALCULATE ([销售额],
商品信息[颜色] IN ("红色" ," "黑色")
正如公式所示,在度量表达式中使用IN操作符,可以比较一个或多个列值(相当于多个列,因为每个布尔值范围的列值将构成新的值列表)。前面的例子中,在操作符后面的语法是一个列表构造函数,当它的内容有多个列值时,都可以有一个行值构造函数(值列表)。
IN运算符实际是一个CONTAINSROW函数,上述IN公式还可以写成以CONTAINSROW定义的逻辑运算符:
CALCULATE ([销售额],
CONTAINSROW({"红色","黑色"},
商品信息[颜色]))
关于逻辑操作符后面还将讨论,这里从略。
值得指出的是,当需要编写复杂的多个逻辑条件时,使用函数方式而不是操作符方式,来处理布尔逻辑将变得非常有用。事实上,后面的列表筛选还将介绍。你可以将单个值列表筛选看成是单个不同的布尔值逻辑。
实际上,在格式化比较长的代码段时,函数方式比操作符更容易格式化和读取。但是,函数的一个主要缺点是,只能一次传递两个参数。如果计算需要两个以上的条件时,需要函数嵌套。你可以根据实际决定何种方式。
条件语句(IF、SWITH)
由于DAX是一种函数式语言,经典的IF条件语句也可用于DAX公式。
第一个也是最常用的条件语句是IF,它有三个参数:第一个是测试条件,第二个是如果第一个参数计算为TRUE时返回的值,第三个是最后要返回的值。如果省略第三个参数,则默认为空。例如:
IF ( 20 <15, "second", BLANK() ) = BLANK()
1)我们可以使用嵌套IF语句检查表达式的不同值(实际也可理解为:布尔值逻辑)。但是,这种情况,我们可以通过使用SWITCH函数获得更多可读的代码,以代替嵌套的IF语句。在数据模型内部,它生成的代码与嵌套的IF语句完全相同,而且性能也是相同的。
需要注意的是,SWITCH函数的第一参数有两种形式:
SWITCH(销售表[销售] ......,第一种为函数引用的某个单列表。
2)我们还可以使用SWITCH来测试不同的、不相关的条件,而不只是匹配由表达式返回的单个值。这可以通过将TRUE()作为SWITCH的第一个参数来获得,接着再编写其要测试的每个条件的逻辑表达式。例如,我们可以编写下面的SWITCH语句:
SWITCH(TRUE(), ......),
例如:我们将商品信息列中的商品,按价格高低(来自[价格]列),设定为四挡:
SWITCH(TRUE(),
商品信息[价格]<10,"低档",
商品信息[价格]<50,"中档",
商品信息[价格]<100,"高档","精品")
注意:SWⅠCTH函数无论哪种形式,其实际是针对一列的全部列值的"值列表"分割。比如前面的[价格]<50为中档,表达的列值是10<中档<50这个范围,因为前面的<10已将所有该范围的列值从列中分割。所以,接下来的<50,并不需要“and"的(<50,并且>10),因为这时候的列值已没有<10的列值,以此类推。这是写SWlCTH函数需要注意的。
你可以使用ISBLANK函数检查表达式是否为空。一个常见用法是为表达式定义一个默认值,以防止其计算结果为空值,如下例所示:
IF(ISBLANK(Sales[数量]),1, Sales[数量] ) --"1"为指定返回的值(或其他值替代"1")。
你还可以使用ISERROR函数检测表达式中的错误。例如,如果想避免在语句中传递错误值,你可以编写这样的表达式:
IF(ISERROR(SQRT(Test[Omega])), BLANK(), SQRT(Test[Omega]) )
当实现前面的模式,在出现错误时返回一个值时,可以使用ISERROR避免重复相同的表达式,如果它产生一个错误,会自动返回作为第一个参数传递的表达式:
IFERROR(SQRT(Test[Omega]),BLANK() )
请记住,应尽量避免ISERROR和IFERROR的使用。预先处理错误总不失为更好的选择。例如,如果前面例子中对SQRT--平方根的关注是负数的存在,那么应该使用这个表达式: IF(Test[Omega]>=0,SQRT(Test[Omega]),BLANK())
另外,一个常见的测试,是检查计算比率时的分母值。在这种情况下,应该使用DIVIDE函数。所以,与其写这个:
IF(Sales[Quantity]=0,BLANK(),Sales[SalesAmount] / Sales[Quantity])
还不如采用:DIVIDE(Sales[SalesAmount], Sales[Quantity])
基于:尽量避免使用ISERROR和ISERROR的劝告,我们应该始终使用DIVIDE,以保护表达式不受division -by- zero错误的影响,因为它提供了比基于IF方法更好的性能。
接下来,则是对应于处理列表的常用函数。
第12式:CALCULATE的常用函数
既然我们将列表区分为值列表、列表两种,并作为与CALCULATE沟通的语言方式,那么,他应该适应于到目前为止的大部分问题
当然,这种区分可能并不准确,只是便于学习和记忆的方式。
2、几乎每个数据模型都需要对数据进行聚合操作。
DAX提供了一组用来集合表中列的值并返回每个值的函数。我们称这组函数为聚合函数。例如,下面的度量:计算Sales表销售栏中所有数字的总和:
Sales : = SUM( Sales[Amount] )
如果在计算列中使用该表达式,那么这个表达式(SUM)就会聚合表中的所有行;在一个度量中使用它,它将只考虑数据表中由切片器、行、列和筛选条件的当前行。
1) 一般来说,聚合函数:SUM、AVERAGE、MIN、MAX、STDEV等,只针对数值或日期类型操作。
2) MIN和MAX是另一个比较特别的函数:如果使用这两个参数,它们将返回两个参数的最小值或最大值。因此,MIN(1、2)将返回1,而MAX(1、2)返回2。这个功能在2015年引入,在需要计算复杂表达式的最小值或最大值时非常有用,因为它避免在IF语句中多次写入相同的表达式。
之前我们使用MIN和MAX的值列表特性,还可以作为是否唯一值的判断依据。例如,使用MIN[列],用于切片器的唯一值筛选,因为当一列中只有一个值时(切片器被选的值),最小、最大值都是其本身值)。
3)与Excel类似,DAX为上述函数还提供了另一种语法:可以在列上进行计算,也可以同时包含数值和非数值的列值,比如文本列。该语法只是将后缀A添加到这类函数名中,从而获得与Exce的同名函数相同的行为。
然而,这些函数只适用于包含TRUE/FLASE值的列。因为TRUE值为1,而FALSE为0,因此,无论列中的内容是什么,文本列总是被认为是0。例如,如果在文本列上使用MAXA,结果总是会得到0。此外,在执行聚合时,DAX也从不会考虑空单元格。
使这些函数可以在非数字列上使用,而不需要重新调整每个错误,但它们的结果是没有用的,因为没有自动转换为文本列的对应数字。这些函数包括:AVERAGEA、COUNTA、MINA和MAXA。
4)在DAX和Excel中使用统计函数的方式则存在差异。因为在DAX中,一个列为一个类型,它的类型决定了聚合函数的类型行为。
Excel能为每一个单元格处理成不同的数据类型,而DAX因为列式的特点,它为每个列处理成单个数据类型。
对于每一列,DAX都以表格式的形式处理数据,而Excel公式则处理不同类型的单元格值,没有定义良好的类型(对类型敏感)。
DAX的列式数据特点,反映在列表类型上:如果Power Pivot中的一个列存在一个数字数据类型,那么,所有的值都只能是数字或空单元格(比如你设置一个计算列,该计算式定义的列表属性将由引擎自动填充至整列)。
如果一个列是文本类型(即使文本可以转换为数字),那么,这些函数的值总被认为是空值“0”(除了COUNT)。在Excel中,值在单元格的基础上被认为是一个数字。由于这些原因,这些DAX函数对于文本列不是很有用也不常用。
前面提到的聚合函数对执行值的聚合很有用。但有时,我们可能对聚合值不感兴趣,而只是针对它们计数。因此,DAX提供了一组有用的函数来对行(列值)计数:
COUNT 仅在数字列上计数:
COUNTA 针对任何类型的列计数:
COUNTBLANK 返回一个列计数的空单元格的数量:
COUNTROWS 返回一个表中的行数:
DISTINCTCOUNT 返回一个列的不同值的数量(不重复值计数)。
其中,COUNTA 函数是唯一一个比较特殊的函数,它可以返回非空的列值,并针对任何类型的列计数。
另外,如果想要计算包含空值的列中的所有空值,可以使用COUNTBLANK函数。
对于任何表中的任何列:
COUNTA(table[column]) +COUNTBLANK(table[column]) =
COUNTROWS (table)。它们的结果相同。
特别注意,如果想要计算一个表的行数,可以使用COUNTROWS(行计数)函数。要注意,COUNTROWS需要一个表作为一个参数,而不是一个列。这是为数不多的使用频率较大的行计数函数。
最后一个函数,DISTINCTCOUNT,是非常有用的一个函数,因为它:计算一个列的不同值,它作为唯一值的参数或用于求列的唯一值基数。不同的是,结果可能包含BLANK空白值。
当然,它其实是:COUNTROWS(DISTINCT(table[column]))这两个函数结合的模式返回的结果,这与单独使用DISTINCTCOUNT是相同的,尽管这更容易读取,但DISTINCTCOUNT只需要一个函数调用。
到目前为止,我们提到的所有聚合函数都是在单列上工作的(除了COUNTROWS之外)。因此,它们可以聚合或计数来自单个列的值。
5)如果需要聚合表达式,而不是单个列计算,则需要一类带X结尾的聚合函数。这组函数非常有用。特别是,当你希望使用不同的关系表的列进行计算时(后面会讨论)。
例如,如果有一个Sales表,一个产品表的数据模型。那么,可以通过用这个表达式来定义一个度量:聚合计算每个销售事务的总成本:
Cost:= SUMX ( Sales, Sales[数量] *
RELATED( 产品表[进价] ) )
这个度量计算了在Sales表中,每一行的销售数量(来自Sales表)和销售产品的标准成本--进价(来自相关的产品表)。最后,它返回所有这些计算值的和。该公式行为这里从略。
所有以X后缀结尾的X系列聚合函数:它们的第一个参数为一个表(整个列值),第二个参数为一个计算表达式,并返回一个由相应的该聚合函数(SUM, MIN, MAX 或 COUNT)应用于这些计算的结果。
这些函数有:SUMX, AVERAGEX, PRODUCTX, COUNTX, COUNTAX, CONCATENATEX, MINX,MAXX等。
如果再加上一些非X后缀的但具有该行为的迭代器,比如 FILTER 和 ADDCOLUMNS等,这将代表一类列表筛选的行为。为了正确理解该类列表筛选的行为,我们需要引入列表筛选的概念。所有的这些都将在稍后详细解释。
以上,我们介绍了一类主要与计算有关的函数--计算类函数,使用它们与列表的组合定义,能完成绝大多数的计算:列聚合(求和或计数)。
其他的计算函数,比如日期函数等。这里从略。我们有专门的函数系列专题。
第13式:CALCULATE的列表参数
现在,是时候见一见CALCULATE的样子了。我们暂时只需要两个提示:在书写DAX公式时录入C开头,显示出的第一个函数就是CALCULATE,接着提示该函数的用法与解释。
我们找到官方的关于该函数的参数说明:你可以最直接的将它分为两部分,计算式+筛选列表(值列表或列表):
当然,你还会记得跟着CALCULATE后面的FILTER(是的,它叫列表筛选),关于这两个函数的讲解很多(后续会讨论)。但无论怎样,最先应该了解:CALCULATE 以及 FILTER都是用于操作列表的。如是,我们最初的了解如图:
最直接的理解,FILTER看起来就是CALCULATE里的其中一个FILTER参数。这里想再次提示:有关DAX的问题,都可以围绕列表的筛选与计算来进行。至少到目前为止,它还是一个正确的结论。接下来的问题,我们需要研究CALCULATE使用列表作为参数的“语法”,因为有了沟通的基本语言方式,还需要一定的语法。
具体地说,就是列表作为CALCULATE的参数的运用,即如何定义一个DAX计算公式,特别是一些复杂公式的构成。
这需要学习DAX的基础知识,它包括:
(1)DAX语法;
(2)DAX可以处理的不同数据类型;(见第10式)
(3)DAX基本操作符;(见第11式)
(4)DAX如何引用列或表。其中,最后一点是重点,己包含最多内容。
下面我们将讨论这些概念。以便我们能运用列表来定义计算。
我们已经知道,使用DAX计算表中列的值,这可以聚合、计算或检索数字等,但是最后:所有的计算都将涉及到列或表(单列表或多个列表)。
因此,学习DAX的第一个语法是如何引用表中的列表。这很简单,一般的格式是先引用表名(单引号,智能提示中双击选择的表即可),再是该表的某个列的列名(方括号,同上方法即可),如:
'Sales'[Sale] -- 请求引用Sales表的[Sale]列
当然,同一个表中,直接:[Sale],也是一个有效的列引用。当然,计算列或在Sales表中的度量也可以这样写。
即使该写法在语法上是正确的,我们也建议你不要使用它。这样的语法将使得代码很难阅读(有时候分不清到底是一个度量还是一个元列)。
所以,当DAX表达式中引用列时,最好总是使用表名( 'Sales'[Sale] ),也叫显式引用某列,即很清晰的告诉DAX:你请求引用Sales表的[Sale]列,而不是任何其他的列)。一旦你引用了某个列,通常都会将它装入CALCULATE容器里,作为CALCULATE的某个计算参数,以定义一个计算数据模型列表。
本部分主要也涉及DAX中的基础语法知识,以便能正确的写出需要的DAX公式,DAX详细的语法,请参阅官方帮助文件。
第14式:CALCULATE对值列表、列表的选择
CALCULATE在定义计算公式时,始终操纵的是列表,只是不同的位置需要不同的列表状态(即值列表或列表)。暂时的你只要记得,需要引用列表的参数位置,不能是值列表,而需要定义值列表的位置,则不能是列表。这是前面我们已证明的,关于值列表与列表对应的两个方面:
(1)值列表或列表的筛选; // 对数据模型内部列表的筛选;
(2)值列表或列表的计算。 // CALCULATE()公式。
所以,有关DAX的问题,我们现在暂时都可以围绕筛选与计算两方面进行。值列表与列表,不管你承认与否,本质上还是为计算提供行、列坐标,它毕竟是由单元格进化而来。不过相比单元格,它具有强大的引擎和列式数据模型的快速计算等等的特点。
我们约定:无论是否存在关系,一个字段的整列(包含全部基数行)称之为列表;涉及该列其中的一个或多个行列值的列称为值列表(可能只包含该列中的部分基数行)。
布尔值,因为其TRUE/FLASE的结果范围也关联到基数行。因此,我们将布尔值也视为值列表。某种意义上说,它们是相同的。
一行多列或多行多列指代多个列表时,我们称之为表。不作特别说明,表指代多个列,列表指单个列。
我们暂时运用CALCULATE( )的:不同位置需要不同的列表状态(即值列表或列表)来解释一些公式行为:
1、最熟悉的莫过于CALCULATE(第一参数)与CALCULATETABLE(第一参数),其第一参数分别是值列表与列表。 例如:
(1)CALCULATE(值列表或结果为值列表的计算式);
(2)CALCULATETABLE(列表或结果为列表的计算式)
当然,你知道FILTER(第一参数)也是列表。所以,某些场景下(例如第一参数为列表)时,使用FILTER与使用CALCULATETABLE没有区别,不过CALCULATETABLE的计算顺序不同于FILTER。在FILTER函数部分将会涉及。
2、前面第一部分提到的计算列与度量公式的隐式行筛选与显式列表筛选行为,如果你比较难理解,你也可以值列表与列表在这两种情形下的不同行为来理解:比如度量的第一参数,以及它的结果都需要是值列表(请不要将它理解为结果为值,它的结果是列表,而且是值列表。可能是一个行的值列表,也可能是多个行的值列表)而不是列表。计算列的结果当然只能是列表,本来就是希望新增一个列表。
用列表与值列表的特点也能理解:正因为度量是值列表结果,因此,它具有行属性行为特点(列表基数可以任意的变化--即接受筛选,从而满足计算的需要)。
比如,你可以将度量乘以一个适当的数值,以获得一个在任何筛选条件下,总会在该元度量基础上获得1.15倍的值。
而计算列是列表,它操作的是该列的整个列基数(唯一值)。
当列表筛选或被筛选发生时,实际都是整列的基数的变化(当相同基数的两列筛选时,实际上筛选并没有发生或者筛选不起作用)。
在度量列里写上:列-列 ,表示我们从数据模型里“引用”了这两个列。从原理上说,你并没有指示引擎下一步该如何做(相当于我们只是从压缩数据中取出两个列表,加载到内存存储起来,公式引擎不起作用,即使引擎执行了隐式的行行为,也是整个列表,或者说这时候的值列表等于列表--基数没有变化)。
所以,这时候,其实它是两个列表而不是值列表。因而我们说,它需要你定义一个值列表的行的行为(如使用SUM、MIN等)。这样,公式引擎将自动执行隐式的行的行为。
而在计算列里写上:列=列,表示我们从数据模型里“引用”了这两个列,由于计算列在当前的表里,引擎执行的隐式的行行为,这时候它是两个值列表,因而能够计算。
再简单点理解,涉及计算列和度量两个概念:
1)计算列的表达式分别对表的每一行进行计算,其结果与其他列值一样被存储在表中。刷新表内容将对该表的计算列更新所有列值,而不必考虑表的修改部分。因此,所有计算列占用内存空间,并在列表处理期间仅计算一次。
为计算列定义的DAX表达式,是在它所属的表的当前列表筛选中操作。任何对该列的引用都将返回当前该列的整个列值,而不能直接访问其他列值。
2)度量值是在由数据模型的一组列值所在的当前列表下计算的DAX表达式。在DAX列表查询中使用的函数会生成不同的列表筛选:用于在度量中定义计算的DAX表达式或由查询本身中定义的原数据模型列表。
此外,数据模型表的每个列值都定义了一个隐式的列表筛选子集,也就是说,对于数据模型表本身的每个列值都是不同的。例如,取决于透视表中的用户选择的当前列值。
所以,当你使用SUM([SalesAmount]) 时,你指的是在这个列表集下聚集的所有行的和;而当我们在计算列中使用[SalesAmount]时,我们指的是当前列值:SalesAmount的列值(值列表,可能是整列的值列表,或者是每个列值构成的一个或多个值列表)。
我们一次一次地说这个概念,都是一次次说不明白,等到恰当的时候,也许你突然就明白了。
这里,我们只要记得,度量与计算列的不同列表参数行为。并且总是以度量行为为主就行。你也可以回顾与结合一下第一部分的介绍。这是被引用过的图:
3、VAR变量(请参阅《变量系列》):VAR可以定义列表或值列表。但一般情况都是定义为值列表,因为列表可以直接引用数据模型里的元列表(如果定义为列表,则一般是:结果为列表的度量或计算式定义)。一旦定义,你就可以在公式中使用 RETURN 取出该定义的列表参与计算。
4、在DAX公式不同函数的不同参数中,需要不同的值列表或列表。例如上图中FILTER的第一参数是一个表,以及构建逻辑筛选条件的两个值列表筛选器。这部分后面将有太多的案例出现,并将解释筛选转换如何与 DAX 中的CALCULATE函数的筛选器参数进行交互。这一点很重要,它能避免在筛选器参数中,进行复杂计算时出现意外结果。
先观察一个DAX计算案例公式的几种变体,我们运用之前有关值列表与列表的行为,来判断公式是否正确。
业务场景:计算两年内每一年的销售额,并只考虑这两年中存在业务记录的那些月份。
根据业务要求,我们模拟DAX的可能条件是:为正确得出2009年的度量结果,一个很重要的前提是,必须考虑该年份中内有销售数据的8月和12月。
出于实验,我们不使用时间智能函数,首先我们写出第一个度量公式:
要解释这个公式行为,需要很多时间以及花很多的文字。这里,我们只是想使用值列表与列表的行为,来直接判断公式是否正确,如果错误,错在哪里? 当然该公式结论是错误的。
公式中,直观上,灰底的SELECTEDVALUE ()部分,无论是作为TREATAS的参数,还是与TREATAS组成新的筛选,再作为CALCULATE的参数,这里需要的是一个列表筛选,而不是行筛选。简单说,该位置需要一个列表而不是值列表。
这时候,通过之前的学习,我们有两种可能的变通方法来解决这个问题:
第一种:使用CALCULATE强制将整个SELECTEDVALUE ()部分,显式引用为列表(即另一个说法:将行筛选转化为等效的列表筛选)。
第二种解决方法。在该位置上使用现有的行筛选。CALCULATE的筛选器参数没有接收到任何筛选转换定义指令,因此任何行筛选仍然可用(使用的是[YearMonth]列的整列的基数值,当然,这实际就是列表)。
最后,我们结束实验,以标准的时期智能函数来解决完成这个公式。如果你已经学习过时间智能函数,应该知道,任何时间智能函数,都有一个对应的使用标准时期参数的CALCULATE公式。
下图是模拟使用上述四个公式得出的结果表,其中第一个公式,由于多重选择,SELECTEDVALUE 返回空白,从而公式结果也返回空白。
当然,你可以列举出许多这方面的案例。这里从略。
前面,我们在为DAX的两大主题之一:计算,准备着相关的需要掌握的内容。接下来,我们将针对另一主题:列表筛选。
未完待续