在这一章中,您将了解DAX中标量函数和表函数之间的区别。表函数对于DAX的内部计算非常重要,当您编写DAX查询而不是用于度量值或计算列的DAX表达式时,它是非常有用的。
这里的目标是介绍表函数的概念,但不提供您将在这里首次看到的所有函数的详细说明。对表函数的更深层次的分析包含在第9章“高级表函数”中。在这里,我们将解释表函数在DAX中的作用,以及如何在常见场景中使用它们,包括在标量DAX表达式中。
介绍表函数
DAX是一种函数式语言,你可以写出在一个表达式,当它完成计算后会返回一个结果。到目前为止,您已经看到DAX表达式通常返回单个值,比如字符串或数字。我们称这种表达式为标量表达式。当您定义一个度量值或一个计算列时,您总是编写一个标量表达式,如下面的例子:
= 4 + 3
= "DAX is a beautiful language"
= SUM ( Sales[Quantity] )
然而,您可以编写一个DAX表达式来生成一个表。您不能将表表达式直接分配给度量值或计算列,但是表表达式是DAX的重要部分。例如,有些DAX函数接收表表达式作为参数,并且需要一个表表达式来编写DAX查询。
表表达式的最简单的例子是引用DAX表达式中的表名,如下列表达式返回销售表的全部内容(所有列和所有行):
= Sales
但是,如果您试图将先前的表达式赋值给一个度量值或一个计算列,那么您将得到一个错误,因为一个度量值需要一个标量值。为了获得一个标量值,您需要操纵表表达式。这是通过使用接受表表达式作为参数的函数来实现的。例如,您可以通过使用COUNTROWS来计算表中包含多少行:
= COUNTROWS ( Sales )
COUNTROWS函数有如下定义:
COUNTROWS ( <table> )
每当你有一个DAX函数接受表格表达式作为参数时,你可以在那个参数中写上表格的名称,或者你可以写一个返回表格的函数
我们根据它们的返回类型对DAX函数进行分类。,那些返回标量值的我们称之为“标量函数”,返回一个表的则称之为“表函数”。例如,COUNTROWS是一个标量函数,因为它返回一个数字,并接受一张表作为参数。
许多表函数通常操作一个表,改变原始表的行和/或列。例如,您可以使用以下表达式来计算销售表中单位价格大于100的的行数。
= COUNTROWS ( FILTER ( Sales, Sales[Unit Price] > 100 ) )
在前面的表达式中, FILTER 返回一张表,其中只包含有单价大于100的销售行。在本章后面,您将了解更多关于 FILTER 的知识。
通常,您在代码中使用表表达式来遍历表格的行,并聚合一些值,以返回一个标量值作为结果。您不能将表表达式直接分配给度量值或计算列。但是,您可以在一个计算表中使用表表达式(如果该特性将来可用),或者在DAX查询中使用表表达式的内容。
例如,您可以通过执行如下表表达式来获得包含所有销售额的表格,其中包含了如下的表格,这将导致您在图3-1中看到的内容。
= FILTER ( Sales, Sales[Unit Price] > 100 )
DAX也提供了EVALUATE语句,你可以用它来计算表表达式
EVALUATE
FILTER ( Sales, Sales[Unit Price] > 100 )
您可以在任何执行DAX查询(Microsoft Excel、DAX Studio、SQL Server Management Studio、Reporting Services等)的客户端工具中执行DAX查询。在下面的部分中,您将看到关于EVALUATE语法的更详细的解释。
EVALUATE语法
[DEFINE { MEASURE <tableName>[<name>] = <expression> }]
EVALUATE <table>
[ORDER BY {<expression> [{ASC | DESC}]} [, ...]
[START AT {<value>|<parameter>} [, ...]] ]
最初的 DEFINE MEASURE部分可以用来定义查询本地的度量(也就是说,它们存在于查询的生命周期中)。当您调试公式时,它变得非常有用,因为您可以定义一个本地度量,测试它,然后在它按照预期运行时将它放入模型中。在第9章中,您将看到更多这种语法的例子。
大多数语法都是由可选参数构成的。最简单的查询从已有的表中检索所有列和行:
为了控制排序顺序,您可以使用ORDER BY子句:
EVALUATE Product
ORDER BY
Product[Color],
Product[Brand] ASC,
Product[Class] DESC
注意
请注意,模型中定义的列属性在DAX查询中没有影响。即使您可能通过按列属性查询单个列来查看排序数据,您也不必依赖于这种行为,就像您不能依赖于SQL查询中的群集索引一样。生成动态DAX查询的客户端应该在模型的元数据中按列属性读取排序,然后根据条件生成相应的次序。在DAX和SQL中,您必须始终使用显式的ORDER BY条件来获得排序的数据。简单来讲就是需要用order by条件来控制排序,而非依赖系统默认的排序顺序。
ASC和DESC关键字是可选的;如果它们不存在,默认情况下是使用ASC。您可以在图3-3中看到先前查询的结果,其中数据按颜色、品牌和类进行排序。
条件的开始也是可选的,并且只能与ORDER BY子句一起使用。你可以通过语句指定每一列的起始值。条件的开始对于在无状态应用程序中分页是很有用的,这些应用程序只从查询中获取有限数量的行,然后在用户请求下一页数据时发送另一个查询。例如,查看以下查询
EVALUATE Product
ORDER BY
Product[Color],
Product[Brand] ASC,
Product[Class] DESC
START AT
"Yellow",
"Tailspin Toys"
查询返回如图3-4所示的表格,其中只包含从"Yellow"、 "Tailspin Toys"开始的行
请注意,“starting from”的概念取决于ORDER BY子句中指定的顺序方向。如果你为 Product[Brand] 指定DESC,就像下面的例子一样,广泛的世界进口商不包括在结果中,而其他品牌,如Southridge视频和Northwind交易员,则跟在 Tailspin Toys后面。您可以在图3-5中看到以下查询的结果。
EVALUATE
Product
ORDER BY
Product[Color],
Product[Brand] DESC,
Product[Class] DESC
START AT
"Yellow",
"Tailspin Toys"
为了过滤行并更改DAX查询返回的列,您必须在EVALUATE关键字后面使用特定的函数来处理表表达式。本章介绍了一些表表达式,而第9章描述了其他的表表达式
使用表表达式
正如您在本章的开头所看到的,您经常使用表表达式作为其他DAX函数的参数。一个典型的用法是在函数中迭代一个表,计算每一行的DAX表达式。例如,所有以“X”结尾的聚合函数,如SUMX:
[Sales Amount] :=
SUMX ( Sales, Sales[Quantity] * Sales[Unit Price])
您可以用一个表函数替换简单的Sales表引用。例如,您可以只使用 FILTER 函数来计算数量大于1的销售额
[Sales Amount Multiple Items] :=
SUMX (
FILTER ( Sales, Sales[Quantity] > 1 ),
Sales[Quantity] * Sales[Unit Price]
)
在一个计算列中,您还可以使用RELATEDTABLE函数来检索一对多关系多方的表的所有行。例如,下面产品表中的计算列计算相应产品的销售数量:
Product[Product Sales Amount] =
SUMX ( RELATEDTABLE ( Sales ), Sales[Quantity] * Sales[Unit Price] )
当你有嵌套的表函数调用时,DAX首先计算最内层的函数,然后再对其他函数进行计算。不要把这个规则和函数调用的参数的计算顺序混淆了
注意
正如您稍后将看到的,嵌套调用的执行顺序可能是混乱的来源,因为CALCULATETABLE的计算顺序与 FILTER不同。在下一节中,您将学习 FILTER行为,您将在第5章“理解CALCULATE和 CALCULATETABLE”中找到 CALCULATETABLE的描述。
理解 FILTER
FILTER函数有一个简单的角色:它获得一个表,并返回一个表,该表与原始表中的列相同,但只包含满足筛选条件的行
FILTER语法如下:
FILTER ( <table>, <condition> )
FILTER遍历<table>,对于每一行,都要计算 <condition>,这是一个布尔表达式。当条件计算为真时, FILTER返回该行;否则,它就会跳过它
注意
从逻辑的角度来看,FILTER为<table>中的每一行执行了 <condition> 。然而,DAX的内部优化器可能会将这些计算的数量减少到在<condition>表达式中包含的列引用的唯一值的数量水平上。对<condition>的实际计算量对应于FILTER操作的“粒度”。这样的粒度决定了FILTER的性能,并且它是DAX优化的一个重要元素。
例如,下面的查询筛选了 Fabrikam 品牌的产品,如图3-6所示。
EVALUATE
FILTER ( Product, Product[Brand] = "Fabrikam" )
您可以在另一个FILTER函数中嵌套FILTER,因为您可以使用任何表表达式作为FILTER参数。第一个被执行的FILTER是最内层的那个。一般来说,嵌套两个FILTER产生的结果与在逻辑条件的AND函数组合结果一样。换句话说,下面的查询产生了相同的结果
FILTER ( <table>, AND ( <condition1>, < condition2> ) )
FILTER ( FILTER ( <table>, < condition1> ), < condition2> )
但是,如果表中有许多行,并且这两个断言有不同的复杂性,那么您可能会观察到不同的性能。例如,考虑下面的查询,该查询返回的是单价超过单位成本三倍的Fabrikam产品,如图3-7所示。
EVALUATE
FILTER (
Product,
AND (
Product[Brand] = "Fabrikam",
Product[Unit Price]
> Product[Unit Cost] * 3
)
)
这样的查询可以将这两个条件应用到产品表的所有行中。如果您有一个更快、更有选择性的两个条件中的一个,您可以首先使用一个嵌套 FILTER 函数来应用它。例如,下面的查询在最内部的Unit Price和Unit Cost中应用了 FILTER函数,然后只对那些满足价格条件的产品进行筛选。
EVALUATE
FILTER (
FILTER ( Product, Product[Unit Price] > Product[Unit Cost] * 3 ),
Product[Brand] = "Fabrikam"
)
如果你对条件进行逆变换,你也会把它们的执行顺序颠倒过来。下面的查询只用于Fabrikam brand产品的价格条件:
EVALUATE
FILTER (
FILTER ( Product, Product[Brand] = "Fabrikam" ),
Product[Unit Price]
> Product[Unit Cost] * 3
)
当您优化DAX表达式时,这些知识将非常有用。您可以选择执行顺序来首先应用最具选择性的过滤器。然而,如果没有对计算上下文的清晰理解,就不要开始优化DAX。在第16章“优化DAX”中,您将找到关于查询优化的更完整的讨论。这些例子的目标是让您了解表函数的嵌套调用的执行顺序。
注意
通常,嵌套函数的执行顺序是从最内层到最外层的函数。您将会看到,由于计算参数的特定顺序,CALCULATE和CALCULATETABLE可能是这种行为的一个例外。因为类似的计算情况下,您可能会使用 FILTER和 CALCULATETABLE,所以在嵌套调用的情况下要注意这种差异