在筛选上下文中使用DISTINCT和SUMMARIZE
既然您对评估上下文有深入的了解,我们就可以使用这些知识逐步解决实际问题。同时,我们提供了一些细节的分析,希望能对行上下文和筛选上下文的基本概念有更多的了解。此外,在此示例中,我们还进一步阐述了SUMMARIZE函数,在第3章“使用基本表函数”中作了简要介绍。
在进入更多细节之前,请注意,基于培训的目的,此示例在得出正确的解决方案之前显示了一些不准确的计算。因为我们想教导编写DAX代码的过程,而不是给出解决方案。在制定度量值的过程中,您可能会犯几个初始错误。在此指导示例中,我们描述了正确的推理方法,可帮助您自己解决类似的错误。
要求是计算Contoso客户的平均年龄。即使这看起来是合理的要求,也并不完整。我们是在谈论他们的当前年龄还是在购买时的年龄?如果客户购买了3次,那么平均应算作一次事件还是三次事件?如果他们在不同年龄段购买了三次,该怎么办?我们需要更加精确。这是更完整的要求:“计算产生销售时客户的平均年龄,如果每个客户在同一年龄进行多次购买,则仅计算一次。”
该解决方案可以分为两个步骤:
- 计算购买发生时的客户年龄
- 平均化
每次销售时客户的年龄都会改变。因此,年龄需要存储在Sales表中。对于Sales中的每一行,可以计算产生销售时的客户年龄。计算列非常适合此需求:
Sales[Customer Age] =
DATEDIFF ( -- Compute the difference between
RELATED ( Customer[Birth Date] ), -- the customer's birth date
Sales[Order Date], -- and the date of the sale
YEAR -- in years
)
由于Customer Age是计算列,因此将在迭代Sales的行上下文中对其进行评估。该公式需要访问与客户关系一侧的Customer[Birth Date](客户生日),这是Customer(客户)表中的一列。在这种情况下,需要RELATED才能让DAX访问目标表。在示例数据库Contoso中,许多客户的生日为空。如果第一个参数为空,则DATEDIFF返回值为空。
因为要求提供平均值,所以第一个且不准确的解决方案可能是对该列求平均值的度量值:
Avg Customer Age Wrong := AVERAGE ( Sales[Customer Age] )
结果不正确,因为如果客户在某个年龄进行多次购买,则 Sales [Customer Age] 将包含多个具有相同年龄的行。要求仅计算每个客户一次,此公式未遵循这样的要求。图4-25并排显示了最后一个度量值的结果和预期结果。
图4-25 一个简单的平均值计算得出的客户年龄的错误结果这是问题所在:每个客户的年龄只能计算一次。一个可能的解决方案(仍然不准确)将是通过以下措施对客户年龄进行平均,然后取平均值:
Avg Customer Age Wrong Distinct :=
AVERAGEX ( -- Iterate on the distinct values of
DISTINCT ( Sales[Customer Age] ), -- Sales[Customer Age] and compute the
Sales[Customer Age] -- average of the customer's age
)
此解决方案不是正确的解决方案。实际上,DISTINCT 返回客户年龄的不同值。此公式仅计算一次两个具有相同年龄的客户。要求是对每个客户计数一次,而此公式对每个年龄计数一次。实际上,图4-26显示了具有“平均客户年龄”新公式的报告。您会看到此解决方案仍然不准确。
图4-26 不同客户年龄的平均值仍然是错误的在最后一个公式中,可能尝试用CustomerKey作为DISTINCT的参数替换Customer Age,如以下代码所示:
Avg Customer Age Invalid Syntax :=
AVERAGEX ( -- Iterate on the distinct values of
DISTINCT ( Sales[CustomerKey] ), -- Sales[CustomerKey] and compute the
Sales[Customer Age] -- average of the customer's age
)
此代码包含错误,DAX不会接受。您可以在不阅读下一段中提供的解决方案的情况下找出原因吗?
AVERAGEX生成对表进行迭代的行上下文。作为AVERAGEX的第一个参数提供的表是DISTINCT(Sales [CustomerKey])。DISTINCT返回仅包含一列的表以及客户键的所有唯一值。因此,由AVERAGEX生成的行上下文仅包含一列,即Sales [CustomerKey]。DAX无法在仅包含Sales [CustomerKey]的行上下文中评估Sales [客户年龄]。
所需要的是行上下文,其粒度为Sales [CustomerKey],但还包含Sales [Customer Age]。第3章介绍的SUMMARIZE可以生成两列的现有唯一组合。现在,我们终于可以显示实现所有要求的此代码的版本:
Correct Average :=
AVERAGEX ( -- Iterate on
SUMMARIZE ( -- all the existing combinations
Sales, -- that exist in Sales
Sales[CustomerKey], -- of the customer key and
Sales[Customer Age] -- the customer age
), --
Sales[Customer Age] -- and average the customer's age
)
像往常一样,可以使用变量将计算分为多个步骤。请注意,访问Customer Age列仍需要在AVERAGEX函数的第二个参数中引用Sales表名称。变量可以包含表,但不能用作表引用。
Correct Average :=
VAR CustomersAge =
SUMMARIZE ( -- Existing combinations
Sales, -- that exist in Sales
Sales[CustomerKey], -- of the customer key and
Sales[Customer Age] -- the customer age
)
RETURN
AVERAGEX ( -- Iterate on list of
CustomersAge, -- Customers/age in Sales
Sales[Customer Age] -- and average the customer's age
)
SUMMARIZE生成当前筛选上下文中可用的客户和年龄的所有组合。因此,具有相同年龄的多个客户将复制该年龄,每个客户一次。AVERAGEX忽略表中存在CustomerKey的情况;它仅使用客户年龄。CustomerKey*只需用来计算每个年龄段的正确出现次数。
值得强调的是,完整的度量值在报表生成的筛选上下文中执行。因此,SUMMARIZE只会评估和返回购买商品的客户。报告的每个单元格都有不同的筛选上下文,度量值仅考虑购买了至少一种对应报告中显示颜色产品的客户。
结论
现在该回顾一下您在本章中学到的有关评估上下文的最相关主题。
- 有两个评估上下文:筛选上下文和行上下文。这两个评估上下文不是同一概念的变体:筛选上下文过滤模型;行上下文迭代一个表。
- 要了解公式的行为,您总是需要考虑两个评估上下文,因为它们同时运行。
- DAX为计算列自动创建行上下文。也可以使用迭代函数以编程方式创建行上下文。每个迭代函数都定义一个行上下文。
- 您可以嵌套行上下文,如果它们位于同一表上,则最里面的行上下文将隐藏同一表上的先前行上下文。当访问所需的行上下文时,变量可用于存储检索到的值。在DAX的早期版本中,变量不可用,而EARLIER函数用于访问前一行上下文。从今天起,不鼓励使用EARLIER。
- 当遍历表表达式返回的表时,行上下文仅包含表表达式返回的列。
- 当您在行、列、切片器和筛选上使用字段时,Power BI等客户端工具会创建筛选上下文。也可以使用CALCULATE以编程方式创建筛选上下文,我们将在下一章中介绍它。
- 行上下文不会自动通过关系传播。需要使用RELATED和RELATEDTABLE强制传播。您需要在一对多关系正确一侧的行上下文中使用这些函数:在“多”侧使用RELATED,在“一”侧使用RELATEDTABLE。
- 筛选上下文过滤模型,并根据其交叉筛选方向使用关系。它总是从一侧传播到另一侧。此外,如果您使用双向交叉筛选方向,则传播也会从多侧向一侧发生。
至此,您已经学习了DAX语言最复杂的概念主题。这些要点支配您公式的所有评估流程,它们是DAX语言的支柱。每当遇到无法计算所需内容的表达式时,很有可能是因为您没有完全理解这些规则。
正如我们在导言中所说,乍一看,所有这些主题看起来都很简单。实际上也简单。使它们变得复杂的原因是,在DAX表达式中,您可能在公式的不同部分具有多个评估上下文。掌握评估上下文是您要通过经验获得的技能,我们将在下一章中通过显示许多示例来帮助您。编写了自己的一些DAX公式后,您将直观地知道使用了哪些上下文以及它们需要哪些函数,最终您将掌握DAX语言。
网友评论