了解 DAX 函数中 LASTDATE 和 MAX 之间的区别

本文翻译自国际Power BI大师Alberto Ferrari的文章——《Understanding the difference between LASTDATE and MAX in DAX》,本文解释了为什么在许多情况下,应使用MAX而不是LASTDATE来使用DAX搜索某个时间段内的最后日期。

很多DAX新手使用LASTDATE搜索某个时间段内的最后一天,使用NEXTDAY检索给定日期之后的日期。尽管这些函数可以实现和描述相符的功能,但它们并不是用于简单的表达式的。相反,它们是用于时间智能计算的表函数。错误地使用它们会导致代码效率低下。此外,以非设定的方式使用这些函数也暴露出开发人员并未掌握DAX的某些细节。

在本文中,我们将详细讨论这个话题,以便读者理解这些时间智能功能的作用。我们还讲讨论为什么它们会如此容易和简单的日期数学运算混淆。我们将通过例子来进行详细阐述。因此,我们先从枯燥的的理论开始讲起,而不是从一个计算开始,即使这个计算工作正常,但它本质上是错误的。

假设您希望计算包含在给定选择中的天数,并构建如下所示的报表。

计算“一段时间范围内的天数”非常简单:天数是时间段中第一个日期和最后一个日期之间的差。DAX提供了两种功能:FIRSTDATE和LASTDATE,这两种功能看起来很适合:

Days in period :=INT ( LASTDATE ( 'Date'[Date] ) - FIRSTDATE ( 'Date'[Date] ) )

这个度量值可以正常工作并得到正确的结果。所以皆大欢喜对吗?并非如此。

我们并不满意此结果,因为我们使用了LASTDATE来检索一个时间段内最后一个可见日期的值。LASTDATE完全执行此作业,但它最终返回一个包含最后日期的表而不是日期。再说一遍:它不返回日期,它返回一个包含日期的表。

这样做的原因是LASTDATE是时间智能函数。其主要目的是用作CALCULATE中的过滤器参数。CALCULATE过滤器参数是表。因此,要使函数在以下度量中使用,它需要返回一个表:

SalesOfLastDay =CALCULATE ( [Sales Amount], LASTDATE ( 'Date'[Date] ))

您可以使用DAX Studio再次检查LASTDATE的结果。LASTDATE返回一个表。这就是为什么您可以在EVALUATE语句中使用它的原因,该语句需要一个表作为结果。


如您所看到的,结果是包含一个列(日期)的表,该表的值为最后日期。

在DAX中,可以使用仅包含一行和一列的表(即您从LASTDATE得到的结果)代替内部的值。实际上,单行一列的表仅包含一个值。这就是DAX允许您将表自动转换为值的原因。这也是您可以在我们的度量中减去两个表的原因:

Days in period :=INT ( LASTDATE ( 'Date'[Date] ) - FIRSTDATE ( 'Date'[Date] ) )

实际上,LASTDATE和FIRSTDATE都返回表。因为我们使用的是减法运算符,所以DAX会将两个表转换为标量值,然后计算表内包含的值之间的差。

虽然这种行为是透明的,但它是有代价的。更好的表达上文计算的办法是使用标量函数,如用MIN代替FIRSTDATE,用MAX代替LASTDATE。MIN和MAX不返回表:它们返回第一个和最后一个日期的值。因此,更好地制订该措施的办法如下:

Days in period MIN MAX :=INT ( MAX ( 'Date'[Date] ) - MIN ( 'Date'[Date] ) )

同样,您可以使用DAX Studio再次检查MIN和MAX的结果。如果您尝试使用MAX而不是LASTDATE作为EVALUATE语句的结果,则会出现错误。

为了使用EVALUATE获得结果,您需要构建一个包含最大日期的表。例如,您可以使用表构造函数来实现。


如前所述,DAX自动将具有一行和一列的表转换为一个值。但是这种行为是有代价的。此外,LASTDATE和FIRSTDATE都在查找第一个和最后一个日期之前执行上下文转换。此行为不会影响我们的简单示例,但是仅由于此方面,在更复杂的情况下性能可能会很差。

如何检查两种形式的公式之间的行为差异?通过使用DAX Studio,您可以分析此查询的服务器时间:

---- This version uses FIRSTDATE and LASTDATE

--

EVALUATE

SUMMARIZECOLUMNS (

'Date'[Year Month],

"Days in period",

[Days in period]

)

尽管速度非常快,但您可以从服务器计时中看到,该引擎必须对日期表进行两次实体化:一次为Date[Date]列,一次为Date[Date]和Date[Calendar Year Month]两列,生成两个2556行数据缓存。公式引擎(FE)随后扫描这些数据缓存以计算所需的结果。


当使用MIN和MAX计算“周期内的天数”时,这个优化版本的计算并没有让查询变得更快。不过,它在具体实现的过程中展现了优越性,因为整个计算被下推到存储引擎(SE),它生成一个87行的数据缓存:与查询结果的行数相同。因此,在最小最大周期内的天数测量产生一个最优的实现,并由SE执行完整的计算。在较大的模型中,或者在更复杂的场景中,这种具体化上的小差异可能会产生巨大的影响。


请注意,大多数时间智能函数(例如FIRSTDATE,LASTDATE,NEXTDAY,PREVIOUSDAY…)都表现相同的行为:它们返回可以自动转换为标量值的表。如果需要标量值,请使用标量函数。仅在需要表作为结果时才使用表函数。

使用LASTDATE代替MAX并不一定会降低计算的性能。它正在加重它的负担,这在现代计算机中并不总是显而易见的。也就是说,如果您仍然混淆了表函数和标量函数,这表明DAX语言中还有一些您没有掌握的细节。多加注意这些细节,你将成为一个更好的DAX开发人员!


如果您想深入学习微软Power BI,欢迎登录网易云课堂试听学习我们的“从Excel到Power BI数据分析可视化”系列课程。或者关注我们的公众号(PowerPivot工坊)后猛戳”在线学习”。


长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~

Power Pivot工坊