大数跨境
0
0

ALLSELECTED 函数的最佳实践指南

ALLSELECTED 函数的最佳实践指南 PowerPivot工坊
2025-08-01
0


本文翻译自Marco Russo & Alberto Ferrari的文章—《ALLSELECTED best practices》 来源:SQLBI ALLSELECTED 是一个强大但危险的函数。本文介绍了应遵循的最佳实践,以避免陷入 ALLSELECTED 可能带来的陷阱。


ALLSELECTED 是整个 DAX 语言中最复杂的函数之一。它是唯一一个利用影子筛选上下文(shadow filter contexts)的 DAX 函数。此外,当在 SUMMARIZECOLUMNS 中使用或在迭代器内部使用时,ALLSELECTED 的行为会略有不同。在 SUMMARIZECOLUMNS 中使用 ALLSELECTED 通常能得到预期结果,而在迭代器内部使用时却可能产生异常结果。混合使用这两种方式,很可能会导致报表出现问题!

本文简要介绍了 ALLSELECTED 在两种场景(SUMMARIZECOLUMNS 和迭代器)中的特性,并提供该函数的最佳实践指南,同时通过示例展示混合使用两种行为如何导致意外结果。


ALLSELECTED介绍

ALLSELECTED 的使用方式可以非常直观。例如,考虑以下报表的需求场景:

该报表使用切片器来筛选特定品牌。它显示每个品牌的销售额,以及每个选定品牌占所有已选品牌总销售额的百分比。计算百分比的公式非常简单:

Pct =DIVIDE (    [Sales Amount],    CALCULATE (        [Sales Amount],        ALLSELECTED ( 'Product'[Brand] )    ))

从直观上看,ALLSELECTED 会返回可视化筛选上下文中选定的品牌值——即用户在Adventure Works和Proseware之间选择的品牌。然而,Power BI向DAX引擎发送的查询并不包含"当前可视化"这一概念。因此,DAX度量值中根本不存在所谓的"可视化筛选上下文"(虽然视觉计算是另一个话题,但它与常规度量值无关)。

那么DAX是如何知道切片器和矩阵中选择了什么呢?答案是:它其实并不知道。ALLSELECTED并不会返回可视化外部筛选过的列(或表)的值。它的作用机制完全不同,只是在大多数情况下会"碰巧"产生相同的结果。

ALLSELECTED的行为表现会因其使用场景不同而变化,主要取决于:是否在SUMMARIZECOLUMNS中使用,以及是否存在影子筛选上下文。由于SUMMARIZECOLUMNS是Power BI查询语义模型的主要函数,我们先从ALLSELECTED在SUMMARIZECOLUMNS中的运作机制开始说明。


SUMMARIZECOLUMNS 中的 ALLSELECTED

当在 SUMMARIZECOLUMNS 中使用时,ALLSELECTED 会移除当前迭代分组列的筛选器,恢复由 SUMMARIZECOLUMNS 自身设置的原始筛选条件。举例来说,让我们看一个填充矩阵的简化查询版本:

EVALUATESUMMARIZECOLUMNS (    'Product'[Brand],    TREATAS (        {            "Adventure Works",            "Contoso",            "Fabrikam",            "Litware",            "Northwind Traders",            "Proseware"        },        'Product'[Brand]    ),    "Sales_Amount"'Sales'[Sales Amount],    "Pct",        DIVIDE (            [Sales Amount],            CALCULATE (                [Sales Amount],                ALLSELECTED ( 'Product'[Brand] )            )        ))

执行该查询后,将生成以下结果:

从查询中可以看出,这里并不存在所谓的"可视化筛选上下文"概念。该查询通过SUMMARIZECOLUMNS同时实现筛选和分组操作:首先预设筛选条件,然后在每行处理时为当前品牌施加筛选,使得[销售额]仅计算当前品牌的数据。当使用ALLSELECTED时,DAX会移除当前品牌的筛选,从而恢复包含所有选定品牌的原始筛选上下文。

值得注意的是,即使筛选并非由SUMMARIZECOLUMNS直接创建,这一机制仍能正常工作。以下查询与我们刚才分析的案例效果等同,尽管其筛选是通过CALCULATETABLE而非SUMMARIZECOLUMNS设置的:


EVALUATECALCULATETABLE (    SUMMARIZECOLUMNS (        'Product'[Brand],        "Sales_Amount"'Sales'[Sales Amount],        "Pct",            DIVIDE (                [Sales Amount],                CALCULATE ( [Sales Amount], ALLSELECTED ( 'Product'[Brand] ) )            )    ),    TREATAS (        {            "Adventure Works",            "Contoso",            "Fabrikam",            "Litware",            "Northwind Traders",            "Proseware"        },        'Product'[Brand]    ))


由于 Power BI 几乎在所有可视化中都使用 SUMMARIZECOLUMNS,因此这种机制是最常用的。然而,ALLSELECTED 在非 SUMMARIZECOLUMNS 场景下同样适用,此时它会基于影子筛选上下文采用另一种不同的技术原理。

引入影子筛选上下文

影子筛选上下文是一种由迭代器创建的特殊筛选上下文。每当迭代开始时,迭代器会创建一个包含被迭代表行的影子筛选上下文。该筛选上下文处于非活跃状态,这意味着它不会主动筛选任何内容。它将保持休眠状态,直到可能被ALLSELECTED调用。

现在让我们重写上一节中使用过的查询,这次不使用SUMMARIZECOLUMNS:

EVALUATECALCULATETABLE (    ADDCOLUMNS (        VALUES ( 'Product'[Brand] ),        "Sales_Amount", [Sales Amount],        "Pct",            DIVIDE (                [Sales Amount],                CALCULATE (                    [Sales Amount],                    ALLSELECTED ( 'Product'[Brand] )                )            )    ),    TREATAS (        {            "Adventure Works",            "Contoso",            "Fabrikam",            "Litware",            "Northwind Traders",            "Proseware"        },        'Product'[Brand]    ))

这个后续查询的结果与我们之前分析的完全一致。但此时ALLSELECTED之所以能从所有品牌中返回六个品牌,是因为作为迭代器的ADDCOLUMNS创建了影子筛选上下文,而ALLSELECTED激活了这个上下文。

以下是查询执行的详细步骤说明(我们在第三步引入影子筛选上下文概念):

  1. 外层的CALCULATETABLE创建包含六个品牌的筛选上下文

  2. VALUES函数返回六个可见品牌,并将结果传递给ADDCOLUMNS

  3. 作为迭代器,ADDCOLUMNS在开始迭代前会创建一个包含VALUES结果的影子筛选上下文

    · 影子筛选上下文类似于普通筛选上下文,但处于休眠状态,不会影响任何计算

    · 影子筛选上下文只能被ALLSELECTED激活(稍后解释)。现在只需记住:该上下文包含六个被迭代品牌

    · 我们将影子筛选上下文与常规筛选上下文区分,称后者为"显式筛选上下文"

  4. 迭代过程中,上下文转换会针对当前行创建仅包含该品牌的显式筛选

  5. 当Pct度量值调用ALLSELECTED时,它会执行以下操作:恢复参数指定列/表上的最后一个影子筛选上下文(若无参数则恢复所有列)。由于最后的影子筛选上下文包含六个品牌,这些品牌会重新可见

这个简单示例帮助我们理解了影子筛选上下文的概念。上述查询展示了ALLSELECTED如何利用影子筛选上下文来获取当前迭代外的筛选上下文。在SUMMARIZECOLUMNS出现前的早期Power BI版本中,系统使用ADDCOLUMNS等迭代器并依赖影子筛选上下文机制,而非SUMMARIZECOLUMNS的现行方案。时至今日,迭代器仍会生成影子筛选上下文,但度量值主要基于SUMMARIZECOLUMN算法运行。必须注意:这两种机制仍然共存,它们协同工作,但若使用不当就可能导致错误结果。


掌握 ALLSELECTED 的最佳实践

避免 ALLSELECTED 引发问题其实很简单:永远不要在迭代中使用包含 ALLSELECTED 的度量值,并尽可能减少使用含 ALLSELECTED 的度量值。随着度量值复杂度增加,ALLSELECTED 的引用终将难以追踪——最终你会不小心在迭代中调用某个度量值,而该度量值又通过 ALLSELECTED 调用了其他度量值。简言之:只要存在活跃迭代器,就应避免使用 ALLSELECTED。这样能确保只有 Power BI 在查询初始阶段通过 SUMMARIZECOLUMNS 触发的机制会生效。

错误案例演示
下面我们通过示例展示:如果在迭代中混用 ALLSELECTED 而忽视其机制差异,将如何导致数据计算错误。

业务需求
仅计算"重要产品"的销售额。判定标准为:当前筛选上下文中,产品销售额占比超过总销售额的 0.5%。

方案设计

  1. 基础度量值:用 ALLSELECTED 创建动态总销售额计算

  2. 筛选度量值:迭代产品并应用条件逻辑,仅累计占比 >0.5% 的产品


Total Sales = CALCULATE ( [Sales Amount], ALLSELECTED () )Relevant Sales Wrong =SUMX (    'Product',    IF ( DIVIDE ( [Sales Amount], [Total Sales] ) >= 0.005, [Sales Amount] ))


正如其名称所示,"错误相关销售额"(Relevant Sales Wrong)这一度量值的计算结果并不准确。问题根源在于:该度量值对[产品]进行迭代时,在其内部调用了另一个使用 ALLSELECTED 的度量值。下图展示了该度量值在矩阵视觉对象中的计算结果:

矩阵中的多行数据值都超过了总计行的数值。在深入分析该度量值错误原因之前,我们先展示其正确版本:

Relevant Sales Correct =VAR TotalSales = [Total Sales]RETURNSUMX (    'Product',    IF ( DIVIDE ( [Sales Amount], TotalSales ) >= 0.005, [Sales Amount] ))


错误版本与正确版本度量值的核心差异
"错误相关销售额"(Relevant Sales Wrong)与"正确相关销售额"(Relevant Sales Correct)的唯一区别在于:[总销售额]度量值的调用位置被移至迭代外部。虽然该度量值通过ALLSELECTED改变了筛选上下文,理论上每行迭代应获得相同结果(即不受上下文转换影响),但由于ALLSELECTED的特殊机制,实际差异非常显著。下图展示了使用正确度量值后的矩阵结果:

为清晰对比差异,我们使用简化版查询(包含两个度量值的完整代码):

DEFINE    MEASURE Sales[TotalSales] =        CALCULATE (            [Sales Amount],            ALLSELECTED ()        )EVALUATESUMMARIZECOLUMNS (    ROLLUPADDISSUBTOTAL (        'Product'[Brand],        "Total"    ),    "Sales Amount", [Sales Amount],    "Wrong",        SUMX (            'Product',            --            --  Inside the iteration, ALLSELECTED restores the shadow filter context            --  created by SUMX, which is iterating over the products of the given brand            --            --  This is the shadow filter context behavior            --            IF (                DIVIDE (                    [Sales Amount],                    [TotalSales]                ) >= 0.005,                [Sales Amount]            )        ),    "Correct",        --        --  Outside of the iteration, ALLSELECTED removes the filter over Product[Brand]        --  and makes all the selected products visible        --        --  This is the SUMMARIZECOLUMNS/ALLSELECTED behavior        --        VAR TotalSales = [TotalSales]        RETURN            SUMX (                'Product',                IF (                    DIVIDE (                        [Sales Amount],                        TotalSales                    ) >= 0.005,                    [Sales Amount]                )            ))

从DAX查询中的注释可见,"错误计算"在迭代产品时从循环内部调用TotalSales度量值,因此它使用了影子筛选上下文机制。由于SUMX在迭代指定品牌的产品,TotalSales计算的是当前显示品牌中所有产品的销售额。

而正确表达式将TotalSales度量值放在迭代外部调用,因此不存在影子筛选上下文,此时ALLSELECTED会采用SUMMARIZECOLUMNS机制——它会移除Product[Brand]上的筛选器,使所有产品可见(若存在切片器筛选,则显示所有被选产品)。

这个示例特意保持简单:仅展示一个在迭代中直接调用含ALLSELECTED度量值的情况。实际场景中,面对包含数百个度量值的语义模型,几乎不可能追踪每个度量值在迭代内外的调用情况。因此规则很简单:ALLSELECTED只能安全用于直接放置在报表中的度量值。您应当避免调用包含ALLSELECTED的度量值,因为其他度量值可能会在不知情的情况下调用您的度量值(而该度量值内部使用了ALLSELECTED)。


结论


  1. ALLSELECTED强大且实用,无需畏惧

  2. 影子筛选上下文机制在SUMMARIZECOLUMNS出现前确实适用

  3. 在SUMMARIZECOLUMNS内部使用时,ALLSELECTED结果可靠

  4. 但若在迭代中调用含ALLSELECTED的度量值,就会触发旧机制,导致难以调试的结果

解决方案很明确:确保迭代开始时绝不使用ALLSELECTED。这需要严格把控度量值调用链,从而保证代码始终输出预期结果。


  • 延伸阅读:


使用AI辅助创建Power BI自定义视觉对象

Power BI 中的应收账款账龄分析

使用视觉计算进行条件格式设置

设计令人印象深刻的仪表板

16个让你的界面next level的UI细节


END

图片

长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~ 

图片

【声明】内容源于网络
0
0
PowerPivot工坊
提供Power Pivot,Power Query等Power BI技术相关文章,培训咨询等服务。
内容 648
粉丝 0
PowerPivot工坊 提供Power Pivot,Power Query等Power BI技术相关文章,培训咨询等服务。
总阅读211
粉丝0
内容648