在这一章中,您将了解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 ( )
每当你有一个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 [] = }]
EVALUATE
[ORDER BY { [{ASC | DESC}]} [, ...]
[START AT {|} [, ...]] ]
最初的 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 ( , )
FILTER遍历
,对于每一行,都要计算 ,这是一个布尔表达式。当条件计算为真时, FILTER返回该行;否则,它就会跳过它
注意
从逻辑的角度来看,FILTER为
中的每一行执行了 。然而,DAX的内部优化器可能会将这些计算的数量减少到在表达式中包含的列引用的唯一值的数量水平上。对的实际计算量对应于FILTER操作的“粒度”。这样的粒度决定了FILTER的性能,并且它是DAX优化的一个重要元素。
例如,下面的查询筛选了 Fabrikam 品牌的产品,如图3-6所示。
EVALUATE
FILTER ( Product, Product[Brand] = "Fabrikam" )
您可以在另一个FILTER函数中嵌套FILTER,因为您可以使用任何表表达式作为FILTER参数。第一个被执行的FILTER是最内层的那个。一般来说,嵌套两个FILTER产生的结果与在逻辑条件的AND函数组合结果一样。换句话说,下面的查询产生了相同的结果
FILTER ( , AND ( , < condition2> ) )
FILTER ( FILTER ( , < 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,所以在嵌套调用的情况下要注意这种差异