第2章 DAX简介
2.1 理解DAX计算
2.2了解计算列和度量值
2.3 变量入门
2.4 DAX表达式中的错误处理
2.5 格式化DAX代码
2.6 聚合函数和迭代函数
2.7 使用常见的DAX函数
了解计算列和度量值
现在您已经了解DAX语法的基础知识,您需要学习DAX中最重要的概念之一:计算列和度量值之间的区别。尽管乍看之下计算列和度量值可能看起来很相似,因为您可以使用其中任何一种来进行某些计算,但实际上它们是不同的。了解两者的差异是释放DAX功能的关键。
计算列
根据所使用的工具,可以不同的方式创建计算列,其实质是一样的:计算列是添加到模型中的新列,但是它不是通过数据源加载的,而是通过DAX公式创建的。
计算列与表格中的任何其他列一样,可以在矩阵或其他报表的行、列引用和筛选。如果需要,还可以使用计算列来定义关系。为计算列定义的DAX表达式在计算列所属表的当前行的上下文中运行。对所属表列的引用都会返回当前行该列的值。无法直接访问其他行的值。
如果使用Tabular的默认导入模式,而不使用DirectQuery,要记住有关计算列的一个重要概念是在数据库处理期间计算这些列,然后将其存储在模型中。如果您习惯于使用SQL计算列(非持久性),则该概念看起来颇为奇怪,这些列是在查询时计算的,并且不使用内存。但是,在Tabular(表格模型)中,所有计算的列都占用内存空间,并在表处理期间进行计算。
每当我们创建复杂的计算列时,此行为很有用,能带来更好的用户体验,因为计算复杂的计算列耗时始终是处理时间而不是查询时间。但是,请注意,计算列会使用宝贵的内存。例如,如果计算列具有复杂的公式,则可能会通过中间辅助列来分步计算,尽管此技术在项目开发中很凑效,但在运行中却是个坏习惯,因为每个中间计算都存储在内存中,浪费了宝贵的内存空间。
如果模型是基于DirectQuery的,则行为会大不相同。在DirectQuery模式下,当表格模型引擎查询数据源时,将即时计算计算列。这可能会导致数据源执行繁重的查询,从而导致模型变慢。
计算订单的交货时间
假设有一个Sales表包含有Order Date和Delivery Date列。使用这两列,可以计算出交付订单所涉及的天数。由于日期存储为1899年12月30日之后的天数,因此简单的减法即可计算两个日期之间的天数差:
Sales[DaysToDeliver] = Sales[Delivery Date] - Sales[Order Date]
但是,因为用于减法中的两列都是是日期,所以结果也是日期。要产生数值结果,请按以下方式将结果转换为整数:
Sales[DaysToDeliver] = INT ( Sales[Delivery Date] - Sales[Order Date] )
结果如图2-2所示
度量值
计算列很有用,但是可以用另一种方式在DAX模型中定义计算。当不想为每一行计算值,而是希望聚合表中多行的值时,您会发现被称为度量值的计算很有用。
例如,您可以在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]
如图2-3所示:该公式在行级别计算正确的值,但是在总计级别,结果显然是错误的。
总计级别显示的值是计算列中逐行计算的各个百分比的总和。当计算一个百分比的合计值时,不能依赖于计算出的列,相反,需要根据各个列的总和来计算百分比。必须将总计值计算为毛利的总和除以销售额的总和。在这种情况下,我们需要计算聚合比率;你不能使用计算列的总计。换句话说,要计算总和的比率,而不是比率的总和。
简单地将GrossMarginPct 列的总计更改为平均值作为结果,同样是错误的,因为这样做会对百分比进行错误评估,而没有考虑金额之间的差异。该平均的值的计算结果见图2- 4,可以很容易地检查(330.31 / 732.23)不等于显示值45.96%, 而应该是45.11%。
GrossMarginPct的正确计算应用度量值来实现:
GrossMarginPct:= SUM (Sales[GrossMargin])/SUM
(Sales[SalesAmount])
如前所述,使用计算列无法获得正确的结果时,如果您需要对聚合值进行操作,而不是逐行进行操作,则必须创建度量值。您可能已经注意到我们使用了:=定义度量值而不是等号(=)。这是我们在整本书中使用的标准,可以更加容易区分代码中的度量值和计算列。
在将GrossMarginPct定义为度量之后,结果是正确的,如图2-5所示。
度量值和计算列均使用DAX表达式;区别在于评估的背景。度量值是在视觉元素的上下文或DAX查询上下文中评估的;计算列的计算是在其所属表的行级别进行的。视觉元素的上下文(在本书的后面,您将学到这是一个筛选上下文)取决于报表中用户的选择或DAX查询的格式。因此,在度量值中使用SUM(Sales [SalesAmount])时,是指在视觉元素的上下文聚合的所有行的总和。但是,当我们在计算列中使用Sales [SalesAmount]时,是指当前行中SalesAmount列的值。
需要在表中定义度量值。这是DAX语言的要求之一。但是,该度量值并不真正属于该表。实际上,我们可以将度量从一个表移动到另一表,而不会失去其功能。
计算列和度量值之间的差异
尽管它们看起来很相似,但计算列和度量值之间还是有很大的差异。计算列的值是在数据刷新期间计算的,并且它使用当前行作为上下文。结果不取决于报告上的用户活动。度量值对当前上下文定义的数据聚合进行操作。例如,在矩阵或数据透视表中,根据单元格的坐标,对源表进行筛选并使用这些筛选聚合和计算数据。换句话说,度量值始终在评估上下文背景下对数据聚合进行操作。评估上下文将在第4 章 “ 了解评估上下文 ”中进一步解释。
既然已经了解了计算列和度量值之间的差异,那么讨论何时何时用计算列何时用度量值变得很有意义。有时两者都是选项之一,大多数情况下,计算需求决定了选择。
作为开发人员,无论何时要执行以下操作,都必须定义一个计算列:
- 将计算结果放在切片器中,或在矩阵、数据透视表的行或列中查看结果(与"值"区域相对),或者在DAX查询中将计算列用作筛选条件。
- 定义一个严格绑定到当前行的表达式。例如,Price * Quantity,对这两列的平均值或总和不起作用。
- 分类文字或数字。例如,量值的范围,客户年龄范围,例如0-18、18-25等。这些分类通常用于筛选或切片器。
但是,要显示计算值反馈用户的选择或在报告中作为聚合值呈现时,必须定义度量值:
- 计算报表中的利润百分比
- 按年份和地区筛选时计算某产品与所有产品的比率
我们既可以用计算列又可以用度量值来定义许多计算,尽管需要分别使用不同的DAX表达式。例如,可以将GrossMargin定义为计算列:
Sales[GrossMargin] = Sales[SalesAmount] - Sales[TotalProductCost]
但是,也可以将其定义为度量:
GrossMargin := SUM ( Sales[SalesAmount] ) - SUM ( Sales[TotalProductCost] )
这种情况下建议使用度量值,因为在查询时对其进行评估不会消耗内存和磁盘空间。通常,只要您可以同时使用两种方法来计算,则首选度量值。您应该将计算列的使用严格限制在需要它们的少数情况。具有Excel经验的用户通常更喜欢使用计算列而不是度量值,因为计算列与Excel中计算的方式非常相似。
但是,在DAX中计算值的最佳方法是通过度量值。
在计算列中使用度量值
显而易见,一个度量值可以引用一个或多个计算列。尽管不那么明显,但反之亦然,计算列也可以引用度量值。这样,计算列将强制在当前行定义的上下文计算度量值。此操作将度量值的结果转换并固定到列中,该列将不受用户操作的影响。显然,这样的操作只有在某些特殊情况下才有意义,因为度量值的计算通常很大程度上取决于用户在视觉对象中所做的选择。而且,作为开发人员在计算列中使用度量时,依赖于称为上下文转换的功能,这是DAX中的一种高级计算技术。强烈建议在计算列中使用度量之前,阅读并理解第4章,其中详细介绍了评估上下文和上下文转换。