分析将从一个简单的表格开始:

图 1 – 用于分析的简单表格(作者供图)
矩阵可视化图表中的每一行都展示了每个月的在线销售总额。
到目前为止,一切看起来都非常直观。
直观的解释是:我们看到的是按月份筛选后的在线销售总额。
但这并非事情的全貌。
接下来,我们审视一下数据模型:

图 2 – 包含日期表和事实表的数据模型部分(作者供图)
仔细观察,可以发现日期表(Date table)与事实表(fact table)之间是基于日期列(date columns)建立关系的。
与月份列(month column)之间并没有直接的关系。
如果沿着这个思路,上述的解释就不完全准确了。
更完整的解释应该是:每一行显示的是通过日期表筛选后的在线销售总额。日期表的行按月份进行分组。因此,每一行展示的是该月份所有日期的总销售额。
当理解了这一细节时,我们就离全面掌握 DAX 的工作原理,特别是时间智能函数的运作机制更近了一步。
现在,让分析更进一步。
YTD 与基础查询
现在,添加一个年度至今(YTD)度量值,观察其表现:

图 3 – YTD 度量值及其结果显示在与之前相同的表格中(作者供图)
这个度量值本身并不复杂,其结果也易于理解。
接下来,深入探究 DATESYTD() 函数的具体功能。
dax.guide 网站对该函数的解释是:“返回一个在筛选上下文中,从年初到最后一个可见日期的日期集。”
这究竟意味着什么?
为了探究这个问题,我们首先编写一个 DAX 查询,以获取 2024 年 6 月的所有日期列表,正如上述可视化图表中所展示的那样:
DEFINE
VAR YearFilter = TREATAS({ 2024 }, 'Date'[Year])
VAR MonthFilter = TREATAS({ 6 }, 'Date'[Month])
EVALUATE
SUMMARIZECOLUMNS('Date'[Date]
,YearFilter
,MonthFilter
)
查询结果是 6 月份的 30 天日期列表:

图 4 – 获取 2024 年 6 月所有日期的基础查询(作者供图)
这就是应用于上述矩阵图表中 2024 年 6 月那一行数据的筛选器。
那么,将 DATESYTD() 函数应用于这个结果会发生什么?
以下是相应的查询:
DEFINE
VAR YearFilter = TREATAS({ 2024 }, 'Date'[Year])
VAR MonthFilter = TREATAS({ 6 }, 'Date'[Month])
VAR BasisDates = CALCULATETABLE(
SUMMARIZECOLUMNS('Date'[Date]
,YearFilter
,MonthFilter
)
)
VAR YTDDates = DATESYTD(TREATAS(BasisDates, 'Date'[Date])
)
EVALUATE
YTDDates
以及其结果:

图 5 – 从 1 月 1 日到 2024 年 6 月最后一天,包含所有日期的列表(作者供图)
这是一个包含 182 行数据的列表,其中包括从年初到 2024 年 6 月最后一天的所有日期。
这正是 YTD(年度至今)的定义。
当我们观察以下度量值时:
Online Sales (YTD) =
VAR YTDDates = DATESYTD('Date'[Date])
RETURN
CALCULATE([Sum Online Sales]
,YTDDates
)
就会发现变量 YTDDates “仅仅”是一个日期列表,作为筛选器应用于 CALCULATE() 函数。
这正是所有时间智能函数的核心关键。
回溯一年 — 示例
如果将另一个函数应用于此结果,例如 SAMEPERIODLASTYEAR(),又会发生什么?
为了回答这个问题,使用以下 DAX 查询:
DEFINE
VAR YearFilter = TREATAS({ 2024 }, 'Date'[Year])
VAR MonthFilter = TREATAS({ 6 }, 'Date'[Month])
VAR BasisDates = CALCULATETABLE(
SUMMARIZECOLUMNS('Date'[Date]
,YearFilter
,MonthFilter
)
)
VAR YTDDates = DATESYTD(TREATAS(BasisDates, 'Date'[Date])
)
VAR YTDDatesPY = SAMEPERIODLASTYEAR(YTDDates)
EVALUATE
YTDDatesPY
为了提高代码的可读性,文章故意将 SAMEPERIODLASTYEAR() 函数的调用与 DATESYTD() 函数分开。实际上,也可以将 DATESYTD() 嵌套在 SAMEPERIODLASTYEAR() 中。
这一次,结果是 181 行数据,因为 2024 年是闰年。
并且日期都向后推移了一年:

图 6 – 应用 SAMEPERIODLASTYEAR() 后的查询结果(作者供图)
所以,再次强调,当将时间智能函数应用于度量值时,该函数(例如 DATESYTD())会返回一个日期列表。
请注意:当应用此筛选器时,日期表上任何现有筛选器都将被清除。
自定义逻辑
现在,将这些知识应用于自定义时间智能逻辑的构建。
首先,稍微改变一下年份和月份的筛选器:
DEFINE
VAR YearMonthFilter = TREATAS({ 202406 }, 'Date'[MonthKey])
EVALUATE
SUMMARIZECOLUMNS('Date'[Date]
, YearMonthFilter
)
这个查询的结果与文章开头展示的结果相同。
这一次,筛选器是通过对 [MonthKey] 列设置一个数值来实现的。
如何才能回溯到上一年呢?
从数学角度思考,只需减去 100 即可实现:
202406 – 100 = 202306
让我们尝试一下:
![图 7 – 从 [MonthKey] 列中减去 100 后的查询结果](/2025/10/02/5ffceb67e032801cbf00ffebe8e285fb.png)
图 7 – 从 [MonthKey] 列中减去 100 后的查询结果(作者供图)
这种方法也适用于其他数字格式。
例如,如果使用像 2425 这样的会计年度格式(表示 24/25 财年),
可以通过减去 101 来获取上一个会计年度:2425 – 101 = 2324。
另一个自定义时间智能逻辑的例子是计算滚动平均值,其中每天的平均值是基于过去 10 天的数据计算的:

图 8 – 计算前 10 天移动平均值的度量值代码和结果(作者供图)
由于变量 DateRange 的内容同样是一个日期列表,因此可以对其应用 SAMEPERIODLASTYEAR() 函数,从而获得所需的结果:
DEFINE
VAR YearFilter = TREATAS({ 2024 }, 'Date'[Year])
VAR MonthFilter = TREATAS({ 6 }, 'Date'[Month])
// 1. 获取当前筛选上下文的起始和结束日期
VAR MaxDate = CALCULATE(MAX( 'Date'[Date] )
,YearFilter
,MonthFilter
)
VAR MinDate =
CALCULATE(
DATEADD( 'Date'[Date], - 10, DAY )
,'Date'[Date] = MaxDate
)
// 2. 生成移动平均所需的日期范围(四个月)
VAR DateRange =
CALCULATETABLE(
DATESBETWEEN( 'Date'[Date]
,MinDate
,MaxDate
)
)
EVALUATE
SAMEPERIODLASTYEAR( DateRange )
以下是其结果:

图 9 – 上一年移动平均值的结果(作者供图)
这个逻辑返回 11 行数据,因为它包含了当月的最后一天。根据所需结果,可能需要调整计算日期列表(应用于度量值的筛选器)中起始日期和结束日期的方式。
这无疑是对前述内容的重复展示,但它旨在说明相同的思路可以应用于各种不同的场景。
一旦理解了这一点,处理时间智能函数以及其他接受值表作为输入的函数时,将变得更加轻松自如,得心应手。
结论
虽然文中使用了 DAX Studio 来执行这些查询,但您同样可以在 Power BI Desktop 内置的 DAX 查询工具中使用它们。
文章特意通过这些查询来强调,在 DAX 中,我们始终在与表打交道,即便有时可能没有明确意识到这一点。
但这却是理解 DAX 工作原理过程中一个至关重要的细节。
尽管随着 Power BI 中新的基于日历的时间智能功能出现,此处展示的一些 DAX 代码可能显得有些过时,但本文阐述的原理依然有效。DATESYTD() 或 SAMEPERIODLASTYEAR() 等函数仍然存在,并以相同的方式工作。目前而言,这方面不会有任何改变,因为本文描述的概念依然适用。
参考文献
与之前的文章一样,本文使用了 Contoso 示例数据集。您可以从 Microsoft 此处免费下载 ContosoRetailDW 数据集。
