08.6 理解ALLSELECTED

ALLSELECTED是最复杂的DAX函数,本文完整的介绍了它的复杂性。但是,对于绝大部分用户,你可以不必深究这背后的原理,理解初识ALLSELECTED部分可以帮助你解决大部分问题

初识ALLSELECTED

当你想把透视表的页面筛选器或切片器作为计算使用的参数时,ALLSELECTED是一个非常有用的函数。例如,假设你希望构建如图所示的报告。08.6 理解ALLSELECTED - 图1透视表中显示的百分比是根据当前显示的总数计算的,而不是所有颜色的总数在这个报告中,显示了当前行的销售额占列总计的比重。这个百分比难以计算的原因是,产品颜色被同时用作切片器(在这个例子中,我们只选择了部分颜色)和行标签。如果你借助到目前为止获得的知识,可以试试这个公式:[SalesPct]:=DIVIDE([Sales Amount],CALCULATE([Sales Amount],ALL(Product[Color])))使用ALL (Product[Color])从颜色列中移除了筛选器,并尝试在列级别计算总数。不幸的是,ALL移除了来自行和切片器的所有筛选,这将导致一个错误的结果。你可以从下面的数据透视表中看到公式的结果,其中总计不显示100%,而是一个较小的值。08.6 理解ALLSELECTED - 图2使用ALL函数计算的百分比是不准确的,因为它是针对所有颜色的百分比这里的问题是计算分母时使用了所有颜色的数据,即使用户只在切片器中选择了其中一部分颜色。对于每一行,计算时使用的分母要大于透视表实际显示的总数。我们需要的是一个不返回所有颜色,但可以返回初始筛选上下文中所有选择颜色的函数,即一个完整的透视表。我们将这种计算称为视觉总计(Visual Totals),因为它使用用户可见的总计代替整个数据模型范围内的总计,使用的函数是ALLSELECTED。如果你这样定义公式:[SalesPct]:=DIVIDE([Sales],CALCULATE([Sales],ALLSELECTED(Product[Color])))结果将是本节开头所示的正确结果。ALLSELECTED只返回透视表在初始筛选上下文中的可见值。换句话说,ALLSELECTED忽略透视表行和列上的筛选器,只考虑用于计算总计的筛选器。ALLSELECTED支持三种不同类型的参数调用:

  • 单列或多列,例如ALLSELECTED (Product[Color]),返回初始筛选的颜色。
  • 整张表,例如ALLSELECTED (Product),对表的所有列执行ALLSELECTED,返回其中所有初始选择的行。
  • 你还可以使用不带参数的ALLSELECTED(),它在数据模型的所有表上执行ALLSELECTED操作,从而可以在不含行和列筛选器的情况下计算透视表的总计。 ALLSELECTED被用来以非常动态的方式计算百分比和比率。接下来,我们将更深入地介绍ALLSELECTED,它隐藏了一些复杂内容,这使它成为DAX中最复杂的函数。# 理解ALLSELECTED 基于我们目前的了解,ALLSELECTED看起来像一个特殊的函数,能够理解用户在透视表中选择的内容。实际上,它允许你检索透视表运行时产生的影子筛选上下文(Shadow Filter Context)。不幸的是,对ALLSELECTED的描述存在一个大问题,即DAX函数如何了解用户在数据透视表中做的设置。如果我们使用的不是透视表而是DAX查询,那么ALLSELECTED是否仍然有效?为了解决这个合理性问题,我们需要深入研究ALLSELECTED,以便准确理解它是如何运行的。## 引言 让我们在阐述开始之前先公布这个简单的事实:ALLSELECTED并不知道用户对透视表所做的操作,它甚至都不知道透视表的存在。那么,它是究竟是如何工作的呢?> open-quote> 译者注:ALLSELECTED是一个如此复杂且充满陷阱的函数,以至于在DAX权威指南第一版面世的时候,对它的介绍仍然不够系统和完整,并且存在错误描述,后来作者已经对此做出了修正,最新的文章发布在,为了保证中文版内容的准确和完备,经作者同意,本节内容使用发表于SQLBI的《The Definitive Guide to ALLSELECTED》,原书内容不再提供,敬请谅解。> close-quote 我们已经写过很多关于ALLSELECTED的文章,不幸的是,我们从来没有完全搞清楚它的行为。原因很简单。我们陷入了ALLSELECTED制造的众多陷阱之一:我们相信自己理解了它的原理,其实我们只是在不断接近真相 – 但尚未抵达。在花费大量时间研究了ALLSELECTED的行为并与Analysis Services开发团队讨论了这一问题之后,我们终于完全理解了ALLSELECTED函数。本文描述了它的行为。如果你已经阅读了第一版“DAX权威指南”,请将这篇文章视为该书的一个勘误表把。事实上,本文内容相比DAX指南成书的时候更加精确和清晰。对此我们深表歉意。在文章开始之前重申:这不是一篇关于如何使用ALLSELECTED的介绍性文章。我们并不是在解释什么时候使用这个函数,以及用它来做什么。一篇只涉及ALLSELECTED内部原理的文章就已经撰写了大约20页;继续添加介绍将超出本文的范围。这篇文章的受众是想深入了解ALLSELECTED原理的读者。让我们从结尾开始,告诉你ALLSELECTED做了什么。第一次阅读的时候读者可能很难理解下面这段陈述。事实上,整篇文章的目的是解释这句话:ALLSELECTED既可以返回表,也可以移除筛选器并恢复之前的筛选上下文。这两种功能的实现,都是通过访问和使用迭代器在筛选上下文堆栈中留下的最后一个影子筛选上下文实现的。## 表函数还是CALCULATE调节器? 在进一步深入之前,我们需要回答一个重要的问题:ALLSELECTED是一个表函数,还是像KEEPFILTERS和USERELATIONSHIP那样的CALCULATE调节器?这取决于参数的数量和函数所在的上下文。事实上,ALLSELECTED可以与三种不同的参数一起使用:表、列或完全没有参数,如下面的公式所示:AllSelectedColumn :=CALCULATE([SalesAmount],ALLSELECTED(Customer[Occupation]))AllSelectedTable :=CALCULATE([SalesAmount],ALLSELECTED(Customer))AllSelectedAll :=CALCULATE([SalesAmount],ALLSELECTED())下图显示了在包含产品类别、客户性别和职业的矩阵中使用这三种度量值的结果:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-00.png此示例说明ALLSELECTED可以作为CALCULATE调节器。尽管如此,ALLSELECTED也可以用作表函数,如以下代码中所示:AllSelectedCustomerSales :=SUMX(ALLSELECTED(Customer),[SalesAmount])当作为表函数使用时,ALLSELECTED将遵循下面解释的规则返回表的一个子集,或列值的一个子集。另一方面,在不使用任何参数的情况下,如以下代码所示,ALLSELECTED只能作为CALCULATE调节器使用:AllSelectedSales :=CALCULATE([SalesAmount],ALLSELECTED())现在,重点是ALLSELECTED与表或列一起使用时的行为,稍后我们将介绍不带参数的ALLSELECTED作为CALCULATE调节器的行为。在对ALLSELECTED进行更深入的分析之前,我们需要引入影子筛选上下文,因为它们在ALLSELECTED的描述中至关重要。## 介绍影子筛选上下文 影子筛选上下文是由迭代器创建的一种特殊类型的筛选上下文,在初始状态下是不活动的。不活动的筛选上下文停留在休眠状态,它不会以任何方式影响公式计值流。尽管如此,它仍然存在,而且对于本文来说非常重要,因为ALLSELECTED在执行时激活了影子筛选上下文。让我们观察下面这个度量值:SalesAmount :=SUMX(Sales,Sales[Quantity]* Sales[Net Price])作为迭代器,SUMX生成一个包含销售表的影子筛选上下文。它不包含整个销售表。只包含其在当前筛选上下文中可见的行。作为一个影子筛选上下文,它是不活动的。因此不会影响计算,这就是为什么影子筛选上下文没有被广泛讨论的原因。为了演示这一点,我们将使用以下代码,它们的行为与预期一致。结果是销售总量乘以颜色的数量。在缺少上下文转换的环境中调用SUM是有意为之:WrongSalesAmount :=SUMX(VALUES(Product[Color]),SUM(Sales[Quantity]))在迭代过程中,颜色列的行上下文不会转换为筛选上下文,因为没有CALCULATE执行上下文转换。正如我们介绍的,这里确实存在一个筛选上下文:“影子”筛选上下文。影子筛选上下文包括迭代器启动时当前筛选上下文中活动的颜色列表。实际上,影子筛选上下文在迭代过程中是不活动的,除非它被一个叫做ALLSELECTED的函数激活。换句话说,当不使用ALLSELECTED时,忽略影子筛选上下文的存在是安全的。另一方面,当你决定使用ALLSELECTED时,影子筛选上下文变得至关重要。细心的读者可能会注意到,上面的公式中即使影子筛选上下文是活动的,它也不会改变结果。事实上,由于影子筛选上下文包含了所有颜色,所以结果与初始筛选上下文相同。但是,随着下面这个表达式的出现,复杂性开始增加:AnotherSalesAmount :=SUMX(CALCULATETABLE(VALUES(Product[Color]),Product[Color]=”Red”),SUM(Sales[Quantity]))在这种情况下,影子筛选上下文包含了一个对颜色的选择——即,只有红色。但是在迭代中,SUM (Sales[Quantity])仍然计算所有销售的总和。如果影子筛选上下文是活动(生效)的,那么引擎将只对红色产品的销售进行求和。在本文中,我们将普通筛选器(即非影子筛选器)称为显式筛选器,惟一的目的是在需要时区分显式筛选器和影子筛选器。现在我们已经了解了影子筛选上下文,我们可以开始分析基于示例数据的ALLSELECTED的行为。## 样例数据 我们使用一个有9行的表, 并且只有三列: “产品”、”品牌” 和 “颜色”:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-01.png在下面的查询中,我们使用外部的CALCULATETABLE来筛选表格中的某些颜色和品牌。我们还应用了一个不常见的度量值,ListProductNames。ListProductNames使用CONCATENATEX返回在当前筛选上下文中可见的产品名称列表。此度量值的目的是帮助我们分析ALLSELECTED在用作表函数时的结果和用作CALCULATE调节器时的结果。DEFINEMEASURE Product[ListProductNames]=CONCATENATEX(VALUES(‘Product’[Product]), Product[Product],”, “)EVALUATECALCULATETABLE(ROW(“Products”,[ListProductNames]),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})查询的结果是:Helmet, Shoes, Keyboard, Piano。下图显示了为颜色列和品牌列生成的两个筛选器,以及最终得到的筛选上下文:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-02.png到目前为止,还没有什么新的东西。接下来,我们将使用加入含ALLSELECTED的代码片段来分析其行为。## 使用列作为参数 ALLSELECTED (Table[Column])返回筛选该列的最后一个影子筛选上下文中可见的列值。在下面的代码中,我们在Product[Product]上使用ALLSELECTED来检索在最后一个影子筛选上下文中可见的所有产品名称的列表。在继续阅读之前,你可以试着猜测一下结果。EVALUATECALCULATETABLE(ROW(“Products”,CONCATENATEX(ALLSELECTED(‘Product’[Product]), Product[Product],”, “)),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})第一种猜测是按照之前图片中的筛选器逻辑进行操作,得到和前面一样的结果:只有四个产品符合颜色是{ “Blue”, “Green” },品牌是{ “Contoso”, “Fabrikam” },即Helmet, Shoes, Keyboard, Piano。但实际上,结果完全不同,查询返回了所有产品:Bike, Helmet, Shoes, Robot, Shirt, Rollerblades, Motorbike, Keyboard, Piano实际上,ALLSELECTED返回由最后一个影子过筛选上下文过滤的列值。你可能已经注意到,在调用ALLSELECTED时没有活动的迭代发生。所以没有需要激活的影子筛选上下文。因此,所有的产品名称都会被返回,因为颜色和品牌都不会直接筛选Product [Product]列,它是通过交叉筛选被影响的。这是我们学到的第一课:ALLSELECTED不考虑筛选上下文。它的主要目的是检索之前设置的影子筛选上下文。ALLSELECTED与VALUES有很大区别。VALUES和DISTINCT总是会考虑筛选上下文,而ALLSELECTED不会。它在单个列上工作,并检查该列是否被影子筛选上下文过滤,忽略任何交叉筛选。用下面这种方式改写前面的公式可能让你感到困惑:EVALUATECALCULATETABLE(ROW(“Products”,CALCULATE(CONCATENATEX(VALUES(‘Product’[Product]), Product[Product],”, “),ALLSELECTED(Product[Product]))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})这一次查询返回预期的结果:即Helmet, Shoes, Keyboard, Piano。当我们理解公式时,我们倾向于认为这是因为ALLSELECTED (Product[Product])在Product[Product]列上应用了一个筛选器。但事实并非如此。在这种情况下,ALLSELECTED不会对Product[Product]列应用任何筛选器,因为没有影子筛选上下文。当公式被调用时,VALUES通过观察位于颜色和品牌上的筛选器来分析当前上下文中可见的产品。ALLSELECTED对此筛选过程未做任何干预。换句话说,对Product[Product]进行筛选的并不是ALLSELECTED,而是颜色和品牌的交叉筛选效果,通过VALUES实现。到目前为止,我们已经看到ALLSELECTED还没有以任何方式筛选列。现在是时候生成一个影子筛选上下文,让ALLSELECTED来为我们激活它了。为此,我们需要构造迭代计算——用ADDCOLUMNS替换ROW,来迭代品牌列,如下面的代码所示:EVALUATECALCULATETABLE(ADDCOLUMNS(VALUES(‘Product’[Brand]),”Brands”,CONCATENATEX(ALLSELECTED(‘Product’[Brand]),Product[Brand],”, “)),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})查询返回预期的结果,为每个品牌返回Contoso、Fabrikam。https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-03.png初步来看,似乎ALLSELECTED恢复了来自外部CALCULATETABLE的筛选器。然而,情况并非如此,尽管它返回相同的结果。所发生的是ADDCOLUMNS(迭代器)生成一个包含迭代表的影子筛选上下文。迭代表是VALUES(Product[Brand])的结果,公式在只包含Contoso和Fabrikam的筛选上下文中计算,返回这两个品牌。ALLSELECTED返回在最后一个影子筛选上下文中可见的品牌,生成预期的结果。如何才能确定这确实是ALLSELECTED的行为呢?我们可以尝试用ALL替换VALUES,这时ADDCOLUMNS的迭代就不再发生在两个品牌上,而是在所有品牌上:EVALUATECALCULATETABLE(ADDCOLUMNS(ALL(‘Product’[Brand]),”Brands”,CONCATENATEX(ALLSELECTED(‘Product’[Brand]),Product[Brand],”, “)),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})这一次,结果将是一个三行表,每一行显示所有品牌:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-04.png原因在于ADDCOLUMNS遍历的是ALL(Product[Brand])。因此,影子筛选上下文包含Product[Brand]的所有值。被激活后,影子筛选上下文显示所有值,尽管外层的CALCULATETABLE只筛选了三个品牌中的两个。现在,我们通过在同一列上使用两个迭代器让情况变得更加复杂。考虑语法规范,我们需要使用SELECTCOLUMNS重命名列,以避免名称冲突。下面是一个更详细的例子:EVALUATECALCULATETABLE(GENERATE(SELECTCOLUMNS(ALL(‘Product’[Brand]),”Outer Brand”, Product[Brand]),GENERATE(SELECTCOLUMNS(VALUES(‘Product’[Brand]),”Inner Brand”, Product[Brand]),ROW(“Brands”,CONCATENATEX(ALLSELECTED(‘Product’[Brand]),Product[Brand],”, “)))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})在同一个Product[Brand]列上有两个嵌套迭代,在内层的度量值中我们使用ALLSELECTED作为表函数。结果将是内层影子筛选上下文中迭代的品牌列表。由于内层影子筛选上下文是由扫描VALUES(Product[Brand])的迭代生成的,并且由于VALUES被最外层CALCULATETABLE创建的筛选上下文筛选,结果中的Inner Brand包含两个品牌:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-05.png交换内部和外部品牌列使用的VALUES和ALL函数会得到不同的结果,实际上,内层的迭代扫描ALL生成的表,ALLSELECTED将返回所有品牌的值。EVALUATECALCULATETABLE(GENERATE(SELECTCOLUMNS(VALUES(‘Product’[Brand]),”Outer Brand”, Product[Brand]),GENERATE(SELECTCOLUMNS(ALL(‘Product’[Brand]),”Inner Brand”, Product[Brand]),ROW(“Brands”,CONCATENATEX(ALLSELECTED(‘Product’[Brand]),Product[Brand],”, “)))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})查询结果如下:https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-06.png关于迭代Product[Brand]的讨论到此告一段落。现在考虑产品名称列,如果我们在对品牌的两个迭代中使用ALLSELECTED (Product [Product]),应该期望得到什么结果?了解规则后很容易解决这个问题:ALLSELECTED返回在最后一个影子筛选上下文中可见的所有产品名称。你可以在观察下面代码的时候猜一下答案:EVALUATECALCULATETABLE(GENERATE(SELECTCOLUMNS(VALUES(‘Product’[Brand]),”Outer Brand”, Product[Brand]),GENERATE(SELECTCOLUMNS(ALL(‘Product’[Brand]),”Inner Brand”, Product[Brand]),ROW(“Products”,CONCATENATEX(ALLSELECTED(‘Product’[Product]),Product[Product],”, “)))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})答案非常直观:因为没有影子筛选上下文筛选产品名称,ALLSELECTED返回了所有产品名称。https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-07.png基于这个例子,一个值得思考的好问题是:是否可以获得倒数第二层影子筛选上下文,就像在行上下文中使用EARLIER那样,如果只依赖ALLSELECTED,答案是否定的。不过,DAX不仅提供了ALLSELECTED,它还提供了一种更强大的机制来处理外部上下文,无论是显式上下文还是影子上下文:它就是变量。如果在内部迭代中,想要访问品牌的外部上下文,只需要在内层的影子筛选上下文生效前,将ALLSELECTED(Product[Brand])的结果保存在变量中,如下例所示:EVALUATECALCULATETABLE(GENERATE(SELECTCOLUMNS(VALUES(‘Product’[Brand]),”Outer Brand”, Product[Brand]),VAR OuterProducts =ALLSELECTED(‘Product’[Brand])RETURNGENERATE(SELECTCOLUMNS(ALL(‘Product’[Brand]),”Inner Brand”, Product[Brand]),ROW(“Brands”,CONCATENATEX(OuterProducts,’Product’[Brand],”, “)))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})我们建议你不妨自己测试一下,结果只有Contoso和Fabrikam是可见的,这两个品牌来自外层筛选上下文。https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-08.png友情提示,使用变量使代码更容易编写和调试:建议你熟练掌握变量的用法,因为它们在复杂的DAX代码中扮演着非常重要的角色。到目前为止,我们已经深入分析了ALLSELECTED使用单列作为参数时的语义。下一步我们增加复杂性,引用完整的表作为参数。## 使用表作为参数 通过在公式中使用ALLSELECTED(Product),情况将开始变得复杂。事实上,一个表包含多个列,在每一列上,都有可能存在通过CALCULATE设置的显式筛选器,或者迭代器设置的影子筛选器。此外,可以在每个列上嵌套多个筛选器(显式筛选器或影子筛选器),这使得事情更加复杂。让我们从一个完整的表开始学习ALLSELECTED的这种行为。使用最简单的测试查询来说明ALLSELECTED用表或列作为参数时的第一个显著不同:EVALUATECALCULATETABLE(ROW(“Products”,CONCATENATEX(ALLSELECTED(‘Product’),Product[Product],”, “)),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})在本文的开头,我们已经声明ALLSELECTED忽略来自列的显式筛选器,它只使用影子筛选上下文,这适用于列作为参数的情况。但如果与表参数一起使用,此结论不成立,实际上,这个查询的结果是:Helmet, Shoes, Keyboard, Piano换句话说,使用表的ALLSELECTED接受CALCULATETABLE创建的显式筛选器,只返回颜色为蓝色或绿色、品牌是Contoso或Fabrikam的产品。你可能已经注意到,在第一个查询中,没有影子筛选上下文,只有显式的筛选上下文。如果我们在其中一列上引入迭代会发生什么?下面这个查询显示了预期中的行为:EVALUATECALCULATETABLE(ADDCOLUMNS(ALL(‘Product’[Color]),”Products”,CONCATENATEX(ALLSELECTED(‘Product’),Product[Product],”, “)),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})因为ADDCOLUMNS迭代的是所有产品颜色,所以现在有一个包含所有颜色的影子筛选上下文。这个上下文被ALLSELECTED激活,它将替换之前蓝色和绿色上的颜色筛选器。另一方面,品牌列没有被影子筛选上下文过滤,仍然在Contoso和Fabrikam上有一个显式筛选器。综合以上条件,查询结果是任何颜色的Contoso或Fabrikam的所有产品。换句话说,颜色上的筛选器被替换了,品牌筛选器则没有。我们建议读者查看原始数据,以充分理解这个复杂的结果。https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-09.png下一个查询比较复杂:外层的影子筛选上下文包含所有品牌,而内层影子上下文只包含两个品牌和颜色。因为ALLSELECTED激活的是每一列上的最后一个影子筛选上下文,所以它将在最内层的筛选上下文上关闭。事实上,它已经过滤了品牌和颜色。因此,在这种情况下,外层的影子筛选上下文没有被激活:EVALUATECALCULATETABLE(GENERATE(SELECTCOLUMNS(ALL(Product[Brand]),”Outer Brand”, Product[Brand]),ADDCOLUMNS(CROSSJOIN(VALUES(Product[Color]),SELECTCOLUMNS(VALUES(Product[Brand]),”Inner Brand”, Product[Brand])),”Products”,CONCATENATEX(ALLSELECTED(‘Product’),Product[Product],”, “))),Product[Color]IN{“Blue”,”Green”},Product[Brand]IN{“Contoso”,”Fabrikam”})产品列生成的内容对于结果的所有行都是相同的:Helmet, Shoes, Keyboard and Piano外层的ALL ( Product[Brand] )没有被激活,被内层的影子筛选上下文覆盖https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-11.png## 初步总结 让我们总结一下目前为止的知识点。
  • 迭代器创建影子筛选上下文
  • CALCULATE创建显式筛选上下文
  • ALLSELECTED是一个表函数,它在与表或列一起使用时返回不同的结果。
  • 使用列作为参数,ALLSELECTED返回最后一个影子筛选器包含的列值(如果有的话)。如果不存在影子筛选上下文,ALLSELECTED将返回列的所有值。
  • 使用表作为参数,ALLSELECTED返回一个表,其中包含对具有影子筛选器的任何列应用最后一个影子筛选器后得到的所有行;或者,如果没有可用的影子筛选上下文,ALLSELECTED返回最后一个显式筛选上下文。

到目前位置,我们提醒你注意这样一个事实:ALLSELECTED经常被用作CALCULATE 调节器,较少作为表函数使用。我们已经了解,下面这两个度量值会得到不同的结果:AllSelectedWithCALCULATE :=CALCULATE(CONCATENATEX(VALUES(‘Product’[Product]),Product[Product],”, “),ALLSELECTED(Product[Product]))AllSelectedAsTableFunction :=CALCULATE(CONCATENATEX(ALLSELECTED(‘Product’[Product]),Product[Product],”, “))事实上,对于上面使用的第一个度量值,外部筛选上下文是由VALUES注入的,而不是ALLSELECTED。此外,ALLSELECTED的操作方式与ALL类似,后者在用作CALCULATE调节器时从筛选上下文中删除相应的筛选器。截至目前,还有以下用法尚未分析:

  • 无参数的ALLSELECTED的用法:此时ALLSELECTED不再是一个表函数,而只是一个CALCULATE 调节器。
  • ALLSELECTED与上下文转换的计值顺序

    无参数的ALLSELECTED

    在参数留空的情况下,ALLSELECTED显然不是一个表函数。相反,此时它只能用作CALCULATE的筛选器参数。你可以这样直观地描述其行为:它在影子筛选上下文引用的所有列上执行ALLSELECTED。在实际操作时,ALLSELECTED恢复每个列的最后一个影子筛选上下文。本文对类似行为已经做了深入探讨,就不再赘述了。我们将举一个例子让读者得出结论。观察下面两个度量值:AllSel :=CALCULATE(SUM(Sales[Quantity]),ALLSELECTED())AllSelSumX :=SUMX(VALUES(‘Product’[Brand]),SUMX(VALUES(‘Product’[Color]),CALCULATE(SUM(Sales[Quantity]),ALLSELECTED())))我们将其放入POWER BI报告中https://www.sqlbi.com/wp-content/uploads/AllSelected-Internals-12.png报告中只选择了Contoso和Fabrikam两个品牌,一共6个产品。AllSel的值看起来是正确的,因为它在每一行计算了当前矩阵的总计数量。但AllSelSumX的结果令人困惑:唯一看起来正确的是底部总计(300×6 = 1800),而其他数字似乎都错了。注意:如果你对ALLSELECTED还停留在基础理解层面,这些数字看起来是错误的。事实上,一旦清楚了ALLSELECTED的工作原理,这些结果是完全正确的。事实上,如果我们关注30 (报告中Contoso/Blue所在的行)这个值,那么解释就很简单了。我们从只包含一个产品、一个品牌、一种颜色的筛选上下文开始。两个嵌套迭代(品牌列上的SUMX和颜色列上的SUMX)都只迭代一行,这意味着两个SUMX函数都生成一个只包含一个值的影子筛选上下文。ALLSELECTED将恢复这些影子筛选上下文,所以SUM在一个只包含一行的筛选上下文中执行。因此,为每一行计算的值是在给定行的筛选上下文中唯一可见的产品的值,因为ALLSELECTED通过激活两个影子筛选上下文将其重新激活。遵循相同的路径,AllSelSumX的所有值开始变得可解释,因为现在我们已经清楚了ALLSELECTED是如何工作的,即恢复影子筛选上下文。我们强烈建议读者在阅读下面的提示之前,花一点时间来理解Contoso所在行的结果:180等于60乘以3;公式对品牌列进行一次迭代;对颜色列进行三次迭代;并且,最内层的影子筛选上下文在颜色列上包含了三种颜色。以上内容不是为了说服你AllSelSumX显示的值有任何意义。很可能报告的用户和读者都认为这个数字是错误的。本文的目标不是找到一种计算指标的正确方法。相反,我们的目的是专注于理解ALLSELECTED是如何计值的,我们只在真正需要使用这个函数的时候才考虑它。## ALLSELECTED和上下文转换 在《DAX权威指南》的第一版中,我们在介绍ALLSELECTED时提到ALLSELECTED删除了由上下文转换生成的最后一个筛选上下文。不幸的是,这是一个错误的描述。这需要进一步解释:ALLSELECTED到底是否与上下文转换的过程发生交互?我们知道,作为创建筛选上下文的一部分,CALCULATE生成的筛选上下文等价于任何现有的行上下文。我们还知道,这个由上下文转换生成的筛选上下文相对于任何显式筛选上下文具有较低的优先级。使用ALLSELECTED作为CALCULATE筛选器参数意味着将影子筛选上下文转换为显式筛选上下文。通过这种方式,它的优先级将超过由上下文转换生成的筛选上下文。 因此,看起来ALLSELECTED移除了由上下文转换生成的最后一个筛选上下文。实际上并不存在这种移除。这个移除效果只是将影子筛选上下文转换为显式筛选上下文的副带作用。一旦ALLSELECTED的语义可以通过影子筛选上下文清晰的解释,理解它的行为就会变得容易得多。## 总结 正如本节介绍的,只有当你更熟悉影子筛选上下文时,ALLSELECTED的行为才更容易理解。也就是说,因为影子筛选上下文的存在,ALLSELECTED变成了一个非常复杂的函数,而且它与显式筛选上下文的交互使得完整描述计值过程变得困难。我们在培训DAX的时候通常会建议用户,当且仅当没有迭代发生时,使用ALLSELECTED来检索查询上下文,也就是透视表或报表所在的计值环境的上下文。如果有任何迭代发生,我们建议避免使用ALLSELECTED,因为结果几乎是不可预测的,并且也是非常复杂和难以理解的。本文介绍了理解ALLSELECTED行为所需的所有工具。但是,我们不希望每次需要调试度量值时都必须执行所有这些复杂的推理步骤。因此,这个建议始终成立:ALLSELECTED不应该在迭代中使用,除非用户已经非常清楚他们在做什么,并且有强烈的需求。在大多数情况下,变量可以避免在迭代中使用ALLSELECTED。我们强烈建议你使用变量。