06.2 以X结尾的迭代函数


在Excel中,我们习惯使用聚合函数一步一步地执行计算。比如为了计算总销售额,你创建了一列,用价格乘以数量,第二步,你将这一列求和以计算总销售额。这个数字会很有用,例如作为计算每个产品销售额百分比的分母。使用DAX,你可以借助迭代器用一步完成相同的操作。迭代器完全按照它的名字表示的意思来执行:迭代整张表,并对表的每一行执行计算,最后聚合结果以生成所需的单个值,这类迭代函数使用的是聚合函数+X的形式。06.2 以X结尾的迭代函数 - 图1## 初识迭代函数 如果需要计算所有销售额的总和,有的用户可能会尝试写出这样的公式[AllSales]:=SUM(Sales[ProductQuantity] Sales[ProductPrice])实际上,这是不被允许的,因为聚合函数只能使用单个列作为参数。你可以使用迭代函数SUMX计算所有销售额的总和,它逐行计算Sales表的销售额,将所有结果相加得到最终答案:[AllSales]:=SUMX(Sales,Sales[ProductQuantity] Sales[ProductPrice])这种方法既有优点也有缺点。好处是,你可以在单个步骤中执行许多复杂的计算,而不必为此添加许多列,这对某些特定的公式很有用。另一方面,由于使用DAX编程的视觉直观性不如Excel。实际上,你观察不到那个将价格和数量相乘的虚拟列;它只短暂的存在于公式运行的时候。你仍然可以选择创建一个计算列来计算价格和数量的乘积。然而,在某些情况下这不是一个好的做法,因为它占用了更多的内存,并可能会减慢模型刷新速度。### 语法示例 聚合函数语法Aggregate ( )迭代函数语法AggreateX (

, )将某些聚合函数结尾加X可以构成迭代函数,迭代函数语法结构基本相同,都以相同方式工作,仅最后的聚合操作有所不同。具体如下:

  1. 在作为第一参数接收到的表上创建一个新的行上下文。
  2. 对于表中的每一行,在新创建的行上下文内部对第二个参数计值(也包括在迭代开始前已经存在的其他上下文)。
  3. 对步骤2中计算出的值进行相应的聚合,SUMX求和、MINX取最小值、AVERAGEX取平均值 以此类推。

    常用的迭代函数

    SUMX

的每一行计算表达式,将得到的所有结果求和。SUMX只考虑列中的数字,空白、逻辑值和文本将被忽略。SUMX (
, )### AVERAGEX 对
的每一行计算表达式,将得到的所有结果计算算术平均值。AVERAGEX忽略空值。AVERAGEX(
,)### MINX 对
的每一行计算表达式,从得到的所有结果中取最小值。MINX支持数值,文本和日期类型,忽略空值,不支持逻辑值TRUE/FALSE。MINX(
, < expression>)### MAXX 对
的每一行计算表达式,从得到的所有结果中取最大值。MAXX支持数值,文本和日期类型,忽略空值,不支持逻辑值TRUE/FALSE。MAXX(
,)### COUNTX 对
的每一行计算表达式,计算包含非空白值或计算结果为非空白值的表达式的行数。COUNTX支持数值,文本和日期类型,不支持逻辑值,如果没有要计数的行则返回空值。COUNTX(
,)### COUNTAX 对
的每一行计算表达式,计算包含非空白值或计算结果为非空白值的表达式的行数。COUNTAX支持数值,文本和日期类型,支持逻辑值,如果没有要计数的行则返回空值。COUNTAX (
, )### PRODUCTX 对
的每一行计算表达式,将得到的所有结果相乘。PRODUCTX仅支持数值,空白、逻辑值和文本将被忽略。PRODUCTX(
, )本文以SUMX为例介绍这类迭代函数。另有两个常用的X结尾的函数,RANKX和CONCATENATEX将在统计类函数中介绍## 与返回表的表达式的配合使用 除了直接使用表作为迭代函数的第一参数,你也可以使用返回表的表达式作为参数, 为表的每行计算 DAX 表达式。例如[Sales Amount]:=SUMX(Sales,Sales[Quantity] Sales[Unit Price])你可以使用其他表函数替换对销售表引用。例如, 你可以使用FILTER函数筛选数量大于1的销售记录:[Sales Amount Multiple Items]:=SUMX(FILTER(Sales,Sales[Quantity]>1),Sales[Quantity] Sales[Unit Price])在计算列中, 还可以使用RELATEDTABLE函数检索位于一对多关系多端的表的所有行。例如,以下产品表的计算列统计所有产品对应的销售金额:Product[Product Sales Amount]=SUMX(RELATEDTABLE(Sales),Sales[Quantity] Sales[Unit Price])在基础函数类型 –关系函数一文中,可以找到RELATEDTABLE函数的详细说明。你可以将对表函数的调用嵌套在同一个DAX表达式中,因为任何表表达式都可以是对表函数的调用。例如,在下面的产品表计算列中,只考虑数量大于1的销售记录,计算产品销售额。Product[Product Sales Amount Multiple Items]=SUMX(FILTER(RELATEDTABLE(Sales),Sales[Quantity]>1),Sales[Quantity] Sales[Unit Price])当发生表函数的嵌套调用时, DAX 首先计算最内层函数, 然后逐级计算到最外层。不要将此规则与函数调用参数时的计算顺序混淆。## 控制计算发生的详细级别 聚合函数的计算默认发生在视觉级别上,计算的颗粒度始终与当前筛选上下文一致。以X结尾的迭代函数因为引入了表作为参数,可以通过对表的汇总级别的控制,调整计算发生的级别。例如你可以按销售大区汇总产品销售金额,如果使用SUM函数,销售额将在大区级别进行汇总。如果使用SUMX函数,稍微修改第一参数,可以将计算级别下移至更细的城市颗粒度=SUMX(VALUES(‘Table’[City]),CALCULATE(SUM(‘Table’[Sales])))目前这个公式的结果与原计算一致,而一旦你需要在大区级别显示各城市销售额总和的平均值 ,又不希望将城市显示到报告上,这种写法会非常有用。Tableau详细级别表达式中的Include关键词也可以实现类似的效果。## 创建行上下文 除了计算列之外,迭代函数也可以创建行上下文,这对于理解DAX计值流非常重要,在理解行上下文中对这部分知识做过详细介绍。## 聚合函数的本质 聚合函数SUM与迭代函数SUMX看起来是两个不同的函数,实际上它们之间存在某种相似性,与之类似的 MIN和MINX、MAX和MAXX等组合也具备这种相似性,理解这种相似性有助于你理解为什么聚合函数忽略行上下文,只考虑筛选上下文。也会加深你对DAX的理解。 SUM(‘Table’[Sales])—— 等价于 ——SUMX(‘Table’,’Table’[Sales])在等价形式中,SUMX第一参数在外部筛选上下文环境中计值,忽略行上下文,这也解释了为什么聚合函数具备相同的行为。## 迭代函数的性能问题 因为SUMX是一个迭代器,您可能认为SUMX本质上是低效的。但事实并不是这么简单。因为DAX引擎已经被优化到可以高效地处理迭代器。不过话虽如此,糟糕的DAX肯定会导致SUMX效率低下,原因是这样的:DAX有2个计算引擎,即存储引擎(SE)和公式引擎(FE)。SE更快,支持多线程和缓存。FE较慢,单线程且不缓存。DAX优化是一个复杂的主题,这里只介绍一点点相关内容。当了解这些信息后,意味着你应该编写公式来尽可能地利用SE。当然,如果你不知道如何做到这一点,这可能会很难,下面的提示也许可以帮助你。
  • sum()总是使用SE进行计算,不需要担心优化问题
  • 对于大多数简单的计算(如Sales[qty]*sales[price per unit]),SUMX()也将使用SE,因此这些计算都没有优化问题。
  • 在某些情况下,SUMX()可能会使用FE来执行部分或全部计算,特别是当您的公式中有一个复杂的比较语句时。如果SUMX需要使用FE,那么性能可能会很慢,有时甚至非常慢。 关于第3点,最后避免在SUMX函数中编写复杂的条件语句,例如IF判断。考虑以下两个公式:低性能公式Total Sales of Items more than $100Bad=SUMX(Sales,IF(Sales[ExtendedAmount]>100, Sales[ExtendedAmount]))
    高性能公式Total Sales of Items more than $100Good=CALCULATE(SUMX(Sales,Sales[ExtendedAmount]),Sales[ExtendedAmount]>100)低性能公式在SUMX中有一个IF语句。此if语句强制存储引擎将计算任务传递给公式引擎进行比较检查,以确定每个单独的行是否大于100,然后再决定是否将其包括在计算中。因此,公式引擎必须一次一行地完成任务,从而使计算变得缓慢且效率低下。第二个高性能公式中首先使用CALCULATE转换来自视觉级别的初始筛选器,以在Sales[ExtendedAmount]>100上添加额外的筛选器。存储引擎非常有效地应用了这个新筛选器。在CALCULATE修改筛选器之后,SUMX()可以使用存储引擎而不是公式引擎应用的新筛选器集来完成将剩余行相加的工作。因此,第二个公式性能更好。在一些简单测试中,第一个公式比第二个公式花费的时间长5倍。在其他情况下,它可能会慢100倍甚至1000倍,所以这种写法可能带来很大的问题。__避免在迭代函数中编写复杂代码06.2 以X结尾的迭代函数 - 图2游客liuFollow this user#676如果创建计算列,而MAXX(table,expression)类函数中的表达式又是table中的列,那其创建行上下文是什么?计算逻辑是啥样呢?赞0踩  回复9 天 之前06.2 以X结尾的迭代函数 - 图3作者高飞Follow this user#677写一个具体的公式 把问题具体化才好讨论赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图4游客liuFollow this user#678table={列1=【a,b,c….】,列2=【1,2,3…】};创建计算列maxx(table,列2);计算逻辑是不是: 当前行的=maxx新创建的行上下文,然后执行max(列2)??赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图5作者高飞Follow this user#679你的理解正确赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图6游客liuFollow this user#680就是说和calculate一样,执行计算的对应关系都是:当前行和maxx创建的表的行【完全一样的时候】才执行对应的聚合计算?赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图7作者高飞Follow this user#681并不是,maxx第二参数的聚合计算是作为第一参数的表的每行执行,而且calculate也不是你说的这个逻辑。建议看下calculate计值流的部分赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图8游客liuFollow this user#682所以是执行“maxx第二参数的聚合计算是作为第一参数的表的每行执行”后再与当前行执行对应关系的时候,那不就没有完全一样的两行了?赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图9作者高飞Follow this user#683不需要与当前行执行对应关系,maxx在第一参数每行计值之后取最大值赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图10游客liuFollow this user#686“maxx在第一参数每行计值之后取最大值”是在他创建的表上下文中,和当前行的上下文是不是不一样?而最后的结果又是显示在当前行里面,这里面应该是不是有一个对应关系才行赞0踩 回复9 天 之前06.2 以X结尾的迭代函数 - 图11作者高飞Follow this user#690不需要对应关系,这个结果只和maxx第一参数使用的行上下文有关,maxx本身在哪个表不影响结果。(这个解释只适用于一开始举例用的公式)赞0踩 回复9 天 之前