【DAX圣经】第二章:介绍DAX(3)
常见的DAX函数
现在您已经了解了DAX的基本原理以及如何处理错误条件,接下来是对DAX最常用的函数和表达式的简要介绍。
在本章的其余部分,您将看到一些最常用的DAX函数,您可能会在自己的数据模型中使用这些函数。
聚合函数
几乎每个数据模型都需要对聚合数据进行操作。DAX提供了一组函数,这些函数聚合了表格中列的值,并返回单个值。我们称这组函数集合函数。例如,下面的度量值计算销售表Sales amount列中所有数字的总和
Sales := SUM ( Sales[SalesAmount] )
这个表达式(SUM)如果在一个计算列中使用的话,就会聚集该表的所有行,但如果使用在度量值中,则只适用于在透视表中使用切片器、行、列和过滤条件的行。
聚合函数(SUM, AVERAGE, MIN, MAX, STDEV, and VAR)只在数字和日期类型上运行。
注意
MIN和MAX还有另一个功能:如果使用两个参数,它们将返回两个参数的最小值或最大值。因此,MIN(1、2)将返回1和MAX(1、2)返回2。这个功能在2015年引入,当您需要计算复杂表达式的最小值或最大值时,它非常有用,因为它避免在IF语句中多次写入相同的表达式。
与Excel类似,DAX为这些函数提供了一种可选的语法,以便在可以同时包含数字和非数字值的列上进行计算,比如文本列。该语法只是将后缀A添加到函数名中,只是为了获得与Excel相同的名称和行为。然而,这些函数只对包含TRUE/FALSE的列有用,因为TRUE被评为1和FALSE为0。文本列总是被认为是0。因此,无论列的内容是什么,如果您使用,例如,在文本列上的MAXA,您总是会得到0作为结果。此外,DAX在执行聚合时从不考虑空的单元格。
即使这些函数可以在非数值列上使用,而且不会返回错误,它们的结果也没有用,因为没有自动转换为文本列的数字。这些函数被命名为AVERAGEA,COUNTA,MINA,MAXA。
注意
尽管统计函数的名称相同,但在DAX和Excel中使用的方式不同,因为在DAX中,列有一个类型,它的类型决定了聚合函数的行为。Excel为每个单元处理不同的数据类型,而DAX则为每一列处理单个数据类型。DAX以表格形式处理数据,为每一列提供定义统一的类型,而Excel公式适用于异构单元格值,没有定义统一的类型。如果Power Pivot中的一个列有一个数字数据类型,那么所有的值都只能是数字或空单元格。
如果一个列是文本类型,那么这些函数的值总是为0(除了COUNTA),即使文本可以转换成数字,而在Excel中,值被认为是逐个单元上的数字。由于这些原因,这些DAX函数对文本列不是很有用
您以前学过的函数对于聚合函数值是很有用的。有时,您对聚合值不感兴趣,但只对计数感兴趣。因此,DAX提供了一组函数,这些函数对于计数行或值很有用。
-
COUNT只在数字列运行
-
COUNTA可以在任意类型列上运行
-
COUNTBLANK返回一列中空单元格数量
-
COUNTROWS返回一个表中行的数量
-
DISTINCTCOUNT返回一列中不重复列的数量
COUNTA是以A为后缀函数组中唯一有趣的函数,因为它返回列中非空的单元格数量,而且是在任何类型的列上工作。如果你想计算一个包含空值的列中的所有值,你可以使用COUNTBLANK函数。最后,如果您想计算一个表的行数,您可以使用COUNTROWS函数。请注意,COUNTROWS需要一张表作为参数,而不是列
注意
对应任意一张表中的列,counta(table[column])+countblank(table[column])总是等于countrows(table)
最后一个函数 DISTINCTCOUNT,是一个非常有用的函数,正如名称所暗示的那样,统计参数列的不同值数量,DISTINCTCOUNT会统计BLANK(空值),并作为其中的一个可能值。
注意
在2012年的DAX版本中,DISTINCTCOUNT成为了一个函数。早期版本的DAX没有包括DISTINCTCOUNT,为了计算一个列的不同值的数量,您必须使用COUNTROWS(DISTINCT(table[column]))。这两种模式返回的结果是相同的,但是DISTINCTCOUNT只需要一个函数调用,更容易阅读。
到目前为止,您所学习的所有聚合函数都是在列上工作的(除了COUNTROWS,它在表上工作)。因此,它们只能聚合来自单个列的值。有聚合函数可以聚合一个表达式,而不是单个列。这组函数非常有用,特别是当您想要使用不同的相关表的列进行计算时。例如,如果一个销售表包含所有的销售事务,并且一个相关的产品表包含关于产品的所有信息,包括它的成本,您可以通过用这个表达式定义一个度量值来计算销售事务的总内部成本
Cost := SUMX ( Sales, Sales[Quantity] * RELATED ( Product[StandardCost] ) )
该方法计算销售表中每一行的数量(来自销售表)和销售产品的标准成本(来自相关产品表)。最后,它返回所有这些计算值的总和。
所有的聚合函数用X后缀结尾的行为如下:他们在每个表的行(第一个参数)计算一个表达式(第二个参数),并返回一个结果,最后通过与这些聚合函数 (SUM, MIN, MAX or COUNT)一致的行为,将这些结果进行聚合计算。
在第4章中,您将进一步了解这种行为,为了正确理解他们的行为,我们将需要引入计算上下文的概念。可用的x后缀函数是 SUMX、AVERAGEX、PRODUCTX、COUNTX、COUNTAX, CONCATENATEX, MINX, and MAXX
还有一些迭代器没有X后缀,比如FILTER和ADDCOLUMNS。所有这些都将在后面详细解释。
逻辑函数
有时您想要在表达式中构建逻辑条件——例如,根据列的值或截获错误条件来实现不同的计算。在这些情况下,您可以使用DAX中的一个逻辑函数。您已经在上一节中了解到,“处理DAX表达式中的错误”,IF和IFERROR是该组函数中的两个最重要的函数。
逻辑函数非常简单,按照它们的名字所暗示的,它们是 AND, FALSE, IF, IFERROR, NOT, TRUE, and OR。例如,如果你想要只有当价格列包含正确的数字值时,计算数量乘以价格,,你可以使用以下模式:
Amount = IFERROR ( Sales[Quantity] * Sales[Price], BLANK ( ) )
如果您没有使用IFERROR,并且Price列包含一个无效的数字,那么计算列的结果将是一个错误,因为如果一行产生计算错误,错误就会传播到整个列。然而,IFERROR的使用会拦截错误并使用空白值替换它。
这个类别中的另一个有趣的函数是SWITCH,当你有一个数量不多的不同值的列时,你想要根据这个列值得到不同的行为,这是很有用的。例如,产品表中的列大小包含L、M、S、XL,您可能想要
将这个值解码成一个更有意义的列,您可以通过使用嵌套的IF调用来获得结果:
SizeDesc =
IF (
DProduct[Size] = "S",
"Small",
IF (
Product[Size] = "M",
"Medium",
IF (
Product[Size] = "L",
"Large",
IF ( Product[Size] = "XL", "Extra Large", "Other" )
)
)
)
使用SWITCH函数得到一种更方便的方式来表达相同的公式:
SizeDesc =
SWITCH (
Product[Size],
"S", "Small",
"M", "Medium",
"L", "Large",
"XL", "Extra Large",
"Other"
)
后一种表达式中的代码可读性更强,即使速度不是更快,因为在内部,DAX将SWITCH语句转换为一组嵌套的IF函数。
小贴士
这里有一种有趣的方法,可以使用SWITCH函数来检查同一个表达式中的多个条件。因为SWITCH被转换成一组嵌套的IF,第一个条件获取匹配的胜出,您可以使用此模式测试多个条件:= SWITCH ( TRUE (), Product[Size] = "XL" && Product[Color] = "Red", "Red and XL", Product[Size] = "XL" && Product[Color] = "Blue", "Blue and XL", Product[Size] = "L" && Product[Color] = "Green", "Green and L" )
在现实运用中,使用TRUE作为第一个参数,它的意思是说:“返回条件计算为真的第一个结果。”
信息函数
当您需要分析表达式的类型时,您可以使用其中一个信息函数。所有这些函数都返回一个 TRUE/FALSE,并且可以在任何逻辑表达式中使用。它们是 ISBLANK, ISERROR, ISLOGICAL, ISNONTEXT, ISNUMBER, and ISTEXT
需要重点注意的是,当一个列(而不是表达式)作为参数传递时,函数ISNUMBER、ISTEXT和ISNONTEXT总是返回TRUE或FALSE,这取决于列的数据类型和每个单元的空值情况。
您可能想知道是否可以使用ISNUMBER与一个文本列来检查是否有可能转换成数字。不幸的是,您不能使用这种方法;如果您想要测试文本值是否可转换为数字,您必须尝试转换,并在转换失败时处理错误。例如,为了测试列价格(即类型文本)是否包含有效数字,您必须编写
IsPriceCorrect = NOT ( ISERROR ( Sales[Price] + 0 ) )
DAX试图在价格中加入数值零,以迫使从文本值转换为数字;如果它成功了,那么它就会返回TRUE(因为ISERROR会返回FALSE),否则它会返回FALSE(因为ISERROR返回TRUE)。某些情况下转换将会失败,例如,对于某些行,价格有一个“n/a”字符串值。
但是,如果您尝试使用ISNUMBER,就像在下面的表达式中一样,您总是会得到错误的结果:
IsPriceCorrect = ISNUMBER ( Sales[Price] )
在这种情况下,ISNUMBER总是返回FALSE,因为根据元数据,Price列不是一个数字,而是一个字符串,不管每一行的内容是什么。
数学函数
在DAX中可用的数学函数集非常类似于Excel中的同一套,具有相同的语法和行为。常用的数学函数是 ABS, EXP, FACT, LN, LOG, LOG10, MOD, PI, POWER, QUOTIENT, SIGN, and SQRT。随机函数是 RAND和RANDBETWEEN。EVEN 和ODD 让你测试数字。GCD和LCM对于计算两个数字的最大公分母和最小公倍数是很有用的。QUOTIENT返回除法结果的整数部分。
最后,还有几个舍入函数值得举例;事实上,您可能使用几种方法来获得相同的结果。考虑这些计算列,以及它们在图2-3中的结果:
FLOOR = FLOOR ( Tests[Value], 0.01 )
TRUNC = TRUNC ( Tests[Value], 2 )
ROUNDDOWN = ROUNDDOWN ( Tests[Value], 2 )
MROUND = MROUND ( Tests[Value], 0.01 )
ROUND = ROUND ( Tests[Value], 2 )
CEILING = CEILING ( Tests[Value], 0.01 )
ISO.CEILING = ISO.CEILING ( Tests[Value], 0.01 )
ROUNDUP = ROUNDUP ( Tests[Value], 2 )
INT = INT ( Tests[Value] )
FIXED = FIXED ( Tests[Value], 2, TRUE )
图2-3不同四舍五入函数的摘要
正如你所看到的,除了指定的数字的数量方式外,FLOOR, TRUNC和ROUNDDOWN 都是非常相似的。相反,CEILING和 ROUNDUP在它们的结果中非常相似。你可以看到在MROUND和 ROUND之间的舍入的方法有一些不同
三角函数
DAX提供了一组丰富的三角函数,这些函数对于某些计算是有用的。我们不详细讨论这些函数的细节,如果需要的话,它们的用法很简单。它们是COS、COSH、COT、COTH、SIN、SINH、TAN和TANH,还有加arc前缀的版本( arcsin,arccos,等等)
文本函数
在DAX中,几乎所有的文本功能都与Excel中可用的功能类似,只有少数例外:它们是CONCATENATE, EXACT, FIND, FIXED, FORMAT, LEFT, LEN, LOWER, MID, REPLACE, REPT, RIGHT, SEARCH, SUBSTITUTE, TRIM, UPPER, and VALUE。这些函数对于操纵文本和从包含多个值的字符串中提取数据非常有用。例如,在图2-4中,您可以看到一个从字符串中提取的姓和名的例子,其中包含了用逗号分隔的值,中间的标题是我们想要删除的
图2-4,您可以看到一个使用文本函数提取姓氏和名字的示例
我们开始计算两个逗号的位置然后我们用这些数字来提取文本的右边部分。SimpleConversion列实现了一个公式,它可能会返回错误值如果字符串中有少于两个逗号(如果没有逗号会返回一个错误),而FirstLastName列实现一个更复杂的表达式,在当列中没有逗号时不会返回错误。
Comma1 = IFERROR ( FIND ( ",", People[Name] ), BLANK ( ) )
Comma2 = IFERROR ( FIND ( ",", People[Name], People[Comma1] + 1 ), BLANK ( ) )
SimpleConversion =
MID ( People[Name], People[Comma2] + 1, LEN ( People[Name] ) )
& " "
& LEFT ( People[Name], People[Comma1] - 1 )
FirstLastName =
TRIM (
MID (
People[Name],
IF ( ISNUMBER ( People[Comma2] ), People[Comma2], People[Comma1] ) + 1,
LEN ( People[Name] )
)
)
& IF (
ISNUMBER ( People[Comma1] ),
" "
& LEFT ( People[Name], People[Comma1] - 1 ),
""
)
正如您所看到的,FirstLastName列是由长DAX表达式定义的,但是您必须使用它来避免可能出现的错误,因为如果单个值产生错误,则会传播到整个列。
转换函数
您在此之前已经了解到,DAX执行数据类型的自动转换,调整它们以满足运算的需要。即使它是自动发生的,一组函数仍然可以执行显式的类型转换
CURRENCY可以将表达式转变为货币类型,而INT则将表达式转换为整数。 DATE 和TIME将日期和时间作为参数,并返回一个正确的 DATETIME。VALUE把字符串转换成数字格式,而格式则作为第一个参数和字符串格式获得一个数字值,并可以将数值转换成字符串。格式通常与DateTime一起使用。例如,下面的表达式返回“2015年1月12日”。
= FORMAT ( DATE ( 2015, 01, 12 ), "yyyy mmm dd" )
相反的操作,即将字符串转换成DateTime值,是通过使用DATEVALUE函数来执行的。
日期和时间函数
几乎在每一种类型的数据分析中,处理时间和日期都是工作的重要部分。DAX有大量的函数可以操作日期和时间。其中一些与Excel中的类似功能相对应,并能对DateTime数据类型进行简单的转换。
日期和时间函数分别是DATE, DATEVALUE, DAY, EDATE, EOMONTH, HOUR, MINUTE, MONTH, NOW, SECOND, TIME, TIMEVALUE, TODAY, WEEKDAY, WEEKNUM, YEAR, and YEARFRAC
为了能对日期做更复杂的操作,比如比较每年的聚合值,或者计算年初至今的度量值,还有另一组叫做时间智能函数的函数,将在第7章“时间智能计算”中描述。
正如在本章中提到的,DateTime数据类型在内部使用一个浮点数,其中整数部分对应于1899年12月30日之后的天数,而小数部分表示一天时间的分数。小时、分钟和秒被转换成十进制小数。因此,将整数值添加到DateTime值中,就会相应地增加相应天数的值。但是,您可能会发现使用转换函数从日期中提取日、月和年更方便。在图2-5中,您可以看到如何从包含日期列表的表格中提取这些信息:
Day = DAY ( Calendar[Date] )
Month = FORMAT ( Calendar[Date], "mmmm" )
Year = YEAR ( Calendar[Date] )
图2-5,您可以看到一个使用日期和时间函数提取日期信息的示例
关系函数
两个有用的函数可以让你在DAX公式中操作关系,这两个函数是RELATED的和RELATEDTABLE
您已经知道,一个计算列可以引用定义它的表的列值。因此,在销售中定义的计算列可以引用同一表格中的任意一列。但是,如果您必须引用另一个表中的列,您该怎么做呢?一般来说,除非在两个表之间的模型中定义了关系,否则不能在其他表中使用列。如果这两个表共享一段关系,那么 RELATED函数使您能够访问相关表中的列
例如,您可能想要在销售表中计算一个计算列,以检查已售出的产品是否在“移动电话”类别中。要计算这样一个列,您必须使用一个检查产品类别的值的条件,而这不是在销售表中。尽管如此,一系列的关系从销售开始,通过产品和产品子类传递到产品类别,如图2-6所示。
图2-6销售与产品类别之间的关系从原来的表到相关的表需要多少步这其实并不重要,因为DAX将遵循完整的关系链并返回相关的列值。因此, AdjustedCost 列的公式可以是
Sales[AdjustedCost] =
IF (
RELATED ( 'Product Category'[Category] ) = "Cell Phone",
Sales[UnitCost] * 0.95,
Sales[UnitCost]
)
在一对多关系中,RELATED可以从多方访问一方,因为在这种情况下,如果有的话,相关表中只有一行存在。如果没有这样的行,RELATED只是返回BLANK
如果你在这段关系的一方,你想要访问多方,那么RELATED就没有帮助了,因为另一边的许多行可能都适用于一行。在这种情况下,您可以使用RELATEDTABLE。RELATEDTABLE返回一个表格,其中包含与当前行相关的所有行。例如,如果你想知道每个类别中有多少产品,你可以用这个公式在产品类别中创建一个列
= COUNTROWS ( RELATEDTABLE ( Product ) )
这个计算列将显示,对于每个产品类别,相关产品的数量,如图2-7所示。
图2-7通过使用RELATEDTABLE计算产品的数量与RELATED的情况一样,RELATEDTABLE可以遵循一系列的关系,并且总是从一方开始,沿着多方的方向前进。