【DAX圣经】第三章:使用基本表函数(1)

在这一章中,您将了解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 )
图3-1 已经被单位价格超过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章中,您将看到更多这种语法的例子。

大多数语法都是由可选参数构成的。最简单的查询从已有的表中检索所有列和行:

图3-2在产品表上的查询结果

为了控制排序顺序,您可以使用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中看到先前查询的结果,其中数据按颜色、品牌和类进行排序。

图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"开始的行

图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"
图3-5 在一个排序的产品表上的查询的结果,直到从"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" )
图3-6查询只筛选了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
    )
)
图3-7查询只过滤了Fabrikam品牌的产品,其单价是单位成本的三倍以上。

这样的查询可以将这两个条件应用到产品表的所有行中。如果您有一个更快、更有选择性的两个条件中的一个,您可以首先使用一个嵌套 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,所以在嵌套调用的情况下要注意这种差异

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • DAX是微软SQL Server分析服务(SSAS)和Microsoft Power Pivot for Exce...
    daxbi阅读 15,340评论 4 31
  • 在快速介绍前一章之后,现在是时候开始讨论DAX语言了。在这一章中,您将学习语言的语法、计算列和度量值(在Excel...
    daxbi阅读 3,597评论 0 11
  • 常见的DAX函数 现在您已经了解了DAX的基本原理以及如何处理错误条件,接下来是对DAX最常用的函数和表达式的简要...
    daxbi阅读 3,545评论 2 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 顺带安利一个京东设计团队为行业做贡献的一个设计文章聚合app。 发现YouTube的视频进度也能搬到文章阅读,如下:
    DanisUX阅读 332评论 2 1