优化互斥计算
本内容基于SQLBI官方文献整理的简体笔记 -- Power 零售 BI
本文介绍如何使用优化:可能会导致查询性能下降的互斥(相互排斥)计算的DAX表达式 。
在以前的文章中,我们讨论了变量的重要性以及如何运用其优化IF函数模式,以减少对同一表达式或度量的多次计算。 但是,有些情况下,在同一表达式的不同定义分支中(比如由IF定义的多个条件结果)执行的计算似乎很难被优化。 例如,请考虑以下模式:
Amount := IF (<Condition>, [Credit], [Debit])-- 如果符合条件,则计算[Credit],否则计算[Debit]。
这种涉及到度量A和B的情况,似乎没有任何可能的优化。 然而,通过考虑两个度量A和B各自的性质:它们可能是基于某个相同基本度量的计算,只不过针对该度量定义了不同的筛选条件参数而产生不同的计算。 例如,度量A和B可以定义为:
Raw Amount :=SUM ( Transactions[Line Amount] )
Credit := CALCULATE ( [Raw Amount], Transactions[Type] = "Credit")
Debit : = CALCULATE ( [Raw Amount], Transactions[Type] = "Debit")
在这种情况下,DAX可能会生成一个查询计划,其中这两种新度量均在内部进行计算,即使在报表中实际上仅显示其中的某一个度量(基于同一度量定义的不同度量)。例如,通过应用筛选来确定对IF条件的持续计算声明。
当然,在一般的简单表达式中,DAX可能会应用short-circuit --直接短路式计算,从而跳过不必要的任何分支的计算。 但是,在复杂报表中,为了生成批量计算的高效查询计划,此优化通常不可用。
那么,怎样才能优化相类似的这种表达呢?
我们知道,编写DAX代码时的一个经验法则是:
先定义筛选器以筛选出计算列表,然后执行CALCULATE计算,这比针对不同的条件而编写不同的CALCULATE语句要更方便。 因此,以前的代码可以写成如下:
Raw Amount :=SUM ( Transactions[Line Amount] )
Amount := VAR TransactionType = IF ( <condition>,"Credit", "Debit" )
RETURN
CALCULATE ( [Raw Amount], Transactions[Type] = TransactionType )
虽然,在这样一个简单的例子中,这种编码可能不会转化为性能优势,但在一些复杂表达中,该方法可能会更快。 可以通过测试两种方法在特定用例上的性能来验证。
但是,这种技术会带来两个额外的挑战:
(1)使用不同变量在多个步骤中分割计算会更困难;
(2)使用条件语句创建适当的过滤器可能更复杂。
展示这种优化的好处的一个常见例子是:显示基于切片器选择的时间智能度量的计算:比如定义一个元度量[Sales Amount],以及基于该度量添加一个或多个步骤、条件之后的新度量[Smart Sales]。
原始[Sales Amount]度量和智能[Smart Sales]度量分别定义如下:
Sales Amount :=
SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
Smart Sales := IF ( HASONEVALUE ( Period[Period] ),
SWITCH ( VALUES ( Period[Period] ),
"Last week",
CALCULATE ([Sales Amount],
DATESINPERIOD ( 'Date'[Date], MAX ( 'Date'[Date] ),
VALUES ( Period[Offset] ),DAY ) ),
"Last 4 weeks",
CALCULATE ( [Sales Amount],
DATESINPERIOD ( 'Date'[Date],MAX ( 'Date'[Date] ),
VALUES ( Period[Offset] ), DAY ) ),
"Last quarter",
CALCULATE ( [Sales Amount],
DATESINPERIOD ( 'Date'[Date],MAX ( 'Date'[Date] ),
VALUES ( Period[Offset] ),DAY )),
"MTD", CALCULATE ( [Sales Amount], DATESMTD ( 'Date'[Date] ) ),
"QTD", CALCULATE ( [Sales Amount], DATESQTD ( 'Date'[Date] ) ),
"YTD", CALCULATE ( [Sales Amount], DATESYTD ( 'Date'[Date] ) ), BLANK() ))
该公式中 Swith的多个结果引用的是同一CaⅠculate条件。 Period--周期表包含每个时间段的period列的period type—周期类型的定义。 在这一列中,“D”表示从所选时间段的最后一天开始的日期范围,“I”表示必须根据时间段名称(MTD, QTD,YTD)在特定时间智能函数中转换的时间段。这里唯一可见的列是Period(其他参数列为隐式),它是上图切片器中使用的列:
通过定义变量作为Date表的筛选器,你可以只使用单个CALCULATE来重写[Smart Sales]--智能销售度量。 试想,倘若能够使用SWITCH函数来创建该筛器表达式,这当然不失为一个好办法,但遗憾的是:IF和SWITCH都返回标量值而不是表格。 因此不可能编写下面的语法:
VAR FilterDates = (前面[Smart Sales]度量公式中,从SWITCH开始的部分)
由于IF与SWITCH无法返回表,因此,可以使用它们定义计算所需的日期范围,并将该时期范围筛选传递给单个DATESBETWEEN函数。 一种可能的实现方式是以下度量:
简体注:该公式是使用变量来定义筛选器书写DAX步骤的又一个案例。其中变量FirstDaySelected需要具备一定的DAX内部引擎知识。
[Smart Sales New]度量可能有更好的性能。当然,构建DAX筛选器需要额外的成本,但这可能比定义不同的CALCULATE语句更有效。在本文所示的包含不同[Smart Sales]度量的小样本数据文件中,这种差异并不明显。然而,这种技术在更大的数据库中会产生较大的影响,因为这种类型的数据库不容易复制和下载。
像往常一样,了解DAX中解决问题的不同方法总是一个好习惯。实际上,性能可能会因需求的具体细节和数据分发的不同而有所不同。
当出现性能问题时,在没有可用的特殊方式(如考虑索引或聚合优化)来解决问题时。 有必要通过更改DAX代码以获得更好的执行计划。
网友评论