我是BI佐罗。针对本问题,我来彻底向您解释清楚展示如何使用标准日历来实现与时间相关的计算。可以为你打开了新世界的大门。 (文末有赠送Power BI学习课程福利)。
标准日历的最大优势是,您可以依赖几个内置的时间智能函数。内置函数可以为最常见的需求提供正确的结果。
如果内置函数无法满足您的需求,或者如果您使用的是非标准日历,则可以使用常规的(与时间无关的)DAX函数来达到相同的目标。以这种方式,您可以随意自定义代码的结果。也就是说,如果您需要自定义计算,则还需要使用DAX公式操作筛选器所需的一组列来丰富日期表。这些自定义计算包含在“ 自定义时间相关的计算 ”模式中。
如果您使用常规的公历日历,则此模式中的公式是生成时间智能计算的最简单、最有效的方法。请记住,标准的DAX时间智能函数仅支持常规的公历日历——即由12个月组成、每个月都有其公历天数、三个月组成一个季度、具备人们普遍惯用的所有常规日历特征的日历。
时间智能计算简介
为了使用所有时间智能计算,您需要一个格式正确的日期表。该 Date 表必须满足以下要求:
- 必须具备年份中的所有日期。该 Date 表必须始终始于1月1日、终于12月31日,并包括此日期范围内的所有天。如果报告仅采用会计年度,则日期表必须包含从会计年度的第一天到最后一天的所有日期。例如,如果2008会计年度始于2007年7月1日,则 Date 表必须包括从2007年7月1日到2008年6月30日的所有天。
- 必须具有包含数据类型为“ 日期/时间 ”或“ 日期 ”的不重复值的列。该列通常命名为 Date 。尽管 Date 列通常用于定义与其他表的关系,但这不是强制的。不过, Date 列必须包含不重复值,并应为其应用“标记为日期表”功能。如果该列还包含时间部分,则不应使用时间——例如,时间应始终为12:00 am。
- 模型中的 Date 表必须标记为日期表,以防没有基于 Date 而与其他表(如示例中的 Sales )建立联系。(译者注:对于符合日期表要求且位于一对多关系一端的日期表,不标记为日期表,也会自动具备日期表的特性。)
有几种创建 Date 表的方法。只要日期表满足要求,创建 Date 表的方式就不会影响您使用标准时间智能计算的方式。如果您已经有一份符合报告需要的 Date 表,只需在确保该表可以满足最低要求后,便可将其导入并将其标记为日期表。如果没有 Date 表,则可以使用DAX计算表来创建(后面会做介绍)。
最佳做法是对用于时间智能计算的 Date 表应用“标记为日期表”设置。“标记为日期表”设置会在每次对 Date 列应用筛选器时,在 Date 表上增加一个REMOVEFILTERS修饰符。用于CALCULATE的所有时间智能函数都会执行此操作(对 Date 列应用筛选器)。如果您使用 Date 列定义 Sales 表和 Date 表之间的关系,则DAX会执行相同的操作。尽管如此,将“标记为日期表”设置应用于日期表依然是最佳做法。如果您有多个日期表,则可以将它们全部标记为日期表。
如果您没有采用“标记为日期表”设置,而且也没有使用日期列来建立关系,那么每当您在CALCULATE中使用时间智能函数时,都必须在 Date 表上增加一个REMOVEFILTERS。《Power BI Desktop中的时间智能》( https:// sql.bi/28211 )一文对此进行了详细描述。
什么是标准DAX时间智能函数
标准时间智能函数是表函数,该函数返回在 CALCULATE中用作筛选器的日期列表。通过编写更复杂的筛选器表达式可以获得时间智能函数的结果。例如,DATESYTD函数可以返回筛选上下文中从显示的日期所在年份的第一天到显示的最后一天之间的所有日期。下方的表达式:
DATESYTD ( 'Date'[Date] )
对应下方的FILTER表达式:
VAR LastDateAvailable = MAX ( 'Date'[Date] )
VAR FirstJanuaryOfLastDate = DATE ( YEAR ( LastDateAvailable ), 1, 1 )
RETURN
FILTER (
ALL ( 'Date'[Date] ),
AND (
'Date'[Date] >= FirstJanuaryOfLastDate,
'Date'[Date] <= LastDateAvailable
)
时间智能函数有很多,大多数时间智能函数是以这种方式呈现。请注意:时间智能函数应用作CALCULATE的筛选器参数,有时您会通过使用变量来实现这一点。在迭代器中使用时间智能函数是很危险的,因为会触发隐式上下文转换,从而导致从筛选上下文中检索有效日期。DAX指南文档中提供了更多详细信息,例如 https:// dax.guide/datesytd/ 。
以下是使用时间智能函数时最佳做法的快速指南:
- 仅在CALCULATE / CALCULATETABLE的筛选器参数中使用诸如DATESYTD之类的时间智能函数,或给变量分配筛选器。
- 在返回值的DAX公式中使用EDATE和EOMONTH之类的标量函数(也称为标量表达式)。这些函数不是时间智能函数,可以用于以行上下文执行的表达式中。
- 使用CONVERT将日期转换为数字,反之亦然。
- 有关时间智能函数的完整最新列表,请访问 https:// dax.guide/ 。
DAX初学者经常将时间智能函数与常规(标量)时间函数混淆。这种混淆导致出现常见错误,可以通过遵循以下建议来避免:
- 不要 使用DATEADD来返回前一天或后一天。可以使用简单的数学运算符来做到这一点。
- 不要 使用PREVIOUSDAY来计算标量表达式中的前一天。从日期中减一,即可获得标量表达式中的前一天。
- 不要 将EOMONTH用作筛选器,而应使用ENDOFMONTH。EOMONTH是标量表达式。ENDOFMONTH是时间智能函数。请始终注意函数的返回类型:只有表函数是时间智能函数,因此不应当用于标量表达式中。
禁用自动日期/时间
Power BI可以自动将 Date 表添加到数据模型。 但是,强烈建议禁用Power BI创建的自动 Date 表 ,而采用导入或创建的方式生成显式 Date 表。更多详细信息,请参见《Power BI中的自动时间智能》一文( https:// sql.bi/137706 )。
自动 Date 表还会启用一种特定语法,称为列变化。列变化的表达方式为在日期列的后面跟着一个点,点的后面跟着自动创建的日期表的列:
Sales[Order Date].[Date]
Power BI快速度量值用于自动 Date 表时,会大量使用列变化。我们不要依赖Power BI中自动创建的日期表,因为我们希望保持最大的灵活性和对模型的最大控制。列变化的语法不适用于作为模型的一部分的日期表,因此不会自动创建。
标准时间智能函数的局限性
标准时间智能函数可以在常规的公历日历上使用。本节中会列出它们存在的几个局限性。当您的需求与这些局限性不兼容时,您需要采用其他模式(请参见“ 自定义时间相关的计算 ”和“ 与周相关的计算 ”)。
- 年度起始于1月1日。对于从不同日期开始的会计日历,该函数起不到太大作用。但是,由于会计日历在处理闰年方面的时间处理缺陷,每个会计年度的第一天必须是相同的,且不能是3月1日。
- 季度总是起始于一月、四月、七月和十月的第一天。季度的范围不能修改。
- 月份始终是一个公历月。
- 标准时间智能函数可能不正确支持其他列的筛选器,例如 Day of Week 或 Working Day 。有关可能的解决方法的更多详细信息,请参见《筛选其他日期属性》一节。
因此,标准时间智能计算不支持许多高阶计算,如,对数周的计算。这些高阶计算需要自定义日历。
创建 Date 表
DAX时间智能函数可在任何标准公历日历表上使用。如果您已经有一份 Date 表,则可以将其导入并使用,而不会出现任何问题。如果没有 Date 表,则可以使用DAX计算表来创建。例如,以下DAX表达式定义了本章中使用的简单 Date 表:
计算表
Date =
VAR FirstFiscalMonth = 7 -- 会计年度的第一个月
VAR FirstDayOfWeek = 0 -- 0 = 周日, 1 = 周一, ...
VAR FirstYear = -- 自定义使用的第一个年份
YEAR ( MIN ( Sales[Order Date] ))
RETURN
GENERATE (
FILTER (
CALENDARAUTO (),
YEAR ( [Date] ) >= FirstYear
VAR Yr = YEAR ( [Date] ) -- 年份编号
VAR Mn = MONTH ( [Date] ) -- 月份编号(1-12)
VAR Qr = QUARTER ( [Date] ) -- 季度编号(1-4)
VAR MnQ = Mn - 3 * (Qr - 1) -- 季度中的月份编号(1-3)
VAR Wd = WEEKDAY ( [Date], 1 ) - 1 -- 星期几(0 = 周日,1 = 周一,...)
VAR Fyr = -- 会计年度编号
Yr + 1 * ( FirstFiscalMonth > 1 && Mn >= FirstFiscalMonth )
VAR Fqr = -- 会计季度(字符串)
FORMAT ( EOMONTH ( [Date], 1 - FirstFiscalMonth ), "\QQ" )
RETURN ROW (
"Year", DATE ( Yr, 12, 31 ),
"Year Quarter", FORMAT ( [Date], "\QQ-YYYY" ),
"Year Quarter Date", EOMONTH ( [Date], 3 - MnQ ),
"Quarter", FORMAT ( [Date], "\QQ" ),
"Year Month", EOMONTH ( [Date], 0 ),
"Month", DATE ( 1900, MONTH ( [Date] ), 1 ),
"Day of Week", DATE ( 1900, 1, 7 + Wd + (7 * (Wd < FirstDayOfWeek)) ),
"Fiscal Year", DATE ( Fyr + (FirstFiscalMonth = 1), FirstFiscalMonth, 1 ) - 1,
"Fiscal Year Quarter", "F" & Fqr & "-" & Fyr,
"Fiscal Year Quarter Date", EOMONTH ( [Date], 3 - MnQ ),
"Fiscal Quarter", "F" & Fqr
)
您可以自定义前三个变量以创建满足特定业务需求的 Date 表。为了获得正确的结果,当列不是文本格式,而是具有标准或自定义格式的“日期”数据类型时,必须按以下方式在数据模型中对列进行设置:
- Date :m/dd/yyyy(8/14/2007), 用作标记为日期表的列
- Year :yyyy(2007)
- Year Quarter :文本(Q3-2008)
- Year Quarter Date :隐藏(9/30/2008)
- Quarter :文本(Q1)
- Year Month :mmm yyyy(Aug 2007)
- Month :mmm(Aug)
- Day of Week :ddd(Tue)
- Fiscal Year :\F\Y yyyy(FY 2008)
- Fiscal Year Quarter :文本(FQ1-2008)
- Fiscal Year Quarter Date :隐藏(9/30/2008)
- Fiscal Quarter :文本(FQ1)
此模式中的 Date 表具有两个层次结构:
- 日历:年( Year ),季度( Year Quarter) ,月( Year Month )
- 会计:年( Fiscal Year ),季度( Fiscal Year Quarter ),月( Year Month )
不管来源如何,若要使用此模式的公式, Date 表都还必须包括一个隐藏的 DateWithSales 计算列:
Date表中的计算列
DateWithSales =
'Date'[Date] <= MAX ( Sales[Order Date] )
如果日期早于或等于 Sales 表中的最后交易日期,则 Date[DateWithSales] 列是 TRUE ,否则为 FALSE 。换句话说,“过去”日期的 DateWithSales 为 TRUE ,“未来”日期为 FALSE ,这里的“过去”和“未来”均相对于 Sales 中的最后交易日期。
控制未来日期中可视化
大多数时间智能计算都不应显示最后有效日期之后的日期值。例如,年初至今的计算也可以显示未来日期的值,但是我们想要将其隐藏。这些示例中使用的数据集在2009年8月15日结束。因此,我们将月份“August 2009”、2009年第三季度“Q3-2009”和年份“2009”视为数据的最后时间段。2009年8月15日以后的任何日期都被视为未来,我们想要隐藏未来值。
为了避免显示未来日期的结果,我们使用 ShowValueForDates 度量值。
如果所选的时间段不是在数据的最后一个时间段之后,则 ShowValueForDates 返回TRUE:
Date表中度量值(隐藏)
ShowValueForDates :=
VAR LastDateWithData =
CALCULATE (
MAX ( 'Sales'[Order Date] ),
REMOVEFILTERS ()
VAR FirstDateVisible =
MIN ( 'Date'[Date] )
VAR Result =
FirstDateVisible <= LastDateWithData
RETURN
Result
ShowValueForDates 度量值是隐藏的。这是一项技术措施,目的是在许多与时间相关的不同计算中实现重复使用同一逻辑,并且用户不应直接在报告中使用 ShowValueForDates 。
命名约定
本节介绍用来引入时间智能计算的命名约定。一个简单的分类显示了一个计算是否:
- 推移一段时间,例如上一年的同一时间段;
- 执行聚合,例如年初至今;或
- 比较两个时间段,例如今年与去年比较。
首字母缩写 | 描述 | 推移 | 聚合 | 比较 |
YTD | 年初至今 | X | ||
QTD | 季初至今 | X | ||
MTD | 月初至今 | X | ||
MAT | 移动年度总计 | X | ||
PY | 上一年 | X | ||
PQ | 上一季 | X | ||
PM | 上一月 | X | ||
PYC | 上一整年 | X | ||
PQC | 上一整季 | X | ||
PMC | 上一整月 | X | ||
PP | 上一时间段 (自动选择年、季或月) | X | ||
PYMAT | 上一年移动年度总计 | X | X |
首字母缩写 | 描述 | 推移 | 聚合 | 比较 |
YOY | 比上个年度的差异 | X | ||
QOQ | 比上个季度的差异 | X | ||
MOM | 比上个月度的差异 | X | ||
MATG | 移动年度总计增长 | X | X | X |
POP | 比上个时间段的差异 (自动选择年、季或月) | X | ||
PYTD | 上个年度的年初至今 | X | X | |
PQTD | 上个季度的季初至今 | X | X | |
PMTD | 上个月度的月初至今 | X | X | |
YOYTD | 比上个年度年初至今的差异 | X | X | X |
QOQTD | 比上个季度季初至今的差异 | X | X | X |
MOMTD | 比上个月度月初至今的差异 | X | X | X |
YTDOPY | 年初至今比上个年度 | X | X | X |
QTDOPQ | 季初至今比上个季度 | X | X | X |
MTDOPM | 月初至今比上个月度 | X | X | X |
期初至今总计
年初至今、季初至今和月初至今的计算会修改 Date 表的筛选上下文,将一系列日期用作筛选器,覆盖所选时间段的筛选器。
所有这些计算都可以使用带有时间智能函数的常规CALCULATE或某一个TOTAL函数(例如TOTALYTD)来实现。TOTAL函数只是CALCULATE版本的语法糖。尽管我们更喜欢CALCULATE版本, CALCULATE会使公式逻辑更明显,并且比TOTAL函数具有更大的灵活性,但我们仍将TOTAL函数作为参考。在以下示例中,使用TOTAL函数的公式会被标记为 (2)。展示TOTAL函数的目的仅仅是为了显示它们可以返回与CALCULATE版本相同的值。
年初至今总计
“年初至今”聚合始于该年度1月1日的数据,如图2-1所示。
图2-1 Sales YTD (simple) 显示了任何时间段的值,而 Sales YTD 和 Sales YTD (2) 隐藏了数据的最后一个时间段之后的数据。
年初至今总计的度量值可以通过DATESYTD函数实现,如下所示:
Sales表中的度量值
Sales YTD (simple) :=
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date] )
)
DATESYTD返回一个日期集,该日期集包含筛选上下文中从显示的日期所在年份的第一天到显示的最后一天之间的所有日期。因此, Sales YTD (simple) 度量值甚至会显示该年份未来日期的数据。通过仅当 ShowValueForDates 度量值返回TRUE时才返回结果,就可以避免在 Sales YTD 度量值中出现这种情况:
Sales表中的度量值
Sales YTD :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date] )
)
如果报告不是基于公历年度,而是会计年度,则DATESYTD会需要增加一个参数来识别会计年度的最后一天。以图2-2中的报告为例。
图2-2 Sales Fiscal YTD 和 Sales Fiscal YTD (2) 显示了基于会计年度的年初至今。
Sales Fiscal YTD 度量值在DATESYTD的第二个参数中指定了会计年度的最后一天和月份。以下度量值将6月30日用作会计年度的最后一天。DATESYTD的第二个参数必须是与 Date 表中会计年度的定义相对应的固定值(也称为常量)。它不能动态计算:
Sales表中的度量值
Sales Fiscal YTD :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date], "6-30" )
)
TOTALYTD函数可以替代DATESYTD:
Sales表中的度量值
Sales YTD (2) :=
[ShowValueForDates],
TOTALYTD (
[Sales Amount],
'Date'[Date]
)
Sales表中的度量值
Sales Fiscal YTD (2) :=
[ShowValueForDates],
TOTALYTD (
[Sales Amount],
'Date'[Date],
"6-30"
)
季度初至今总计
“季初至今”聚合始于该季度第一天的数据,如图2-3所示。
图2-3 Sales QTD 显示了季初至今的金额,2009年显示为空白,因为2009年4季度没有数据。
季初至今总计的度量值可以通过DATESQTD函数来计算,如下所示:
Sales表中的度量值
Sales QTD :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
DATESQTD ( 'Date'[Date] )
)
TOTALQTD函数可以替代DATESQTD:
Sales表中的度量值
Sales QTD (2) :=
[ShowValueForDates],
TOTALQTD (
[Sales Amount],
'Date'[Date]
)
月初至今总计
“月初至今”聚合始于该月度第一天的数据,如图2-4所示。
图2-4 Sales MTD 显示了月初至今的金额,CY 2009和Q3-2009显示为空白,是因为2009年8月15日之后没有数据。
月初至今总计的度量值可以通过DATESMTD函数来计算,如下所示:
Sales表中的度量值
Sales MTD :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
DATESMTD ( 'Date'[Date] )
)
TOTALMTD可以替代DATESMTD:
Sales表中的度量值
Sales MTD (2) :=
[ShowValueForDates],
TOTALMTD (
[Sales Amount],
'Date'[Date]
)
比上个时间段增长的计算
一个常见的需求是将一个时间段与上一年、上一个季度或上一个月的相同时间段进行比较。上一个年、季、月可能不完整,因此,为了实现合理的比较,比较时应考虑一个等效时间段。由于这些原因,本节中显示的计算会使用 Date[DateWithSales] 计算列,如《隐藏DAX计算中的未来日期》( https:// sql.bi/78171 )一文所述。
比上个年度增长
“比上个年度”是将一个时间段与上一年的等效时间段进行比较。在此示例中,有效数据截止到2009年8月15日。因此, Sales PY 显示的2008年数字,仅考虑2008年8月15日之前的交易。图2-5显示,2008年8月的 Sales Amount 为721,560.95,而2009年8月的 Sales PY 返回值为296,529.51,是因为度量值仅考虑了截至2008年8月15日的销售额。
图2-5 2009年8月的 Sales PY 显示的是2008年8月1日-15日的金额,因为在2009年8月15日之后没有数据。
Sales PY 使用DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。“比上个年度增长”的计算,以数字形式表现为 Sales YOY ,以百分比形式表现为 Sales YOY % 。这两个度量值都使用 Sales PY 来确保仅计算截至2009年8月15日的数据:
Sales表中的度量值
Sales PY :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, YEAR ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales YOY :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PY]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod ) && NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales YOY % :=
DIVIDE (
[Sales YOY],
[Sales PY]
)
也可以使用SAMEPERIODLASTYEAR编写 Sales PY 。SAMEPERIODLASTYEAR更易于阅读,但没有任何性能上的优势。这是因为,从本质上讲,该函数在前面的公式中已被翻译为DATEADD函数:
Sales表中的度量值
Sales PY (2) :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
CALCULATETABLE (
SAMEPERIODLASTYEAR ( 'Date'[Date] ),
'Date'[DateWithSales] = TRUE
)
比上个季度增长
“比上个季度”是将一个时间段与上一季度的等效时间段进行比较。在此示例中,有效数据截止到2009年8月15日(即2009年第三季度的前半部分)。因此,2009年8月(即第三季度的第二个月)的 Sales PQ 显示截至2009年5月15日(即上一季度第二个月的前半部分)的销售情况。图2-6显示,2009年5月的 Sales Amount 为1,067,165.23,而2009年8月的 Sales PQ 返回值为435,306.10,是因为仅考虑截至2009年8月15日的销售额。
图2-6 2009年8月的 Sales PQ 显示的是2009年5月1日-15日的金额,因为在2009年8月15日之后没有数据。
Sales PQ 使用DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。“比上个季度增长”的计算,以数字形式表现为 Sales QOQ ,以百分比形式表现为 Sales QOQ % 。这两个度量值都使用 Sales PQ 来确保对相同时间段进行合理比较:
Sales表中的度量值
Sales PQ :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, QUARTER ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales QOQ :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PQ]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod ) && NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales QOQ % :=
DIVIDE (
[Sales QOQ],
[Sales PQ]
)
比上个月度增长
“比上个月度”是将一个时间段与上一月度的等效时间段进行比较。在此示例中,有效数据截止到2009年8月15日。因此, Sales PM 仅考虑2009年7月1日-15日这一时期的销售额,以便返回与2009年8月进行比较的值。以这种方式,它仅返回上一个月相应时间段的数据。图2-7显示,2009年7月的 Sales Amount 为1,068,396.58,而2009年8月的 Sales PM 返回值为584,212.78,是因为仅考虑了截至2009年7月15日的销售额。
图2-7 2009年8月的 Sales PM 显示的是2009年7月1日-15日的金额,因为在2009年8月15日之后没有数据。
Sales PM 使用DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。“比上个月度增长”的计算,以数字形式表现为 Sales MOM ,以百分比形式表现为 Sales MOM % 。这两个度量值都使用 Sales PM 来确保对相同时间段进行合理比较:
Sales表中的度量值
Sales PM :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, MONTH ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales MOM :=
VAR ValueCurrentPeriod = [Sales Amount]
VAR ValuePreviousPeriod = [Sales PM]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales MOM % :=
DIVIDE (
[Sales MOM],
[Sales PM]
)
比上个时间段增长
“比上个时间段增长”会根据当前的可视化选择,自动选择本节中先前介绍的度量值之一。例如,如果可视化显示月度级别的数据,则会返回“比上个月度增长”的度量值;如果可视化显示年度级别的总计,则返回“比上个年度增长”的度量值。预期结果如图2-8所示。
图2-8 Sales PP 在月度级别上显示上个月的值,在季度级别上显示上一个季度的值,在年度级别上显示上一年的值。
Sales PP 、 Sales POP 和 Sales POP% 这三个度量值,会根据报告中选择的级别,定向到相应的年、季度和月的度量值,来进行计算。ISINSCOPE函数会检测报告中所使用的级别。传递给ISINSCOPE的参数是图2-8的矩阵视图的行所使用的属性。度量值的定义方法如下:
Sales表中的度量值
Sales POP % :=
SWITCH (
TRUE,
ISINSCOPE ( 'Date'[Year Month] ), [Sales MOM %],
ISINSCOPE ( 'Date'[Year Quarter] ), [Sales QOQ %],
ISINSCOPE ( 'Date'[Year] ), [Sales YOY %]
)
Sales表中的度量值
Sales POP :=
SWITCH (
TRUE,
ISINSCOPE ( 'Date'[Year Month] ), [Sales MOM],
ISINSCOPE ( 'Date'[Year Quarter] ), [Sales QOQ],
ISINSCOPE ( 'Date'[Year] ), [Sales YOY]
)
Sales表中的度量值
Sales PP :=
SWITCH (
TRUE,
ISINSCOPE ( 'Date'[Year Month] ), [Sales PM],
ISINSCOPE ( 'Date'[Year Quarter] ), [Sales PQ],
ISINSCOPE ( 'Date'[Year] ), [Sales PY]
)
期初至今增长的计算
“期初至今”增长的度量值是指将“期初至今”度量值与基于特定偏移量的等效时间段的同一度量值进行比较。例如,您可以将年初至今的聚合结果与上一年的年初至今进行比较,偏移量为一年。
这组计算中的所有度量值均需考虑不完整时间段。示例中有效数据截止到2009年8月15日,因此这些度量值可确保上一年的计算不包括2009年8月15日之后的日期。
比上个年度年初至今增长
“比上个年度年初至今增长”将特定日期的年初至今与上一年等效日期的年初至今进行比较。图2-9显示,2009年的 Sales PYTD 仅考虑截至2008年8月15日的交易。因此,Q3-2008的 Sales YTD 为7,129,971.53,而Q3-2009的 Sales PYTD 较低,为5,741,502.86。
图2-9 Q3-2009的 Sales PYTD 显示的是2008年1月1日至8月15日的金额,因为在2009年8月15日之后没有数据。
Sales PYTD使用 DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。 Sales YOYTD 和 Sales YOYTD % 通过 Sales PYTD 来确保对相同时间段进行合理比较:
Sales表中的度量值
Sales PYTD :=
[ShowValueForDates],
CALCULATE (
[Sales YTD],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, YEAR ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales YOYTD :=
VAR ValueCurrentPeriod = [Sales YTD]
VAR ValuePreviousPeriod = [Sales PYTD]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales YOYTD % :=
DIVIDE (
[Sales YOYTD],
[Sales PYTD]
)
Sales PYTD 使用DATEADD将日期筛选器回移一年。使用DATEADD可以轻松地推移两年或两年以上。但是,也可以使用SAMEPERIODLASTYEAR编写 Sales PYTD 将日期回移一年。如下面这个示例,其本质用法就是上例中的DATEADD。
Sales表中的度量值
Sales PYTD (2) :=
[ShowValueForDates],
CALCULATE (
[Sales YTD],
CALCULATETABLE (
SAMEPERIODLASTYEAR ( 'Date'[Date] ),
'Date'[DateWithSales] = TRUE
)
比上个季度季初至今增长
“比上个季度季初至今增长”将特定日期的季初至今与上一季度等效日期的季初至今进行比较。图2-10显示,2009年8月的 Sales PQ 仅考虑截至2008年5月15日的交易,仅获得上一季度的前半部分的金额。因此,2009年5月的 Sales QTD 为1,746,058.45,而2009年8月的 Sales PQTD 较低,为1,114,199.32。
图2-10 2009年8月的 Sales PQTD 显示的是2009年4月1日至5月15日的金额,因为在2009年8月15日之后没有数据。
Sales PQTD 使用DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。 Sales QOQTD 和 Sales QOQTD % 通过 Sales PQTD 来确保对相同时间段进行合理比较:
Sales表中的度量值
Sales PQTD :=
[ShowValueForDates],
CALCULATE (
[Sales QTD],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, QUARTER ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales QOQTD :=
VAR ValueCurrentPeriod = [Sales QTD]
VAR ValuePreviousPeriod = [Sales PQTD]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales QOQTD % :=
DIVIDE (
[Sales QOQTD],
[Sales PQTD]
)
比上个月度月初至今增长
“比上个月度月初至今增长”将特定日期的月初至今与上一个月等效日期的月初至今进行比较。图2-11显示,2009年8月的 Sales PMTD 仅考虑截至2009年7月15日的交易,以获得上一个月的相应时间段。因此,2009年7月的 Sales MTD 为1,068,396.58,而2009年8月的 Sales PMTD 较低,为584,212.78。
图2-11 2009年8月的 Sales PQTD 显示的是2009年7月1日-15日这一时期的金额,因为在2009年8月15日之后没有数据。
Sales PMTD 使用DATEADD并筛选 Date [DateWithSales] 列,以确保与上一时间段的数据进行合理比较。Sales MOMTD 和 Sales MOMTD % 通过 Sales PMTD 度量值来确保对相同时间段进行合理比较:
Sales表中的度量值
Sales PMTD :=
[ShowValueForDates],
CALCULATE (
[Sales MTD],
CALCULATETABLE (
DATEADD ( 'Date'[Date], -1, MONTH ),
'Date'[DateWithSales] = TRUE
)
Sales表中的度量值
Sales MOMTD :=
VAR ValueCurrentPeriod = [Sales MTD]
VAR ValuePreviousPeriod = [Sales PMTD]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales MOMTD % :=
DIVIDE (
[Sales MOMTD],
[Sales PMTD]
)
期初至今与上一个完整时间段的比较
当将上一个完整时间段作为基准时,将期初至今聚合结果与上一个完整时间段进行比较是很有用的。一旦当前年初至今达到上一完整年度的100%,这意味着我们已经达到了与上一完整时间段相同的绩效,有望以更少的天数。
年初至今比上个完整年度
“年初至今比上个完整年度”是将年初至今与上一个完整年度进行比较。图2-12显示,2008年11月的 Sales YTD 几乎达到2007年全年的 Sales Amount 。 Sales YTDOPY% 体现年初至今与上个年度总额的直接比较;当该百分比为正数时,表示增长超过上一个会计年。本案例中,从2008年12月1日起实现超过上一年度的增长。
图2-12 Sales YTDOPY % 从2008年12月1日起显示为正数,表示 Sales YTD 开始大于2007年的 Sales Amount 。
“年初至今比上个年度增长”是通过 Sales YTDOPY 和 Sales YTDOPY % 度量值来计算的;分别通过 Sales YTD 度量值来计算年初至今的值,通过 Sales PYC 度量值来获得上一完整年度的销售额:
Sales表中的度量值
Sales PYC :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
PARALLELPERIOD ( 'Date'[Date], -1, YEAR )
)
Sales表中的度量值
Sales YTDOPY :=
VAR ValueCurrentPeriod = [Sales YTD]
VAR ValuePreviousPeriod = [Sales PYC]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales YTDOPY % :=
DIVIDE (
[Sales YTDOPY],
[Sales PYC]
)
也可以使用PREVIOUSYEAR来编写 Sales PYC 度量值,其执行方式类似于PARALLELPERIOD(此示例不涉及二者差异):
Sales表中的度量值
Sales PYC (2) :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
PREVIOUSYEAR ( 'Date'[Date] )
)
如果进行比较时使用的是会计年度,则必须使用PREVIOUSYEAR,因为PREVIOUSYEAR可以接受用于指定会计年度最后一天的第二个参数。参见下方图2-13中的报告,该报告按会计期对度量值进行了分割。
图2-13 Sales Fiscal YTDOPY % 将 Sales YTD 与上一会计年度的 Sales Amount 进行了比较。
报告中使用的度量值定义如下。请注意 Sales Fiscal PYC 中PREVIOUSYEAR的第二个参数:
Sales表中的度量值
Sales Fiscal PYC :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
PREVIOUSYEAR ( 'Date'[Date], "06-30" )
)
Sales表中的度量值
Sales Fiscal YTDOPY :=
VAR ValueCurrentPeriod = [Sales Fiscal YTD]
VAR ValuePreviousPeriod = [Sales Fiscal PYC]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales Fiscal YTDOPY % :=
DIVIDE (
[Sales Fiscal YTDOPY],
[Sales Fiscal PYC]
)
季初至今比上个完整季度
“季初至今比上个完整季度”是将季初至今与上一个完整季度进行比较。图2-14显示,2008年5月的 Sales QTD 超过了Q1-2008的 Sales Amount 总额。 Sales QTDOPQ% 体现季初至今与上个季度总额的直接比较;当该百分比为正数时,表示增长超过上一季度。本案例中,从2008年5月起实现超过上一季度的增长。
图2-14 Sales QTDOPQ % 从2008年5月起显示为正数,表示 Sales QTD 开始大于Q1-2008的 Sales Amount 。
“季初至今比上个季度增长”是通过 Sales QTDOPQ 和 Sales QTDOPQ % 度量值来计算的;分别通过 Sales QTD 度量值来计算季初至今的值,通过 Sales PQC 度量值来获得上一完整季度的销售额:
Sales表中的度量值
Sales PQC :=
[ShowValueForDates] && HASONEVALUE ( 'Date'[Year Quarter] ),
CALCULATE (
[Sales Amount],
PARALLELPERIOD ( 'Date'[Date], -1, QUARTER )
)
Sales表中的度量值
Sales QTDOPQ :=
VAR ValueCurrentPeriod = [Sales QTD]
VAR ValuePreviousPeriod = [Sales PQC]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales QTDOPQ % :=
DIVIDE (
[Sales QTDOPQ],
[Sales PQC]
)
也可以使用PREVIOUSQUARTER来编写 Sales PQC 度量值,只要不是用在超过一个季度的年度级别即可:
Sales表中的度量值
Sales PQC (2) :=
[ShowValueForDates] && HASONEVALUE ( 'Date'[Year Quarter] ),
CALCULATE (
[Sales Amount],
PREVIOUSQUARTER ( 'Date'[Date] )
)
月初至今比上个完整月度
“月初至今比上个完整月度”是将月初至今与上一个完整月度进行比较。图2-15显示,2008年4月期间的总 Sales MTD 超过了2008年3月的 Sales Amount 。 Sales MTDOPM% 体现月初至今与上个月度总额的直接比较;当该百分比为正数时,表示增长超过上一月度。本案例中,从2008年4月19日起实现超过上一月度的增长。
图2-15 Sales MTDOPM % 从2008年4月19日起显示为正数,表示 Sales MTD 开始大于2008年3月的 Sales Amount 。
“月初至今比上个月度增长”是通过 Sales MTDOPM % 和 Sales MTDOPM 度量值来计算的;分别通过 Sales MTD 度量值来计算月初至今的值,通过 Sales PMC 度量值来获得上一完整月度的销售额:
Sales表中的度量值
Sales PMC :=
[ShowValueForDates] && HASONEVALUE ( 'Date'[Year Month] ),
CALCULATE (
[Sales Amount],
PARALLELPERIOD ( 'Date'[Date], -1, MONTH )
)
Sales表中的度量值
Sales MTDOPM :=
VAR ValueCurrentPeriod = [Sales MTD]
VAR ValuePreviousPeriod = [Sales PMC]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales MTDOPM % :=
DIVIDE (
[Sales MTDOPM],
[Sales PMC]
)
也可以使用PREVIOUSQUARTER来编写 Sales PMC 度量值,只要不是用在超过一个月的季度或年度级别即可:
Sales表中的度量值
Sales PMC (2) :=
[ShowValueForDates] && HASONEVALUE ( 'Date'[Year Month] ),
CALCULATE (
[Sales Amount],
PREVIOUSMONTH ( 'Date'[Date] )
)
使用移动年度总计计算
聚合几个月数据的一种常用方法是使用移动年度总计,而不是年初至今。移动年度总计包括过去12个月的数据。例如,2008年3月的移动年度总计包括从2007年4月至2008年3月的数据。
移动年度总计
Sales MAT 度量值用于计算移动年度总计,如图2-16所示。
图 2-16 2008年3月的 Sales MAT 聚合了 2007年4月至2008年3月的 Sales Amount 。
移动年度总计使用DATESINPERIOD来选择上一年:
Sales表中的度量值
Sales MAT :=
[ShowValueForDates],
CALCULATE (
[Sales Amount],
DATESINPERIOD (
'Date'[Date],
MAX ( 'Date'[Date] ),
)
DATESINPERIOD返回一个日期集,该日期集包含从第二个参数开始传递的日期,同时应用在第三个参数和第四个参数中指定的偏移。例如, Sales MAT 度量值返回筛选上下文中最后有效日期之前的全年日期。通过分别给第三个参数和第四个参数赋值-12和MONTH,可以获得相同的结果。
移动年度总计增长
“移动年度总计增长”借由 Sales MAT 度量值,通过 Sales PYMAT 、 Sales MATG 和 Sales MATG % 度量值来计算。 Sales MAT 度量值提供首次销售后一年的正确值(当它收集一整年的数据时);但如果当前时间段不足一整年,则不受保护。例如,2009年全年的 Sales PYMAT 为9,927,582.99,这与2008年的 Sales Amount 相对应,如图2-17所示。将2009年销售额与2008年全年销售额进行比较,只能比较不足8个月的数据,是因为有效数据截止到2009年8月15日。类似地,您可以看到2008年开始出现的 Sales MATG % 数值非常高,一年后稳定下来。前几个数值非常高,是由于上一年没有销售。设计原理是:移动年度总计通常是以月粒度或日粒度进行计算,以便在图表中显示趋势。
图2-17 Sales MATG% 显示了 Sales MAT 和 Sales PYMAT 之间增长的百分比。
度量值的定义方法如下:
Sales表中的度量值
Sales PYMAT :=
[ShowValueForDates],
CALCULATE (
[Sales MAT],
DATEADD ( 'Date'[Date], -1, YEAR )
)
Sales表中的度量值
Sales MATG :=
VAR ValueCurrentPeriod = [Sales MAT]
VAR ValuePreviousPeriod = [Sales PYMAT]
VAR Result =
NOT ISBLANK ( ValueCurrentPeriod )
&& NOT ISBLANK ( ValuePreviousPeriod ),
ValueCurrentPeriod - ValuePreviousPeriod
RETURN
Result
Sales表中的度量值
Sales MATG % :=
DIVIDE (
[Sales MATG],
[Sales PYMAT]
)
也可以使用SAMEPERIODLASTYEAR来编写 Sales PYMAT 度量值。如下面的示例,其本质用法就是上例中的DATEADD:
Sales表中的度量值
Sales PYMAT (2) :=
[ShowValueForDates],
CALCULATE (
[Sales MAT],
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
移动平均
移动平均通常用在折线图中显示趋势。图2-18包括30天( Sales AVG 30D )、三个月( Sales AVG 3M )和一年( Sales AVG 1Y )的 Sales Amount 的移动平均。
图2-18 Sales AVG 30D 、 Sales AVG 3M 和 Sales AVG 1Y 分别显示了30天、三个月和一年的移动平均。
移动平均30天
Sales AVG 30D 度量值通过遍历借由DATESINPERIOD获得的过去30天的日期列表,来计算30天的移动平均:
Sales表中的度量值
Sales AVG 30D :=
VAR Period30D =
CALCULATETABLE (
DATESINPERIOD (
'Date'[Date],
MAX ( 'Date'[Date] ),
'Date'[DateWithSales] = TRUE
VAR FirstDayWithData =
CALCULATE (
MIN ( Sales[Order Date] ),
REMOVEFILTERS ()
VAR FirstDayInPeriod =
MINX ( Period30D, 'Date'[Date] )
VAR Result =
FirstDayWithData <= FirstDayInPeriod,
AVERAGEX (
Period30D,
[Sales Amount]
RETURN
Result
这种模式非常灵活。但是对于常规的累加计算,可以使用另一个更快的公式来实现 Result :
VAR Result =
FirstDayWithData <= FirstDayInPeriod,
CALCULATE (
DIVIDE (
[Sales Amount],
DISTINCTCOUNT ( Sales[Order Date] )
Period30D
)
移动平均3个月
Sales AVG 3M 度量值通过遍历借由DATESINPERIOD获得的过去3个月的日期列表,来计算3个月的移动平均:
Sales表中的度量值
Sales AVG 3M :=
VAR Period3M =
CALCULATETABLE (
DATESINPERIOD (
'Date'[Date],
MAX ( 'Date'[Date] ),
MONTH
'Date'[DateWithSales] = TRUE
VAR FirstDayWithData =
CALCULATE (
MIN ( Sales[Order Date] ),
REMOVEFILTERS ()
VAR FirstDayInPeriod =
MINX ( Period3M, 'Date'[Date] )
VAR Result =
FirstDayWithData <= FirstDayInPeriod,
AVERAGEX (
Period3M,
[Sales Amount]
RETURN
Result
对于简单的累加度量值,在“移动平均30天”中介绍的基于DIVIDE的模式,也可以用于计算3个月的移动平均。
移动 平均1年
Sales AVG 1Y 度量值通过遍历借由DATESINPERIOD获得的过去1年的日期列表,来计算1年的移动平均:
Sales表中的度量值
Sales AVG 1Y :=
VAR Period1Y =
CALCULATETABLE (
DATESINPERIOD (
'Date'[Date],
MAX ( 'Date'[Date] ),
'Date'[DateWithSales] = TRUE
VAR FirstDayWithData =
CALCULATE (
MIN ( Sales[Order Date] ),
REMOVEFILTERS ()
VAR FirstDayInPeriod =
MINX ( Period1Y, 'Date'[Date] )
VAR Result =
FirstDayWithData <= FirstDayInPeriod,
AVERAGEX (
Period1Y,
[Sales Amount]
RETURN
Result
对于简单的累加度量值,在“移动平均30天”中介绍的基于DIVIDE的模式,也可以用于计算1年的移动平均。
筛选其他日期属性
一旦将 Date 表标记为日期表,每当CALCULATE 筛选 Date 表的日期列时,DAX会自动删除 Date 表中的所有筛选器。设计这样的执行方式,其目的是简化时间智能计算的编写。的确,如果DAX不删除筛选器,则每次使用DAX时间智能函数时,需要在 Date 表上手动添加REMOVEFILTERS,这会带来负面的开发体验。
自动删除 Date 表中的筛选器可能会给某些特定报告带来问题。例如,如果一份报告计算年初至今销售情况的方式是按星期几对销售额进行切分,则仅通过使用时间智能函数DATESYTD获得的结果会是错误的。图2-19显示了一周中所有日期的值,可以看出,一周中每一天的 Sales YTD 结果略小于或等于行总计。
图2-19 按星期几切分的度量值 Sales YTD 产生了不正确的结果。
值不正确的原因是DATESYTD在 Date[Date] 列上应用了一个筛选器。因为 Date 被标记为日期表,所以DAX会自动将 REMOVEFILTERS('Date') 修饰符应用于筛选器参数中使用DATESYTD的同一CALCULATE,从而删除星期几上的筛选器。因此,显示的数字是年初至今,与星期几上的任何筛选器无关。星期几筛选器仅影响报告行中指定的时间段(年或季度)的最后一天。正确的结果(如图20所示)需要使用另一种方法。
图2-20 按星期几切分的 Sales YTD (day of week) 产生了正确的结果。
有两个方法可以获取正确的值:要么以CALCULATE语句重新遍历星期几上的筛选器,要么更新数据模型。
仅在对列进行筛选的情况下,恢复星期几上的筛选器需要添加 VALUES(Date[Day of Week]) ,如下方代码:
Sales表中的度量值
Sales YTD (day of week) :=
[ShowValueForDates],
ISFILTERED ( 'Date'[Day of Week] ),
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date] ),
VALUES ( 'Date'[Day of Week] )
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date] )