Power BI 中的行上下文

2024年9月20日 | 阅读时长 10 分钟

行上下文对于处理计算列和度量值至关重要,并且是 DAX 中最基本的评估上下文。忽略行上下文的细微差别可能导致计算不准确和报表不确定。在本文中,我们将深入探讨行上下文,查看其细节,并提供真实世界的实例来说明其重要性。

什么是行上下文?

当表正在被扫描时,当前正在评估的行被称为行上下文。当我们有一个当前行并且在表扫描过程中知道我们的位置时,我们就说我们有一个行上下文。观察一些代码示例可以更轻松地理解行上下文。

计算列中的行上下文

DAX 中的计算列默认始终具有行上下文。这意味着,当您创建计算列时,DAX 会知道为计算考虑哪一行,因为它是一行一行地计算该列的。

Row Context in Power BI

假设您的 Power BI 模型中有一个销售表,其中包含 CustomerFirstName、CustomerLastName、ItemSold、NetProfit、ProductName、Quantity 和 Unit Cost 等列。如果您想创建一个新列来计算 SalesAmount(Quantity*Unit Cost),则可以创建如下计算列。

由于乘法是逐行计算的,因此当您创建此计算列时,DAX 会知道要考虑哪一行。换句话说,计算列默认具有行上下文,DAX 可以使用每行的值来执行计算。

如果您在报表中可视化此计算列,您将看到每行的准确 SalesAmount。

Row Context in Power BI

度量值中的行上下文

与计算列不同,DAX 中的度量值默认不具有行上下文。度量值是在报表级别计算的,而不是逐行计算的。如果您使用与上面计算列相同的表达式,将会发生错误。


Row Context in Power BI

错误消息显示:“无法确定表 Sales 中 Quantity 的单个值。”当度量值引用具有多个值的列而未定义聚合(如 min、max、count 或 total)以产生单个结果时,可能会出现这种情况。

这是因为在没有行上下文的情况下,DAX 无法确定 Quantity 和 Unit Cost 列应使用哪一行的值。要解决此问题,您必须使用迭代函数,如“SUMX”,来建立行上下文。

SUMX 在表达式 Amount = SUMX(Sales, Quantity * Unit Cost) 中充当迭代器,逐行处理 Sales 表并为它检查的每一行创建一个行上下文。这个行上下文允许函数在其执行过程中识别并使用每行的唯一数据。SUMX 在遍历 Sales 表时使用行上下文,为每行单独确定 Quantity 和 Unit Cost 的乘积。

由于 SUMX 是逐行工作的,因此它可以准确地将 Quantity 乘以 Unit Cost 的每一行,这与执行相同功能的计算列的行为一致。因为 SUMX 创建了行上下文,所以在 SUMX 的迭代过程中,表达式 Quantity * Unit Cost 可以有效地为每一行进行评估。

此 SUMX 操作最终会产生这些单个行计算的聚合,这通常会导致一个总计,反映了好像它是在每一行的列中确定的销售额。因此,当可视化 SUMX 函数时,Sales 表中计算列的输出(其中每行的销售额由相同的 Quantity * Unit Cost 计算得出)应该与从 SUMX 函数接收到的值匹配。

Row Context in Power BI

现在创建一个具有相同表达式的计算列,


Row Context in Power BI

当您在扫描 Sales 表的 SUMX 函数的上下文中写入 Quantity * Unit Cost 时,意图是为 SUMX 迭代的每一当前行计算 Quantity * Unit Cost。但是,重要的是要理解 SUMX 会跨整个表聚合这些单独的行计算,从而产生一个总计,而不是为每一行生成一个值。

嵌套行上下文

在某些需要使用多个迭代函数的情况下,可能需要引入嵌套行上下文。考虑这样一个场景:您需要找出每个客户的总销售额,包括他们子客户(如果有)的任何销售额。这可以通过使用嵌套的“SUMX”函数来完成。

在此表达式中

  • 外部 SUMX 遍历 Customers 表中的唯一 Customer Names(假设您有一个包含 CustomerName 列的单独的 Customers 表)。
  • 对于每个 CustomerName,ADDCOLUMNS 函数会创建一个名为“CustomerSales”的新列,该列使用内部 SUMX 计算该客户的销售额。
  • 内部 SUMX 扫描 Sales 表并对 NetProfit 列求和,在 Sales 表中引入了一个嵌套的行上下文。
  • 外部 SUMX 然后对“CustomerSales”值求和,从而有效地聚合每个客户的净利润。

此度量值计算每个客户的总净利润,同时考虑与该特定客户相关的任何销售额。外部 SUMX 在客户级别聚合数据,而内部 SUMX 引入了一个嵌套的行上下文,以确保计算在每笔销售的行级别进行。

通过理解这些嵌套行上下文的工作原理,您可以构建复杂的 DAX 表达式来满足各种业务需求,例如在多个详细级别(例如产品、客户、区域等)计算销售额或利润。

行上下文和表筛选

重要的是要理解,行上下文只是逐行扫描表并将当前行提供给表达式,它不会筛选表。不当处理此行为可能导致意外结果。

例如,请看下面的计算列。

在这种情况下,计算列将显示 SalesAmount 的总计,而不是行级别的 SalesAmount。这是因为“SUMX”迭代器扫描 Sales 表中的所有行,而不应用任何筛选器。

要获取行级别的 SalesAmount,您需要使用迭代函数来筛选表。这是一个更新的表达式

在此表达式中

  • 使用 SELECTEDVALUE 函数从报表上下文中获取当前的 ItemSold。
  • FILTER 函数仅包括 ItemSold 与当前 ItemSold 匹配的 Sales 表中的行。
  • SUMX 迭代器在处理筛选出的行时,会计算每行的 SalesAmount(Sales[Quantity] * Sales[Unit Cost])。

在迭代器方法中使用正确的筛选器可以确保行上下文正常工作并生成所需的行级别 SalesAmount 结果。

行上下文和性能

行上下文和迭代函数是创建复杂 DAX 表达式的有用工具,但如果处理不当,它们可能会对查询性能产生负面影响。特别是嵌套行上下文,如果未正确优化,可能会导致性能下降。

以下是一些有关如何更有效地处理 DAX 表达式中的行上下文的建议。

  1. 变量赋值:建议将复杂表达式赋值给变量,然后将其用于迭代函数中,以避免重复,这可以通过避免不必要的计算来提高性能。
  2. 过渡上下文:在上下文之间切换时,请使用“CALCULATE”函数而不是仅使用嵌套迭代器。这可以提高效率并简化您的 DAX 表达式。
  3. 索引:为提高查询性能,请确保在计算中使用的表具有正确的索引。
  4. 缓存:为了避免重复计算,请利用 Power BI 的缓存功能来存储和使用中间计算结果。

通过遵循 DAX 优化最佳实践并理解行上下文的性能影响,您可以构建高效且高性能的 Power BI DAX 表达式。

实际案例

考虑一个场景:您需要创建一个列,该列计算每个已售商品的累计净利润,并按已售商品排序。

在此表达式中

  • CALCULATE 函数会更改汇总或聚合数据的上下文。
  • SUM(Sales[Net Profit]) 计算净利润总额。
  • FILTER 函数创建一个修改后的筛选上下文,其中仅考虑截至当前已售商品的销售记录。这是通过确保 Sales[Items Sold] 小于或等于在当前计算点之前遇到的最大 Items Sold 值来实现的。
  • ALL(Sales) 会删除 Sales 表上任何现有的筛选器,允许计算考虑当前上下文中的最大 Items Sold 值之前的所有销售记录。
  • ALL(Sales) 的使用确保重置筛选上下文,以便计算可以考虑从开始到当前 Items Sold 值的所有先前销售额。此方法正确计算累计净利润,因为它将已评估上下文中的净利润从开始累加到当前 Items Sold。
Row Context in Power BI

在报表中可视化此度量值可以准确显示每个已售商品的累计净利润。结果按 Items Sold 列排序,反映了您的销售数据中每个点合并的总净利润。

行上下文的高级用法

Power BI 中的行上下文有时被认为仅适用于计算列。然而,它的用途远不止于此,它为复杂的数据分析和 DAX 公式提供了基础。嵌套计算、筛选上下文交互以及将行上下文扩展到时间智能函数等都是高级用法的示例。

嵌套迭代器和行上下文

嵌套迭代器是行上下文的一种复杂应用,它允许您计算依赖于在另一个行上下文内生成的行上下文的值。例如,您可以创建一个 DAX 公式,使用嵌套的 SUMX 方法来获得依赖于多个因素的加权平均值。通过创建其行上下文,您可以基于数据库的当前行来计算每个 SUMX 正在迭代的表达式。

这是 DAX 中嵌套迭代器的一个示例

内部 SUMX 使用 Product 表的当前行上下文确定每个产品的加权价格。然后,外部 SUMX 将此结果乘以每笔销售的数量以确定总销售额。

行上下文在自定义聚合中

行上下文至关重要的另一个应用是自定义聚合。例如,如果您需要仅对满足特定要求的商品应用折扣,您可以使用 CALCULATE 函数和行上下文来有条件地应用折扣。

在此实例中,由于 CALCULATE 函数在保持 Sales 表的行上下文的同时修改了筛选上下文,因此您可以仅将折扣应用于具有“Volume”折扣类型的销售。

将筛选上下文与行上下文集成

可以将筛选上下文和行上下文结合起来,以提供有效的计算指标。例如,您可以仅计算客户实际购买日的平均销售额。

此公式使用 EARLIER 函数返回到较早的行上下文,以便将 Sales 表当前行的 Customer ID 与外部行上下文中的 Customer ID 进行比较。

行上下文在时间智能函数中

Power BI 中的时间智能函数是 DAX 的一个子集,它允许分析人员执行复杂的时间数据分析。当与行上下文结合以在不同的时间粒度计算值时,它们会变得更加强大。

用于运行总计的行上下文

计算运行总计或在每个时间段结束时重置的余额是一个常见的应用。这可以通过行上下文来实现,方法是遍历日期表并使用 CALCULATE 函数应用筛选器。

在这种情况下,VALUES 函数生成一个不重复的日期列表,并且再次使用 EARLIER 函数来获取较早的行上下文。

带移动平均值的行上下文

行上下文经常与移动平均值一起使用,特别是当平均值需要考虑与当前行日期相关的时间跨度时。

在此示例中,DATESINPERIOD 函数使用 LASTDATE 函数提供的当前行上下文生成跨越过去 30 天的日期系列。

分析周期变化

时间智能函数广泛用于分析跨时间的变化。在当前行上下文内,您可以使用 DATEADD 函数通过与上一个周期进行比较来计算销售额变化。

在此公式中,DATEADD 函数与 CALCULATE 一起使用,移动到上一年的上下文以比较销售额。