第4章了解评估上下文
4.1 介绍评估上下文
到此,您已经学习了DAX语言的基础知识。您知道如何创建计算列和度量值,并且对DAX中使用的常用函数有很好的了解。在本章中,您将使用该语言进入更高的层次:在学习DAX语言的扎实理论背景之后,您将成为真正的DAX拥护者。
利用到目前为止所获得的知识,您已经可以创建许多有趣的报告,但是您需要学习评估上下文才能创建更复杂的公式。实际上,评估环境是DAX所有高级函数的基础。
我们想给读者一些警告。评估上下文的概念很简单,您将很快学会和理解它。但是,您需要彻底理解一些细微的注意事项和细节。否则,您将在DAX学习道路上的某个时刻感到迷失。我们一直在向公共和私人班级中的数千名用户教授DAX,因此我们知道这很正常。在某些时候,您会感觉公式的作用像魔术一样,因为是有效的,但是您不理解为什么。不用担心:不止你是这样。大多数DAX学生都曾经这样,将来很多人也会这样。这仅表示他们对评估上下文还不够清楚。那时的解决方案很简单:回到本章,再读一遍,
此外,在使用 CALCULATE 函数时,评估上下文也起着重要的作用,CALCULATE 函数可能是功能最强大且最难学习的DAX函数。我们将在第5章“了解 CALCULATE 和 CALCULATETABLE”中介绍 CALCULATE ,然后在本书的其余部分中使用它。在没有扎实了解评估上下文的情况下理解 CALCULATE 是有问题的。另一方面,不尝试使用 CALCULATE 来了解评估上下文的重要性几乎没有可能。因此,根据我们以前写过的书的经验,本章和后一章总是被标记成两部分,并且页面的角被折叠。
在本书的其余部分中,我们将使用这些概念。然后,在第14章“高级DAX概念”中,您将通过扩展表来完成评估上下文的学习。请注意,本章的内容还不是对评估上下文的明确描述。对评估上下文的更详细描述是基于扩展表的描述,但是在对评估上下文的基础有很好的了解之前,很难了解扩展表。因此,我们以不同的步骤介绍整个理论。
介绍评估上下文
有两个评估上下文:筛选上下文和行上下文。在下一部分中,您将学习它们是什么以及如何使用它们编写DAX代码。在了解它们是什么之前,重要的是要指出一点:它们是不同的概念,具有不同的功能和完全不同的用法。
DAX新手最常犯的错误是混淆两个上下文,就好像行上下文是筛选器上下文的微小变化一样。不是这种情况。筛选上下文筛选数据,而行上下文则迭代表。DAX进行迭代时,不会进行筛选;并且当它进行筛选时,它不会迭代。即使这是一个简单的概念,我们也从经验中知道很难在脑海中留下印记。我们的大脑似乎更喜欢一条短途学习之路——当它认为存在某些相似之处时,它将两个概念合并为一体来使用它们。别被骗了。每当您感觉到两个评估上下文看起来相同时,就在心中停下来重复这句话,就像咒语:“筛选上下文筛选,行上下文迭代,它们是不相同的。”
评估上下文是评估DAX表达式的上下文。实际上,任何DAX表达式都可以在不同的上下文中提供不同的值。这种行为很直观,这就是为什么无需事先了解评估上下文就可以编写DAX代码的原因。您可能已经在编写DAX代码的书中达到了这一点,而没有了解评估上下文。因为您需要更多,所以现在是时候变得更加精确了,以正确的方式建立DAX的基础,并准备好发挥DAX的全部功能。
了解筛选上下文
让我们首先了解什么是评估上下文。所有DAX表达式都在上下文中求值。上下文是评估公式的求值“环境”。例如,思考下列度量值:
Sales Amount := SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
此公式计算 Sales 表中数量(Sales[Quantity])乘以价格(Sales[Net Price])的总和。我们可以在报告中使用此度量并查看结果,如图4-1所示。
仅这个数字看起来并不有趣。但是,如果您仔细考虑,该公式将精确计算出一个预期值:所有销售额(Sales Amount)的总和。在真实的报告中,很可能会按某列来切分数据。例如,我们可以选择产品品牌(Brand),在行上使用它,然后矩阵报表开始显示有趣的业务见解,如图4-2所示。
总计仍然存在,但现在是较小值的总和。每个值以及所有其他值都提供了更详细的见解。但是,您应该注意,正在发生一些奇怪的事情:该公式并未计算出我们显然要求的内容。实际上,在报表的每个单元格中,公式不再计算所有销售额的总和。相反,它计算给定品牌的销售额。最后,请注意,代码中没有任何地方说它可以(或应该)对数据子集起作用。此筛选发生在公式之外。
由于DAX执行公式的评估上下文,每个单元格计算的值都不同。您可以将公式的评估上下文视为DAX评估公式的单元格的周围区域。
DAX在相应的上下文中评估所有公式。即使公式相同,结果也有所不同,因为DAX针对不同的数据子集执行相同的代码。
该上下文称为“筛选上下文”,顾名思义,它是筛选表上下文。曾经编写的任何公式都将具有不同的值,具体取决于用于执行其评估的筛选上下文。这种行为虽然直观,但由于隐藏了许多复杂性,因此需要很好地理解。
报告的每个单元格都有不同的筛选上下文。您应该考虑到每个单元格都有不同的评估——就像是一个不同的查询一样,独立于同一报表中的其他单元格。引擎可能会执行某种程度的内部优化以提高计算速度,但是您应该假设每个单元格都对基础DAX表达式进行了独立且自主的评估。因此,图4-2中“总计(Total)”行的计算不是通过对报告的其他行求和来进行的。它是通过汇总Sales表的所有行来计算的,尽管这意味着已经为同一报表中的其他行计算了其他迭代。因此,依据DAX表达式,“总计”行中的结果可能显示不同的结果,该结果与同一报表中的其他行无关。
注意
在这些示例中,为简单起见,我们使用矩阵。我们也可以使用查询定义评估上下文,您将在以后的章节中进一步了解它。就目前而言,最好保持简单,只考虑报告,以简化和直观地理解概念。
当品牌在行上时,筛选上下文为每个单元格筛选一个品牌。如果通过在列上添加年份来增加矩阵的复杂性,则会获得图4-3中的报告。
现在,每个单元格都显示与一个品牌和一年有关的数据子集。这样做的原因是,每个单元格的筛选上下文现在都筛选了品牌和年份。在“总计”行中,筛选仅在品牌上,而在“总计”列中,筛选仅在年份上。总计是唯一一个计算所有销售额之和的单元格,因为筛选条件没有对模型应用任何筛选条件。 此时的游戏规则应该很清楚:我们用于切片和切块的列越多,矩阵每个单元格中的筛选上下文将筛选出更多的列。如果将 Store [Continent] 列添加到行中,结果将再次有所不同,如图4-4所示。
现在,每个单元格的筛选上下文是筛选品牌,国家和年份。换句话说,筛选上下文包含一整套用于报表行和列的字段。
注意
字段是在视觉的行还是列上,还是在切片器和/或页面/报表/视觉对象的筛选上,还是在我们可以使用报表创建的任何其他类型的筛选中,所有这些都是不相关的。所有这些筛选都有助于定义单个筛选上下文,DAX使用该上下文来评估公式。在行或列上显示字段对于美学目的很有用,但是DAX计算值的方式没有变化。
Power BI中的视觉交互通过组合图形界面中的不同元素来构成筛选上下文。实际上,单元格的筛选上下文是通过将来自行,列,切片器以及用于筛选的任何其他视觉对象的所有筛选合并在一起来计算的。例如,查看图4-5。
左上方单元格的筛选上下文(A.Datum,CY 2007,57,276.00)不仅筛选视觉对象的行和列,还筛选即将到来的职业(专业)和大陆(欧洲),筛选上下文来自各种视觉对象。所有这些筛选都有助于定义对一个单元格有效的单个筛选上下文,DAX在评估公式之前将其应用于整个数据模型。
筛选上下文的更正式定义是说筛选上下文是一组筛选。筛选又是元组的列表,元组是一些已定义列的一组值。图4-6显示了筛选上下文的直观表示,在该上下文中评估突出显示的单元格。报告的每个元素都有助于创建筛选上下文,并且报告中的每个单元格都有不同的筛选上下文。 该图使用符号,报告和箭头来描述筛选上下文。
图4-6的筛选上下文包含三个筛选。第一个筛选包含一个日历年的元组,值CY2007。第二个筛选包含两个用于教育的元组,值“ High School”和“ Partial College”。第三个筛选包含Brand的单个元组,值Contoso。您可能会注意到,每个筛选仅包含一列的元组。稍后,您将学习如何创建具有多列的元组。多列元组是DAX开发人员手中既强大又复杂的工具。
在结束本介绍之前,让我们回顾一下本节开始时使用的度量值:
Sales Amount := SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
这是读取先前度量值的正确方法:对于当前筛选上下文中可见的 Sales 中所有行,该度量值计算“数量”乘以“净价”之和。
这同样适用于更简单的聚合。例如,考虑以下度量值:
Total Quantity := SUM ( Sales[Quantity] )
它将 Sales 表中所有当前筛选上下文中可见的行的“数量([Quantity])”列求和。通过考虑相应的SUMX版本,您可以更好地了解其工作原理:
Total Quantity := SUMX ( Sales, Sales[Quantity] )
查看SUMX定义,我们可能认为筛选上下文会影响Sales表达式的求值,该表达式仅返回在当前筛选上下文中可见的Sales表的行。的确如此,但是您应该考虑筛选上下文也适用于以下度量值,这些度量值没有相应的迭代器:
Customers := DISTINCTCOUNT ( Sales[CustomerKey] ) -- Count customers in filter context
Colors :=
VAR ListColors = DISTINCT ( 'Product'[Color] ) -- Unique colors in filter context
RETURN COUNTROWS ( ListColors ) -- Count unique colors
在这一点上,花费大量时间强调筛选上下文始终处于活动状态并且会影响公式结果的概念可能看起来很花哨。但是,请记住,DAX要求您非常精确。DAX的大部分复杂性都不在于学习新功能。相反,复杂性来自许多细微概念的存在。当这些概念混合在一起时,就会出现一个复杂的场景。现在,筛选上下文由报告定义。一旦您了解了如何自己创建筛选上下文(下一章中将介绍的一项关键技能),那么了解公式的每个部分中哪个筛选上下文处于活动状态就至关重要。
了解行上下文
在上一节中,您了解了筛选上下文。在本节中,您现在将学习第二种类型的评估上下文:行上下文。请记住,尽管行上下文和筛选上下文都是评估上下文,但是它们不是同一概念。正如您在上一节中所学到的,顾名思义,筛选上下文的目的是筛选表。另一方面,行上下文不是筛选表的工具。相反,它用于遍历表和评估列值。
这次我们使用不同的公式进行考虑,定义了一个计算列以计算毛利率:
Sales[Gross Margin] = Sales[Quantity] * ( Sales[Net Price] - Sales[Unit Cost] )
所得的计算列中的每一行都有一个不同的值,如图4-7所示。
不出所料,表的每一行在计算列中都有一个不同的值。实际上,由于表达式中使用的三列的每一行都有给定的值,因此,最终表达式计算出不同的值是很自然的结果。与筛选上下文一样,原因是存在评估上下文。这次,上下文不筛选表。相反,它标识要进行计算的行。
注意
行上下文引用DAX表表达式的结果中的一行。请勿将其与报告中的一行混淆。DAX无法直接引用报告中的行或列。在Power BI中的矩阵中以及在Excel中的数据透视表中显示的值是在筛选上下文中DAX度量值的计算结果、存储在表中的值或计算列的值。
换句话说,我们知道已计算列是逐行计算的,但是DAX如何知道它当前在迭代哪一行?它知道该行,因为有另一个评估上下文提供行——-它就是行上下文。当我们在具有一百万行的表上创建一个计算列时,DAX将创建一个行上下文,该行上下文使用该行上下文作为游标,对在表上逐行迭代的表达式求值。
当我们创建计算列时,默认情况下DAX将创建行上下文。在这种情况下,无需手动创建行上下文:计算列始终在行上下文中执行。您已经学习了如何通过开始迭代手动创建行上下文。实际上,可以将毛利率写为度量值,如以下代码所示:
Gross Margin :=
SUMX (
Sales,
Sales[Quantity] * ( Sales[Net Price] - Sales[Unit Cost] )
)
在这种情况下,因为该代码用于度量值,所以没有自动行上下文。SUMX是一个迭代函数,它创建一个行上下文,该行上下文开始逐行遍历Sales表。在迭代过程中,它在行上下文内执行SUMX的第二个表达式。因此,在迭代的每个步骤中,DAX都知道要对表达式中使用的三个列名称使用哪个值。
当我们创建计算列或在迭代内计算表达式时,将存在行上下文。没有其他方法可以创建行上下文。此外,当我们想要获取特定行的列的值时,要一个行上下文是有帮助的。例如,以下度量值定义无效。实际上,它尝试计算 Sales [Net Price] 的值,并且没有行上下文提供需要对其执行计算的行:
Gross Margin := Sales[Quantity] * ( Sales[Net Price] - Sales[Unit Cost] )
当对计算列执行该表达式时,该表达式有效;如果用于度量,则该表达式无效。原因不是度量值和计算列具有使用DAX的不同方式。原因是所计算列具有自动行上下文,而度量值则没有。如果要在度量内逐行评估表达式,则需要开始迭代以创建行上下文。
注意
列引用需要一个行上下文才能从表中返回该列的值。列引用还可以用作几个没有行上下文的DAX函数的参数。例如,DISTINCT和DISTINCTCOUNT可以将列引用作为参数,而无需定义行上下文。但是,DAX表达式中的列引用需要评估行上下文。
在这一点上,我们需要重复一个重要的概念:行上下文不是筛选一行的一种特殊的筛选上下文。行上下文不会以任何方式筛选模型。行上下文仅向DAX指示要在表中使用哪一行。如果要对模型应用筛选,则使用的工具是筛选上下文。另一方面,如果用户想逐行评估表达式,则行上下文将完成这项工作。