1、DAX基础语法
DAX公式一般的格式是将表名用单引号括起来,后跟用方括号括起的列名,如下:
'Sales'[Quantity]
但是标准是在列引用中始终使用表名,在度量引用中始终避免使用表名。
在以后介绍了上下文转换之后,我们会了解到这个标准背后的基本原理
Sales[Quantity] * 2 -- 这是计算列的写法
[Sales Amount] * 2 -- 这是度量值的写法
'--'和'//'在DAX中表示单行注释
多行注释以'/*'开始,以'*/'结束。
1.1 DAX数据类型
DAX支持七种数据类型参与计算
DAX会在操作符需要时自动将字符串转换为数字,并将数字转换为字符串。
例如,如果我们使用连接字符串的&操作符,DAX将其参数转换为字符串。
下面的公式以字符串形式返回“54”:
= 5 & 4
另一方面,该公式返回一个值为9的整数结果:
= "5" + "4"
但是上述方式并不推荐,如果真想转换数据类型,推荐显式转换,上面的例子可以改为:
= VALUE ( "5" ) + VALUE ( "4" )
1.2 DAX操作符
1.3 构造表
在DAX中,我们可以直接在代码中定义匿名表。
如果表只有一列,那么语法上只需要一个值列表—每一行一个值—由花括号分隔。
我们可以用括号分隔多行,如果表只有一列,括号是可选的。
例如,以下两个定义是等价的:
{ "Red", "Blue", "White" }
{ ( "Red" ), ( "Blue" ), ( "White" ) }
如果表有多列,括号是必需的。
每一列的所有行都应该有相同的数据类型;否则,DAX将自动将列转换为一种数据类型,这种数据类型可以容纳同一列的不同行中提供的所有数据类型。
{
( "A", 10, 1.5, DATE ( 2017, 1, 1 ), CURRENCY ( 199.99 ), TRUE ),
( "B", 20, 2.5, DATE ( 2017, 1, 2 ), CURRENCY ( 249.99 ), FALSE ),
( "C", 30, 3.5, DATE ( 2017, 1, 3 ), CURRENCY ( 299.99 ), FALSE )
}
表构造函数通常与IN操作符一起使用。例如,:
'Product'[Color] IN { "Red", "Blue", "White" }
( 'Date'[Year], 'Date'[MonthNumber] ) IN { ( 2017, 12 ), ( 2018, 1 ) }
以下示例展示了使用IN操作符比较一组列(元组)
这种方式不能与比较运算符一起使用,所以以下写法是无效的
( 'Date'[Year], 'Date'[MonthNumber] ) = ( 2007, 12 )
但是,我们可以用以下方式来重写它
( 'Date'[Year], 'Date'[MonthNumber] ) IN { ( 2007, 12 ) }
1.4 条件语句
在DAX中,我们可以使用IF函数编写条件表达式。例如:
IF (
Sales[Quantity] > 1, "MULTI", "SINGLE"
)
IF函数有三个参数,但只有前两个是必需的。第三个选项是可选的,默认为空白。所以以下两种写法都是一样的
IF (
Sales[Quantity] > 1, Sales[Quantity]
)
IF (
Sales[Quantity] > 1, Sales[Quantity], BLANK ()
)
2、计算列和度量值的区别
2.1 计算列
计算列是添加到模型中的新列,但它不是从数据源加载的,而是通过使用DAX公式创建的。
计算列和表格中的其他列一样,还可以使用计算列来定义关系。
定义计算列的DAX表达式在计算列所属表的当前行上下文中操作。对列的任何引用都将返回该列的当前行值,我们不能直接访问其他行的值。所有计算的列都占用内存,并且都是在表处理期间计算的。
在创建复杂的计算列时,存放在内存非常有用。计算复杂计算列所需的时间始终是处理时间而不是查询时间,这样可以获得更好的用户体验。不过,要注意计算列使用了宝贵的RAM。
例如,如果我们有一个复杂的计算列公式,我们可能会试图将计算步骤拆分到不同的中间列中。尽管这种方式在项目开发期间很有用,但在生产中却是一个坏习惯,因为每个中间计算都存储在RAM中,浪费了宝贵的空间。
如果模型是基于DirectQuery的,那么行为就会有很大的不同。在DirectQuery模式下,当表格引擎查询数据源时,计算的列是动态计算的。这可能导致数据源执行大量查询,从而产生速度较慢的模型。
例如,我们可以利用下单时间和交货日期两列数据,两列相减得出出交付订单所需的天数,以下是计算列的公式:
Sales[DaysToDeliver] = INT ( Sales[Delivery Date] - Sales[Order Date] )
2.2 度量值
度量值是用聚合函数计算出来的值,它的结果只有一个,而计算列得出的结果是一列的值
例如,在Sales表中定义一些计算列来计算毛利率金额:
Sales[SalesAmount] = Sales[Quantity] * Sales[Net Price]
Sales[TotalCost] = Sales[Quantity] * Sales[Unit Cost]
Sales[GrossMargin] = Sales[SalesAmount] – Sales[TotalCost]
如果想显示毛利占销售额的百分比,可以使用以下公式创建一个计算列:
Sales[GrossMarginPct] = Sales[GrossMargin] / Sales[SalesAmount]
这个公式在行级别上计算正确的值,如下图所示,但是在总体总数级别上,结果显然是错误的。
可以看出来,551.46%是所有毛利占比的总和,这种计算方式显然是错误的,我们应该以所有毛利的总和除以所有销售额的总和
如果需要对聚合值进行操作,而不是逐行操作,则必须创建度量。我们使用:=来定义度量值,而不是等号=,以便在代码中更容易区分度量和计算列。
定义GrossMarginPct作为度量值后,结果是正确的,如下图所示。
GrossMarginPct := SUM ( Sales[GrossMargin] ) / SUM (Sales[SalesAmount] )
度量值和计算列都使用DAX表达式,不同之处在于计算的环境。
计算列的值是在数据刷新期间计算的,它使用当前行作为上下文。结果不依赖于用户在报表上的操作。
度量值对当前上下文定义的数据聚合进行操作。例如,在透视表中,根据透视表的行过滤源表,并使用这些过滤器聚合和计算数据。换句话说,度量值总是在计算环境下对数据的聚合进行操作(关于计算上下文会在之后进一步说明)。
因此,当在度量值中使用SUM(Sales[SalesAmount])时,我们指的是筛选聚合的所有行的总和。但是,当我们在计算列中使用Sales[SalesAmount]时,我们指的是当前行中SalesAmount列的值。
需要在表中定义度量值,但是并不真正属于表格。实际上,我们可以将度量值从一个表移动到另一个表,而不会丢失其功能。
因为度量值在查询时计算,它不会消耗内存和磁盘空间。通常,当我们可以用两种方式表达计算时,度量值是首选。应该将计算列的使用限制在少数严格需要它们的情况下。具有Excel经验的用户通常更喜欢计算列而不是度量,因为计算列非常类似于在Excel中计算的方式。然而,在DAX中计算值的最好方法是通过度量值。
此系列是对The_Definitive_Guide_to_DAX第2版的学习笔记
欢迎关注微信公众号:纸上躬行君