掌握CALCULATE必读。
DAX是PowerPivot和Analysis Services在表格模式下使用的新语言,它类似于Excel公式的语法,它可以被认为是一种功能语言。虽然没有迭代语句,但却可以运行迭代函数,例如SUMX和FILTER。DAX中最重要的函数是CALCULATE和CALCULATETABLE,这些函数也是最复杂的,因为它们内部做了很多操作。为了充分了解它们,您还必须很好地了解计算上下文(行上下文和筛选上下文)。
一般的流程是,这些函数将行上下文(如果存在)转换为筛选上下文,并自动传播到相关表,然后根据第一个参数之后传递的参数逐一修改筛选上下文,最后在修改过的筛选上下文中计算表达式的第一个参数。
如果您仔细阅读前面的描述,您将发现一个并不总是直观的行为,这将成为使用DAX时成为混乱的根源。函数参数的顺序通常与参数的顺序相同:第一个参数先被计算,然后才是第二个参数,接着是第三个参数,依此类推。对于大多数DAX函数来说,这种情况总是如此,而CALCULATE和CALCULATETABLE则不同。在这些函数中,只有在计算了所有其他参数之后,才会计算第一个参数。如果你有C#背景,你可以将第一个参数理解为一个C#回调函数来考虑,只有稍后才会被调用。
因此,当你写:
CALCULATE(
[Measure],
Customer[Country] = "Italy" //FILTER语句
)
首先执行FILTER语句,然后在筛选上下文中计算[Measure]
,其中可见的客户只有来自意大利的客户(假设意大利在公式的调用者的筛选上下文中)。
这似乎很直观,但是当你嵌套CALCULATE语句时,事情会更加困难。请考虑以下示例:
CALCULATE(
CALCULATE(
[Measure],
Customer[Country] = "Italy"
),
ALL( Customer[Country] )
)
在这种情况下,ALL( Customer[Country] )
在内部CALCULATE语句之前执行,因此筛选上下文删除Customer表的Country列上现有的任何筛选器,然后在该筛选上下文中筛选出意大利。从功能的角度来看,与以前的CALCULATE公式的唯一区别是,意大利将是计算[Measure]
中唯一选择的国家,不管在调用者的筛选上下文中是否存在除国家/地区以外的任何筛选器。
现在考虑另一个例子:
CALCULATE(
CALCULATE(
[Measure],
ALL( Customer[Country] )
),
Customer[Country] = "Italy"
)
首先执行位于外层CALCULATE的意大利筛选器,然后ALL( Customer[Country] )
删除外部筛选器的任何影响,导致将在新的已删除任何筛选器的筛选上下文中计算[Measure]
。
以下示例计算2012年以前有过购买行为的意大利客户的数量。首先执行外部意大利筛选器,并将其效果应用于在外部CALCULATE的表达式中执行的FILTER函数。在每个客户上迭代执行内部CALCULATE,并返回在2012年之前针对该客户的销售额。
CALCULATE(
COUNTROWS(
FILTER(
Customer,
CALCULATE(
SUM( Sales[Amount] ),
YEAR( Sales[Date] ) < 2012
) > 0
)
),
Customer[Country] = "Italy"
)
在这一点上可能出现的一个错误是假设在计算顺序中发生反转,而CALCULATE的所有筛选器参数彼此独立地执行。在下一个表达式中,结果是相同的(意大利客户在2012年之前买了一些东西),但是这里的FILTER对所有客户进行迭代,而不仅仅是意大利客户,因为它与意大利的筛选器并行执行。
CALCULATE(
COUNTROWS( Customer ),
FILTER(
Customer,
CALCULATE(
SUM( Sales[Amount] ),
YEAR( Sales[Date] ) < 2012
) > 0
),
Customer[Country] = "Italy"
)
通过使用嵌套的CALCULATE,我们强制在内部筛选器之前执行了Customer[Country] = "Italy"
筛选器,然后将该过滤器应用于FILTER语句,该语句仅计算意大利客户的销售量。在这种情况下,结果将是一样的,但是您可能会观察到两种解决方案之间的不同性能(下一个嵌套的CALCULATE比先前的独立筛选器更快),因为我们用不同的语法实现了不同的算法(即使结果将是一样的)。
CALCULATE(
CALCULATE(
COUNTROWS( Customer ),
FILTER(
Customer,
CALCULATE(
SUM( Sales[Amount] ),
YEAR( Sales[Date] ) < 2012
) > 0
)
),
Customer[Country] = "Italy"
)
结论是执行CALCULATE和CALCULATETABLE参数的顺序与其他DAX函数不同,并要求正确地了解筛选器对完整表达式计算的影响。