在 Power BI 中实现基于时间的计算

Power BI 提供了多种工具来执行基于时间的计算,这些计算依赖于自动生成的日期表或用户添加的日期表。

我们建议使用 基于日历的时间智能(预览), 因为它提供最佳性能和最高灵活性,以满足任何日历。

下表比较了提供的三种工具:

Tool 所需的设置工作 管理简便 灵活性 注释
自动日期/时间 几乎为零 由于创建多个隐藏日期表,模型大小增加
经典时间智能 容易 需要创建日期表,假定使用公历或移位公历,在某些特定情境中存在性能问题
基于日历的时间智能 建议创建日期表、最高灵活性、最佳性能,但设置成本增加

注释

我们建议不要使用替代的时间智能技术,特别是那些需要向日期表中添加额外列来计算偏移量的技术,除非在特定情况下。 虽然这些方法可能会由于其简单的 DAX 公式而吸引初学者,但它们往往不必要的膨胀语义模型。 随着数据集的增长,这种膨胀可能导致数据刷新速度变慢和报表性能下降。

自动日期/时间

自动日期/时间功能会自动为数据模型中的每个日期字段创建隐藏日期表。 有关此自动行为的详细信息,请参阅 在 Power BI Desktop 中应用自动日期/时间

注释

虽然自动日期/时间是简单模型的便捷选项,但不建议用于更复杂的方案和更大的模型。 对于这些模型,最好创建专用表,提高灵活性。

添加日期表

对于大多数模型,建议添加日期表(在某些情况下或更多)。 许多数据分析师更喜欢创建自己的日期表,这很好。

有多种方法可以创建此类表,包括:

哪种选项最适合你取决于各种因素,并且超出了本教程的范围。

使用基于时间的计算

假设未使用 自动日期/时间,Power BI 中有两种使用 时间智能函数 来执行基于时间的计算的替代方法:

  • 经典时间智能。 最简单的选项,非常适合公历或移位公历,但对于结构不同或基于周的计算的日历来说,灵活性有限。
  • 基于日历的时间智能(预览版)。 较新的选项,但需要稍微多一点工作来完成设置。 但是,它还为你提供了更好的性能、使用非公历的灵活性以及执行基于周的计算的能力。

注释

需要在某些特定情况下将 您的表设置为日期表

经典时间智能

此选项要求你在模型中有一个日期表,并 相应地对其进行设置。 之后,您可以使用 时间智能函数 并引用日期表。 例如,如果模型中有一个名为 Date 的日期表,该表设置为日期表,其中包含 Date 列,则可以使用:

SAMEPERIODLASTYEAR ( 'Date'[Date] )

虽然这是一种快速而简单的方法,但与 基于日历的方法相比,有许多缺点:

  • 它要求设置日期表
  • 它仅适用于至少有一个专用日期表的模型
  • 使用的日期列在第一个和最后一个日期之间不应有任何日期缺失。 如果第一个日期和最后一个日期之间有任何缺失日期,则会引发错误。
  • 它不太灵活,因为它专门针对公历或调整过的公历进行了优化,例如从 7 月 1 日开始但仍遵循公历的财政年度。
  • 它不提供基于周的计算
  • 在特定方案中,基于时间的计算性能不佳。

注释

建议使用 基于日历的增强 方法。

基于日历的时间智能(预览版)

日历是添加到表中的元数据定义,用于指示该表中的哪些列表示时间的属性。 可以在模型中的任何表上定义一个或多个日历。 在模型中定义日历后,可以在时间智能函数中引用它。 例如,下面介绍如何使用定义的 会计日历计算销售额的总年数:

TOTALYTD ( [Sales], 'Fiscal Calendar' )

基于日历的时间智能的优点

基于日历的时间智能的主要优点包括:

兼容任何日历

日历使你可以完全灵活地决定如何在年份、季度、月和周中划分时间。 例如,可以定义遵循以下模式的日历:

  • Gregorian
  • 变更的公历
  • 零售(445、454、544 模式)
  • 13 个月
  • 月球

可能性是无休止的,因为 Power BI 没有关于日历的结构的内置假设。 基于日历的时间智能对基础日期没有假设。 所有计算都使用原始数据,且保持数据原样不变。

稀疏日期

经典时间智能 要求提供的日期列必须是完整的 - 如果第一个日期和最后一个日期之间有任何缺失的日期,则会引发错误。 基于日历的时间智能函数没有此类要求。 相反,它们直接按原样处理日期。 虽然我们仍然建议拥有完整的专用日历表,但不再需要拥有该表。 例如,如果所有零售商店在周末关闭,因为周末没有任何销售,您可以跳过周末这几天。 假如你的周末是星期六和星期天,你现在可以用不包含周末条目的表格中的PREVIOUSDAY日历,从星期一直接跳到星期五。

基于周的计算

基于日历的时间智能直接提供以每周粒度运行的 DAX 函数。 例如,可以使用 TOTALWTD 直接计算本周累积总数:

TOTALWTD ( Expr, CalendarName )

性能改进

某些方案在将基于日历的时间智能函数与其经典对应函数进行比较时,可能会表现出更好的性能。 例如,按周分组并执行年初至今的计算的TOTALYTD ( ..., CalendarName )可视化通常比使用其经典对应项TOTALYTD ( ..., TableName[DateColumnName] )时一般执行速度会更快。 有关发生这种情况的原因的见解,请参阅 上下文清除 部分。

启用增强的 DAX 时间智能预览版

若要开始,首先需要启用 增强型 DAX 时间智能 预览功能。

  1. 在 Power BI Desktop 中,转到“文件 > 选项和设置 > 选项 > 预览功能”。
  2. 选择 增强型 DAX 时间智能 预览版。
  3. 选择确定
  4. 重启 Power BI Desktop

管理日历

若要管理日历,请右键单击包含日历的表或要定义日历的表,然后在选择表格工具功能区中选择“ 日历”选项 或选择“ 日历”选项

显示用于打开表上的日历选项的入口点的屏幕截图。

或者,可以使用外部工具或 TMDL 视图 来定义日历。 有关详细信息,请参阅 TMDL 脚本

日历也显示在定义它们的表格所在的模型资源管理器中。

显示语义模型的模型资源管理器的屏幕截图。“日期表”节点已展开,并突出显示日历。

日历选项屏幕

日历选项屏幕显示所选表上定义的日历。 在这里,你可以:

  • 通过选择“新建日历”创建新日历
  • 通过选择“编辑”编辑现有日历
  • 选择删除以删除现有日历。
  • 通过选择“标记为日期表”将表设置为日期表

显示表上的日历选项的屏幕截图。

分配列的类别

定义日历涉及为其指定名称并将列分配到类别。 每个类别表示一个时间单位和特定的 列类别 可用。 至少需要为类别分配一个主列才能保存日历。 每个类别都应有一个 主列,并且可以有零个或多个关联的列。 每当与某个类别关联的任何列都位于上下文中时,Power BI 就会知道它们存在的时间单位。 此外,对于某些函数(如 TOTALMTD Power BI),使用映射到引用日历中相关类别的主列来执行请求的计算。 若要将列分配到类别,请从 “添加类别”菜单中选择该类别 ,然后选择主要列和可选关联列。

显示日历创建和编辑屏幕的屏幕截图。

可用列类别

下表显示了可用的类别。 该表还提供公历的示例值和基数。

类别分为两个组:

  • 完成。 分配给“Complete”类别的列中的数据足以唯一标识时间周期。
  • 部分。 分配给分部类别的列中的数据不足以唯一标识时间段。
类别 Description 类型 公历中的基数示例 公历中的列值示例
年份 年份 完成 Y = 年数 2024, 2025
季度 包含年份的季度 完成 4*Y 2024年第一季度, 2025年第二季度
年度季度 年度四分之一 部分 4 第 1 季度、YQ1、Q1、第 2 季度
月份 包含年份的月份 完成 12*Y ≤ value ≤ 13*Y 2023 年 1 月,2024 年 2 月
年月 一年中的月份 部分 12 1 月,年份月份11,YM11,M11,11
季度月份 季度的月份 部分 3 1、QM2
Week 包括年份在内的一周 完成 52 ≤ value ≤ 53 2023年 第50周、W50-2023、2023-W50
一年中的第几周 一年中的一周 部分 52 第50周,W50,50
季度中的某周 季度的一周 部分 13 季度周 10、QW10、10
月周 每月的某一周 部分 5 月周2,MW2,2
日期 日期 完成 365*Y ≤ value ≤ 366*Y 2025 年 12 月 31 日
一年中的一天 一年中的一天 部分 365 ≤ value ≤366 365、D1
季度中的某一天 季度中的一天 部分 92 四分之一天 10, QD2, 50
每月的某一天 月份的日期 部分 31 30日,MD10和30
星期几 星期几 部分 7 第 5 周、WD5、5

除了这些类别,还可以将表中的任意数量的列与 时间相关的 类别相关联。 目前无法在日历选项中执行此作,但只能使用外部工具或 TMDL 来完成。

注释

在执行除和DATEADD之外的所有函数中的计算时,将删除被分配到SAMEPERIODLASTYEAR类别的任何列的上下文。 在定义了日历的表中,但未在该日历中标记的列,其上下文将被保留。

注释

我们建议您仅关联日历中要用于时间智能计算的列。

主列与关联列

每个类别都需要主要列。 每当在上下文中引用的日历上分配给同一类别的列或任何关联列,或需要用该类别执行计算时,Power BI 将使用主要列。 此外,主列用于排序。 如果主列中的值不允许其按预期进行排序,则可以 将主列配置为按另一列排序 或使用另一列并使原始列成为关联列。 例如,格式为mm-yyyy(即01-202402-2024,等等)的包含月数和年份的文本数据的列在多个年份之间无法正确排序,但使用格式的yyyy-mm列将:

显示两个表的屏幕截图。每个表都有一列。第一个表包含一列,该列包含以 mm-yyyy 格式显示的文本月数和年份信息,第二列包含 yyyy-mm 格式的相同信息。包含 mm-yyyyy 格式数据的列未正确排序。

可以为类别分配零个或多个关联的列。

Validation

请务必验证和测试日历,以便确定它满足你的需求。 Power BI 中提供的验证包括 实时验证脱机验证

注释

尽管存在脱机验证错误,但可以保存日历,但建议先解决这些错误。 必须修复实时验证失败才能保存。

实时验证

对日历执行的实时验证包括:

  • 唯一日历名称。 每个日历必须具有语义模型中的唯一名称。
  • 每个日历的单个关联。 列不能属于同一日历中的多个类别。
  • 周期唯一性。 分配的类别应唯一标识时间段。
  • 一致的分类。 这可确保各日历中的列与同一类别相关联。

时间段唯一性

应始终有一个路径来唯一标识已分配类别的时间段。

每当添加 部分类别时,Power BI 会验证是否在同一日历中标记了完整或部分类别的匹配组合。 如果情况并非如此,将显示警告。

显示日历创建和编辑屏幕的屏幕截图,其中包含实时验证错误。

例如,为基于周的计算设置日历时,请确保将至少一列作为主要列分配给以下类别集中之一:

  • Week
  • 年周,年
  • 季度、季度周
  • 季度周、年度季度、年份
  • 月份中的周, 月份
  • 月的周、年份的月、年
  • 月中的星期,季度中的月份,季度
  • 月的周、季度的月、年的季度、年

一致的分类

列必须具有跨日历的一致类别。 不能将同一列分配给不同的类别,例如 年份年份季度或单独日历中 与时间相关的 列。

脱机验证

脱机验证在访问表数据时可能很耗时。 因此,与实时验证不同,它们不会自动运行。 若要运行验证,请选择“ 验证数据”:

显示日历创建和编辑屏幕的屏幕截图。突出显示了“验证数据”按钮。

脱机验证检查以下规则,如果日历中有任何规则失效,则返回警告:

  • 与类别关联的列没有空值。
  • 高层次和低层次类别具有一对多基数关系。 例如,与 Year 类别关联的列应具有与 Month 类别关联的列的一对多基数。
  • 与同一级别的类别关联的列具有一对一基数比率。 例如,与“月份”类别关联的列应与“年度”和“年的月份”类别关联的列的组合具有一对一的基数关系。
  • 分配给同一类别的主列和关联的列具有一对一基数比率。 例如,当分配给 Month 类别时,主列 Month 和关联的列 EnglishMonthName 应具有一对一基数。

使用日历

定义日历后,可以在 时间智能函数中引用它。 例如,以下度量值根据 ISO-454 日历计算截至本月的总数量值:

Total Quantity MTD ISO-454 = TOTALMTD ( [Total Quantity], 'ISO-454' )

如果未定义日历并返回错误:

显示使用带有日历参数的 TOTALMTD 函数的度量值时,指向一个不存在的日历的屏幕截图。

但是,即使定义了日历,度量值也可能返回错误。 如果使用的函数预期日历中存在某个类别,并且日历没有该类别,则会发生此情况。 例如, TOTALWTD 预期日历中存在特定类别。 如果没有,则返回错误:

屏幕截图显示度量使用 TOTALWTD 函数以及有效的日历引用,但未定义所需类别。

时间智能函数和所需类别

许多 时间智能函数 要求在函数调用中引用的日历中包含足够的类别,以便 Power BI 可以标识唯一特定的时间单位。 换句话说,Power BI 需要能够从计算的层级逐步上升,直至到达某个特定年份。 例如,当对季度进行计算时,可以使用 TOTALQTD ,要么分配“季度”类别, 要么根据 期间唯一性 验证规定,在日历中同时分配“年份季度”和“年份”。

注释

对于某些函数,其名称指示计算作的级别(例如 TOTALYTD),而对于其他函数,则依赖于参数和上下文(例如, DATEADD)。

上下文清除

时间智能函数通过从某个时间点出发,对其执行某些操作,以生成不同的时间点。 当然,初始时间点可能与此结果冲突,从而导致筛选器上下文在默认情况下相交,从而产生部分或空结果。 例如,考虑以下情形。

日历定义

我们有一个简单的公历,用于标记三个类别,定义为:

类别 主列
年份 年份
年月 MonthOfYear
季度 季度

度量值定义

定义了两个基本度量值:一个用于计算总销售额,另一个用于计算上一季度的总销售额:

[TotalSales] = CALCULATE ( SUM( FactInternetSales[SalesAmount] ) )
[LastQuarterSales] = CALCULATE ( [TotalSales], DATEADD( GregorianCalendar, -1, QUARTER ) )

示例:上下文清除的工作原理

表格视图以月为粒度,使用 YearMonthOfYear 列进行浏览。

年份 MonthOfYear 总销售额 上季度销售
2011 年 1 10
2011 年 2 20
2011 年 3 30
2011 4 40 10
2011 年 5 50 20

在该表中,粗体行显示的是 2011 年 4 月按月级别概览。 因此,此行中的所有度量值都将在 [Year] == 2011 和 [MonthOfYear] == 4 的筛选器上下文下进行评估。
按预期计算,此处的 TotalSales 计算为 2011 年 4 月的总销售额。

LastQuarterSales 同样计算 TotalSales,但给定基于日历的 DATEADD 函数提供的额外筛选器。 对于此行,DATEADD 的初始时间点为 2011 年 4 月,并将生成一个时间点,该时间点恰好是一个季度前:2011 年 1 月。 因此,人们可能期望在以下两个筛选器上下文下计算此 TotalSales

  • 由当前行的浏览列提供: { [Year] == 2011, [MonthOfYear] == 4 } (等效于 2011 年 4 月)
  • DATEADD 筛选器提供:{ [Year] == 2011, [MonthOfYear] == 1 } (相当于 2011 年 1 月)

显然,这两个筛选器上下文会产生冲突——因为当前月份同时包含 2011 年 1 月和 2011 年 4 月,所以我们无法评估总销售额。 这种交集不会产生任何结果。 但是,这不是发生的情况。 相反,根据日历定义,基于日历的时间智能函数会根据函数执行的时间作来确定哪些类别的列可能会导致冲突。 在本例中,DATEADD季度 级别进行转换。 该函数识别 年度年中月份 的类别可能因 季度 类别的列更改而发生变化。 因此,该函数会清除那些被标记到这些类别中的所有列(包括主要列和关联列)的筛选器上下文。

换句话说,我们可以说,YearMonth of Year 类别依赖于 季度 类别。 相反,我们可以说季度类别是年度类别和年度月份类别的依赖。

上下文清除的工作原理

所有日历类别的格子结构图。

提供了此关系图,以便更好地可视化不同时间类别之间的依赖关系。 在这个 lattice 中,每个类别都表示所有标记到该类别的列(包括主列和关联列)。 类别通过箭头连接到其依赖项。 例如,“Month”依赖于“Year”、“年度季度”、“季度的月份”、“季度”和“年度的月份”。

在日历中标记的列或其关联的“排序依据”列上设置上下文时,将会清除之前的筛选器上下文:

  1. X 的所有类别 依赖项 。这可以视为 X 以上的所有类别。
  2. X 及其依赖项的所有类别相关依赖项(即上面的第 1 项)。 这可以视为 X 以下的所有类别和上面 1 中的所有类别。

注释

上下文清除会在日历中标记的列或关联的排序列上发生,无论上下文是否通过时间智能函数或其他方式设置。

大多数时间智能函数(除了 DATEADDSAMEPERIODLASTYEAR)将清除所有与时间相关的列和关联的 排序列的上下文。

跨日历行为

如果同一表上定义了多个日历,这些过程将为表上定义的 每个 日历完成。 这包括关于时间相关列上下文清理的说明。 换句话说,假设表定义了三个日历:Calendar1、Calendar2 和 Calendar3。 如果在 Calendar1 中的类别“X”上设置了筛选器上下文,则上述过程在所有三个日历上执行。

示例:“季度”上的筛选条件设置

如果在类别“Quarter”上设置了筛选器上下文,该过程将如下所示。

  1. 首先,将考虑“季度”类别的所有依赖项。

    筛选器上下文清除行为示例,从类别“季度”开始:依赖项。

  2. 接下来,将考虑“季度”及其依赖项的所有依赖项。

    筛选器上下文清除行为示例:从类别“季度”开始,依赖项。

  3. 最后,最终结果如下。 所有红色类别都将删除其以前的筛选器上下文,并在 Quarter 上设置新上下文。

    按季度类别开始的筛选器上下文清除行为示例:结果

日历的 TMDL 脚本

createOrReplace

	table Date
		lineageTag: xyz

		column Date
			dataType: dateTime
			formatString: Long Date
			lineageTag: abc
			summarizeBy: none
			sourceColumn: Date

        column Year
			dataType: string
			lineageTag: abc
			summarizeBy: none
			sourceColumn: Year

			annotation SummarizationSetBy = Automatic
        
        column Month
			dataType: string
			lineageTag: def
			summarizeBy: none
			sourceColumn: Month

			annotation SummarizationSetBy = Automatic

        column MonthName
			dataType: string
			lineageTag: ghi
			summarizeBy: none
			sourceColumn: MonthName
			sortByColumn: SortByMonth

			changedProperty = SortByColumn

			annotation SummarizationSetBy = Automatic

        column DutchMonthName
			dataType: string
			lineageTag: jkl
			summarizeBy: none
			sourceColumn: DutchMonthName

			annotation SummarizationSetBy = Automatic

        column 'Holiday Name'
			dataType: string
			lineageTag: mno
			summarizeBy: none
			sourceColumn: Holiday Name

			annotation SummarizationSetBy = Automatic
        
        column IsWorkingDay
			dataType: string
			lineageTag: pqr
			summarizeBy: none
			sourceColumn: IsWorkingDay

			annotation SummarizationSetBy = Automatic
		...	
		
		calendar 'Demo Calendar'
			lineageTag: def

			calendarColumnGroup = year
				primaryColumn: Year

			calendarColumnGroup = month
				primaryColumn: Month
				associatedColumn: DutchMonthName
				associatedColumn: MonthName
			
			calendarColumnGroup
                column: 'Holiday Name'
                column: isWorkingDay

注释

请注意,如果未为 calendarColumnGroup TMDL 中指定任何类别,列将标记为 时间相关。 在此示例中,假日名称isWorkingDay演示日历上的时间相关列。

把它放在一起:时间转移的示例

一些 时间智能函数 仅横向转移上下文,考虑所有列,而其他函数则执行分层移位 -- 根据是否在日历中标记列来保留或清除上下文。 根据时间智能函数是否允许层级变更,它们可以被划分为两个组:

  • 已修复。 此组中的函数是 DATEADDSAMEPERIODLASTYEAR。 这些函数仅允许横向时间转移,并且不返回不同级别的详细信息值。
  • 灵活。 此组包含所有其他时间智能函数。 这些函数确实允许进行分层时间转换,根据日历设置,可以从不同层次的详细信息中返回结果。

为了显示这些行为,让我们使用由两个表、两个日历和五个度量值组成的简单数据模型演练一个示例。

数据表和关系

对于此示例,我们有以下简单的数据模型:

Table
日期 年份、是否工作日、日期
Sales 订单键、数量、订单日期

下面是 Date 表中的一些示例行: 显示示例 Date 表的前 14 行的屏幕截图。该表定义 Date、Year 和 IsWorkingDay。Date 列包含日期、年份列、年份值,IsWorkingDay 列是布尔值(True/False)列,指示日期是否为工作日。

下面是 Sales 表中的一些示例行: 显示示例 Sales 表的前 9 行的屏幕截图。该表定义 OrderKey、OrderDate 和 Order Quantity。OrderKey 列包含每个销售订单的唯一编号,OrderDate 是一个日期,将“销售额”与“日期”关联,“订单数量”是一个数字列,表示每个订单的产品数。

Sales 和 Date 表与 OrderDate 和 Date 相关。

显示示例模型的模型视图的屏幕截图。它显示 Sales 和 Date 表,以及 OrderDate 和 Date 上它们之间的一对多关系。

Calendars

“日期” 表中,我们定义了具有以下映射的日历:

CalendarName 类别 主列
公历 年份 年份
日期 日期
GregorianWithWorkingDay 年份 年份
日期 日期
时间相关 IsWorkingDay

这两个日历的等效 TMDL 定义为:

ref table Date
    calendar 'Gregorian'
        lineageTag: xyz
    
        calendarColumnGroup = year
    	    primaryColumn: Year
    
    	calendarColumnGroup = date
    		primaryColumn: Date
    
    calendar 'GregorianWithWorkingDay'
    	lineageTag: dc4fc383-1661-4112-8afb-930d324fbb6e
    
    	calendarColumnGroup = year
    		primaryColumn: Year
    
    	calendarColumnGroup = date
    		primaryColumn: Date
    
    	calendarColumnGroup
    		column: IsWorkingDay   

措施

Sales 表中,我们定义了以下度量值:

Total Quantity = SUM ( 'Sales'[Order Quantity] )

OneYearAgoQuantity =
CALCULATE ( [Total Quantity], DATEADD ( 'Gregorian', -1, YEAR ) )

OneYearAgoQuantityTimeRelated =
CALCULATE ( [Total Quantity], DATEADD ( 'GregorianWithWorkingDay', -1, YEAR ) )

FullLastYearQuantity =
CALCULATE ( [Total Quantity], PARALLELPERIOD ( 'Gregorian', -1, YEAR ) )

FullLastYearQuantityTimeRelated =
CALCULATE ( [Total Quantity], PARALLELPERIOD ( 'GregorianWithWorkingDay', -1, YEAR )
)

横向移位示例

让我们创建一个视觉对象,显示年份、月份、工作日、总数量、一年前的数量和与时间相关的一年前数量,针对 2024 和 2025:

显示 Year、IsWorkingDay、Total Quantity、OneYearAgoQuantity 和 OneYearAgoQuantityTimeRelated 的表视觉对象的屏幕截图。OneYearAgoQuantity 和 OneYearAgoQuantityTimeRelated for 2025 的值与相同 IsWorkingDay 值的 2024 值匹配。

对于 2025 年,同一 IsWorkingDay 值的 OneYearAgoQuantityOneYearAgoQuantityTimeRelated 的所有值都与上一年(2024 年)的 Total Quantity 相匹配。

这表明,无论日期表上的任何列是否标记为与时间相关或未标记,DATEADD 都会保留上下文。 由于在我们的 度量值定义 中,我们指示 DATEADD 将一 移回,唯一其上下文已移位的列是与 Year 类别关联的列。 IsWorkingDay 列是否在日历中标记为与时间无关,但根本不会更改结果。 显示此行为的唯一其他函数是 SAMEPERIODLASTYEAR

分层移位示例

现在,让我们看看一个示例,其中将列标记为与时间相关的列确实会更改结果。

为此,我们将使用 FullLastYearQuantity 和 FullLastYearQuantityTimeRelated 度量值,重现之前示例中的相同可视化效果:屏幕截图显示一个表格视觉,其中列出了 Year、IsWorkingDay、Total Quantity、FullLastYearQuantity 和 FullLastYearQuantityTimeRelated。对于 FullLastYearQuantity,2025 年的值与具有相同 IsWorkingDay 值的 2024 年值相匹配,但 FullLastYearQuantityTimeRelated 的值则始终等于总数量值,无论 IsWorkingDay 的值如何。

这表明 PARALLELPERIOD 保留日历中未标记为时间相关的列的上下文,但会清除标记为时间相关的列的上下文。 FullLastYearQuantity 使用未在日历中标记 IsWorkingDay 的 公历 ,而 FullLastYearQuantityTimeRelated 使用 公历WithWorkingDay 日历,其中 IsWorkingDay 被标记为与时间相关的。 除DATEADD和SAMEPERIODLASTYEAR外,所有时间智能函数均以此方式工作。

提示:如果真的想强制这些函数保留与时间相关的列的上下文,也可以使用 VALUES

FullLastYearQuantityTimeRelatedOverride =
CALCULATE ( [Total Quantity], PARALLELPERIOD ( 'GregorianWithWorkingDay', -1, YEAR ), VALUES('Date'[IsWorkingDay]) )

在这种情况下,FullLastYearQuantityTimeRelatedOverride 返回的结果与 FullLastYearQuantity 相同。

结论

上面的详细示例显示,不同的时间智能函数的行为方式不同,具体取决于是否在日历中标记为与时间相关的列。 DATEADD 并且 SAMEPERIODLASTYEAR 仅执行横向时间转移。 所有其他 时间智能函数 都允许分层时间转移。

与日历一起使用DATEADD

DATEADD函数具有特定的参数,允许对选定内容在比interval参数指示的移位级别更细致的级别时如何进行移位的细致控制。 例如,如果要在日期级别显示数据,但将参数设置为intervalDATEADDMONTH,则会发生此情况。 例如,在公历中,将 3 月 3 日到 3 月 10 日的时间段移动一个月后,结果为 4 月 3 日至 4 月 10 日。 但是,由于公历中的月份因长度而异,因此在移动时可能会导致歧义。 下面是基于公历的示例方案:

从较短时间段转变到较长时间段

例如,如果在 2 月份进行选择并向前移动一个月,那么目标月份就是 3 月。 可以使用参数 extension 来影响转换的执行方式:

扩展参数值 Description 结果
precise 这严格保留原始日期范围。 2月25-28日改为3月25-28日。
extended 允许时间窗口扩展至月底。 2月25-28日改为3月25-31日。

从更长的时间转移到更短的时间段

例如,在 3 月选择一个日期,然后向后移动一个月,因此目标月份为 2 月。

可以使用参数 truncation 来影响转换的执行方式:

截断参数值 Description 结果
anchored 将结果锚定到较短月份的最后一个有效日期。 3 月 31 日将调整为 2 月 28 日(或在闰年为 2 月 29 日)。
blank 如果移位日期不存在,则返回 空白 将 3 月 31 日移回一个月返回 空白(因为 2 月 31 日并不存在)。

使用基于日历的时间智能的注意事项

  • 在定义日历并受行级安全性(RLS)规则影响的事实数据表上执行时间智能计算,可能会导致意外结果。
  • 此预览功能的性能不代表最终产品。
  • 你尚无法在 Power BI 服务中创作日历。
  • 不应将 自动日期/时间表 用于自定义日历。
  • 不能将日历与实时连接模型或复合模型配合使用。
  • 我们建议您仅关联日历中要用于时间智能计算的列。
  • 日历受 实时脱机 验证的约束。 尽管存在脱机验证错误,但可以保存日历,但建议先解决这些错误。 必须修复实时验证失败才能保存。
  • 每个日历必须在数据模型中具有唯一的名称
  • 单个表可以包含多个日历
  • 包含日历的表必须少于 200 列。 如果表包含的行数超过 20,000 行,则验证将不可用,但仍可以添加日历。
  • 日历必须至少向类别分配一个主要列
  • 日历只能将自己的表中的列分配给类别
  • 每个类别应设有一个主要列,并且可以分配零个或多个关联列。
  • DATEADD 具有用于控制扩展及其行为的新参数,这些参数在 IntelliSense 中无法识别。
  • 任何给定列只能映射到一个类别
  • 不能嵌套使用日历的时间智能函数。 例如,不支持以下 DAX 语句:
ThisIsNotSupported = PREVIOUSDAY ( PREVIOUSMONTH( 'Calendar' ) )

相反,你可以执行以下操作:

ThisWorks = CALCULATETABLE ( PREVIOUSDAY ( 'Calendar' ), PREVIOUSMONTH( 'Calendar' ) )

使用内置工具创建日期表

以下示例使用 Power Query M 或 DAX 创建日期表,从 2010 年 1 月 1 日到 2030 年 12 月 31 日。 它包含以下列:年份、月份数字、月份名称、月份年份、季度、年份季度、天、日期。

Power Query M

let
    StartDate = #date(2010, 1, 1),
    EndDate = #date(2030, 12, 31),
    NumberOfDays = Duration.Days(EndDate - StartDate) + 1,
    DateList = List.Dates(StartDate, NumberOfDays, #duration(1,0,0,0)),
    DateTable = Table.FromList(DateList, Splitter.SplitByNothing(), {"Date"}),
    AddYear = Table.AddColumn(DateTable, "Year", each Date.Year([Date]), Int64.Type),
    AddMonthNumber = Table.AddColumn(AddYear, "Month Number", each Date.Month([Date]), Int64.Type),
    AddMonthName = Table.AddColumn(AddMonthNumber, "Month Name", each Date.ToText([Date], "MMMM"), type text),
    AddMonthYear = Table.AddColumn(AddMonthName, "Month Year", each Date.ToText([Date], "MMM yyyy"), type text),
    AddQuarter = Table.AddColumn(AddMonthYear, "Quarter", each "Q" & Text.From(Date.QuarterOfYear([Date])), type text),
    AddYearQuarter = Table.AddColumn(AddQuarter, "Year Quarter", each Text.From(Date.Year([Date])) & " Q" & Text.From(Date.QuarterOfYear([Date])), type text),
    AddDay = Table.AddColumn(AddYearQuarter, "Day", each Date.Day([Date]), Int64.Type)
in
    AddDay

DAX

DateTable =
ADDCOLUMNS (
    CALENDAR ( DATE ( 2010, 1, 1 ), DATE ( 2030, 12, 31 ) ),
    "Year", YEAR ( [Date] ),
    "Month Number", MONTH ( [Date] ),
    "Month Name", FORMAT ( [Date], "MMMM" ),
    "Month Year", FORMAT ( [Date], "MMM YYYY" ),
    "Quarter", "Q" & FORMAT ( [Date], "Q" ),
    "Year Quarter",
        FORMAT ( [Date], "YYYY" ) & " Q"
            & FORMAT ( [Date], "Q" ),
    "Day", DAY ( [Date] ),
    "Date", [Date]
)

有关详细信息和更多选项,请参阅 日期表

有关本文的详细信息,请参阅以下资源: