【DAX圣经】第四章:理解计算上下文(1)

到本书的这一章时,您已经学习了DAX语言的基础知识。您知道如何创建计算列和度量值,并且您已经很好地理解了DAX中的通用函数。在这一章中,你将进入该语言的下一阶段:在学习了DAX语言坚实的理论背景之后,你将能够成为一个真正的DAX选手。

到目前为止,您已经获得了一些知识,您可以创建许多有趣的报告,为了能创建更复杂的报告,你还需要学习计算上下文。计算上下文是DAX所有高级特性的基础。

我们想对我们的读者说几句话:计算上下文的概念很简单,你很快就会学习和理解它。然而,您需要彻底地理解一些底层的细枝末节;否则,在你的DAX学习路径中,你会感到迷失。我们在公共和私人课程中教许多用户。我们知道这是绝对正常的。在某一时刻,你会觉得公式像魔术一样工作,因为它们是有效的,但你不明白为什么。别担心:许多人也有同样的问题。大多数DAX的学生在学习时能理解,而其他许多学生将来也会理解。这仅仅意味着计算上下文对他们来说不够清晰。在这一点上,解决方案很简单:回到这一章,再读一遍,你可能会发现一些新的东西,这是你在第一次读到的时候漏掉了。

此外,计算上下文在CALCULATE函数的使用中起着重要的作用。CALCULATE可能是DAX中最强大、最难以学习的函数,我们将在第5章“理解 CALCULATE和CALCULATETABLE”中介绍CALCULATE,然后在书的其余部分中使用它。在没有坚实的计算上下文背景的情况下,理解CALCULATE是有问题的。另一方面,在不尝试使用CALCULATE的情况下,理解计算上下文的重要性几乎是不可能的。因此,这一章和随后的一章根据我们之前的经验,总是会被标记并反复查阅。

介绍计算上下文

让我们先了解一下计算上下文是什么。任何DAX表达式都是在上下文中求值的。上下文是计算公式的“环境”。例如,考虑一个非常简单的公式:

[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )

您已经知道这个公式计算的是什么:数量乘以价格的总和。您可以将这个度量值放在一个透视表中,并查看结果,如图4-1所示。

图4-1 没有筛选条件的销售金额度量值,显示了销售总额

这个数字本身看起来一点都不有趣,是吗?但是,如果你仔细想想,这个公式计算出它应该计算的东西:所有销售金额的总和,这是一个没有多少含义的大数字。当我们使用一些列来分开查看这个结果时,这个透视表变得就有趣了。例如,您可以使用产品颜色,将其放在透视表行中,这时透视表突然就显示了一些有趣的业务见解,如图4-2所示

图4-2 按color分割销售额,看起来更有趣

总数仍然在那里,但现在它是小数字的总和,每个值,连同所有其他的值,都有意义。但是,如果你再仔细想想,你应该注意到这里发生了一些奇怪的事情:这个公式并不是计算我们所要求的。

我们认为这个公式的意思是“所有销售金额的总和”。但在透视表的每个单元格中,公式并不是计算所有销售的总和,而是计算具有特定颜色的产品的销售总额。然而,我们从未指定计算必须在数据模型的一个子集上工作。换句话说,这个公式并没有指定它可以在数据子集上工作。

为什么在不同的单元格中,公式计算不同的值?答案是非常简单的:因为在这个计算上下文下,DAX计算公式。你可以把一个公式的计算上下文想象成围绕在单元格周围可以计算DAX公式的区域。

因为产品的颜色在各行中,透视表中的每一行都可以看到,在整个数据库中,只有特定颜色的产品的子集。这是公式的周围区域,也就是在公式计算之前应用到数据库的一组过滤器。当公式计算所有销售金额的总和时,它不会在整个数据库中计算它,因为它无法选择全部行。当DAX计算出与行值为白色的公式时,只有白色的产品是可见的,因此,它只考虑与白色产品相关的销售。因此,当计算仅显示白色产品的透视表中的一行时,销售金额的总和就变成了所有白色产品的销售额之和。

任何DAX公式都确定了一个计算,但是DAX在一个上下文执行计算,它定义了计算的最终值。公式虽然是一样的,但是值是不同的因为DAX根据不同的数据子集对它进行计算。

现在让我们把年份放在列上,使透视表更有趣。现在的报告如图4-3所示。

图4-3 销售额现在被颜色和年份分割

计算的规则在这一点上应该是清楚的:每个单元格现在都有不同的值,即使公式总是相同的,因为透视表的行和列选择都定义了上下文。事实上,2008年的白色产品销售与2007年的白色产品销售不同。而且,因为您可以在行和列中放置多个字段,所以最好说行上的字段集和列上的字段集定义上下文。图4-4使这个更加明显

图4-4 上下文是由行和列上的字段集定义的

每个单元格有不同的值,因为在行、颜色和品牌名称上有两个字段。行和列完整的字段集合定义了上下文。例如,图4-4中突出显示的单元格的上下文对应黑色、品牌Contoso和日历年。

注意
字段是否在行或列上(或在切片器和/或页面过滤器上,或者在任何其他您可以用查询创建的类型过滤器中)并不重要。所有这些过滤器共同定义单个上下文,而DAX利用这个上下文来计算公式。在行或列上放置一个字段会产生一些美观上的影响,但是在DAX计算值的方式上没有任何变化。

现在让我们看完整的场景。在图4-5中,我们在切片器上添加了产品类别,并在过滤器上添加了月份,我们选择了12月。

图4-5 在一个典型的报告中,上下文定义在很多方式,包括切片器和过滤器

在这一点上,很明显,每个单元计算的值都有一个由行、列、切片器和过滤器定义的上下文。所有这些过滤器共同定义一个上下文,并且在公式计算之前,DAX将该上下文应用于数据模型上。此外,重要的是要知道,并非所有的单元格都具有相同的过滤器集,不仅在值方面,而且在字段方面也是如此。例如,列上的grand total只包含类别、月份和年份的过滤器,但是它不包含颜色和品牌的过滤器。颜色和品牌的字段在行中,它们不会过滤总数。这同样适用于透视表中颜色的子总数:对于那些单元,制造商没有过滤器,来自行的唯一有效的过滤器是颜色

我们把这个上下文称为筛选上下文,正如它的名字所暗示的那样,它是一个筛选表的上下文。您所编写的任何公式都有不同的值,这取决于执行计算的DAX语句上的筛选上下文。这种行为,虽然非常直观,但需要被充分理解。

现在您已经了解了筛选上下文是什么,您知道下面的DAX表达式应该被理解为“在当前筛选上下文中可见的所有销售金额的总和”:

[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )

稍后您将学习如何阅读、修改和清除筛选上下文。到目前为止,我们已经足够深入地了解了这个事实,即筛选上下文总是存在于透视表的任何单元格或报告/查询中的任何值。你总是需要考虑筛选上下文来理解DAX是如何计算公式的。

理解行上下文

筛选上下文是DAX中存在的两个上下文之一。它的搭档是行上下文,在本节中,您将了解它是什么以及它是如何工作的。

这一次,我们使用了一个不同的公式来考虑:

Sales[GrossMargin] = Sales[SalesAmount] - Sales[TotalCost]

您可能会在一个计算列中写出这样的表达式,以便计算毛利润。一旦您在计算列中定义了这个公式,您就会得到结果表,如图4-6所示。

图4-6

DAX对表中所有行进行公式计算,对于每一行,它都按照预期计算出了不同的值。为了理解行上下文,我们需要有点学究式的阅读公式:我们要求减去两列,但是我们从哪里告诉DAX从哪一行来得到列的值呢?您可能会说,要使用的行是隐式的。因为它是一个计算列,所以DAX计算它一行一行,对于每一行,它都会计算一个不同的结果。这是正确的,但是,从DAX表达式的角度来看,要使用哪一行的信息仍然缺失。

实际上,用于执行计算的行并没有存储在公式中。它是由另一种上下文定义的:行上下文。当你定义计算的列时,DAX从表的第一行开始了一个迭代;它创建了包含该行的行上下文并对表达式求值。然后它移到第二行,再次求出表达式。这发生在表格中所有的行中,如果你有100万行,你可以认为DAX创建了100万行上下文来评估这个公式100万次。显然,为了优化计算,这并不是发生的事情;否则,DAX将是一种非常缓慢的语言。不管怎样,从逻辑的角度来看,这就是它的工作原理。

让我们试着更精确一点。行上下文是一个总是包含一行的上下文,DAX在计算列的创建过程中自动定义它。您可以使用其他技术创建行上下文,这将在本章后面讨论,但是解释行上下文的最简单方法是查看计算过的列,其中引擎总是自动创建它。

总是有两种上下文
到目前为止,您已经了解了行上下文和筛选上下文是什么,它们是DAX中仅有的上下文类型。因此,它们是修改公式结果的唯一方法。任何公式都将在这两个不同的上下文中进行计算:行上下文和筛选上下文。

我们将这两种上下文称为“计算上下文”,因为它们是改变公式计算方式的上下文,为相同的公式提供不同的结果。

这是一个非常重要的点,在开始时很难集中注意力:总是有两个上下文,一个公式的结果取决于两者。在你的DAX学习路径的这一点上,你可能会认为这是显而易见的,而且是很自然的。你也许是对的。然而,在书的后面,如果你不记得这两个上下文的共存,你会发现一些公式是很难理解的,每一个都可以改变公式的结果。

测试你的计算上下文理解

在我们继续进行关于计算上下文的更复杂的讨论之前,我们希望通过几个示例来测试您对上下文的理解。请不要立即看这个解释;在这个问题之后停下来,试着回答这个问题。然后阅读解释,并试着去理解它。

在计算列中使用SUM

第一个测试是非常简单的。如果您在销售表中定义了一个计算列,那么会发生什么呢?

Sales[SumOfSalesAmount] = SUM ( Sales[SalesAmount] )

因为它是一个计算列,它将被逐行计算,对于每一行,您将获得一个结果。你希望看到什么数字?从这些选项中选择一个:

  • 这一行的销售额的值,同时也就是每一行的不同值。

  • 所有行的总销售额,也就是说,所有行的值都是一样的。

  • 一个错误;你不能在计算的列中使用求和。

请停止阅读,当我们等待你的有根据的猜测之后再继续。

现在,让我们详细说明当DAX计算公式时发生了什么。您已经了解了公式的含义:“在当前筛选上下文中看到的所有销售金额的总和。“因为在一个计算列中,DAX会逐行计算公式。因此,它为第一行创建一行上下文,然后调用公式计算,并对整个表进行迭代。这个公式计算当前筛选上下文中所有销售金额的总和,所以真正的问题是“当前的筛选上下文是什么?”答案:它是完整的数据库,因为DAX计算这个公式在任何透视表或任何其他类型的筛选条件之外。实际上,当没有筛选条件激活时,DAX将它作为计算列定义的一部分进行处理。

即使有行上下文,SUM也会忽略它。相反,它使用筛选上下文,现在的筛选上下文是完整的数据库。因此,第二种选择是正确的:您将获得总销售额,与所有销售行相同的值,如图4-7所示。

图4-7 SUM ( Sales[SalesAmount] )在一个计算列中,是根据完整的数据库计算的

这个例子说明了这两个上下文共存。他们共同在公式的结果上工作,但是用不同的方式。计算列中使用的SUM、MIN和MAX等聚合函数只使用过滤上下文,忽略行上下文,DAX只使用它来确定列值。如果你选择了第一个答案,就像许多学生通常做的那样,这是完全正常的。关键是,您还没有想到这两个上下文正在一起工作,以不同的方式改变公式结果。当使用直观的逻辑时,第一个答案是最常见的,但它是错误的,现在你知道为什么了。

在度量值中使用列

我们想和你们做的第二个测试有点不同。假设你想要在一个度量中定义毛利润的公式,而不是在一个计算的列中。你有一个带有销售金额的列,另一个用于产品成本的列,你可以写下面的表达式:

[GrossMargin] := Sales[SalesAmount] - Sales[ProductCost]

如果你试图定义这样一个度量值,你应该期待什么结果?

  • 1、这个表达式工作正常,我们需要在报告中测试结果。

  • 2、一个错误,你甚至不能写这个公式

  • 3。你可以定义这个公式,但是当它在透视表或查询中使用时,它会给出一个错误

和以前一样,停止阅读,思考答案,然后阅读下面的解释

在这个公式中,我们使用了Sales[SalesAmount],这是一个列名,也就是销售表中销售金额的值。这个定义缺少什么吗?您应该回忆一下,从以前的观点来看,这里缺少的信息是从哪里获取当前销售额的值。当您在计算列中编写这段代码时,DAX知道在计算表达式时要使用的行,这要归功于行上下文。然而,这个度量值会发生什么呢?没有迭代,也没有当前行,也就是说,没有行上下文。

因此,第二个答案是正确的,你甚至不能写公式;它在语法上是错误的。当你试图输入它的时候,你会收到一个错误

记住,列本身没有值。相反,它对于表格的每一行都有不同的值。因此,如果你想要一个单独的值,你需要指定要使用的行。指定要使用的行的惟一方法是行上下文。因为在这个度量中没有行上下文,公式是不正确的,从而DAX会拒绝它。

在一个度量值中指定这个计算的正确方法是使用聚合函数,如下所列

[GrossMargin] := SUM ( Sales[SalesAmount] ) - SUM ( Sales[ProductCost] )

使用这个公式时,您现在要求通过SUM进行聚合。因此,后一个公式并不依赖于行上下文;它只需要一个筛选上下文,同时提供了正确的结果。

利用迭代器创造一个行上下文

您了解到DAX在定义一个计算列时自动创建行上下文。在这种情况下,引擎会逐行计算DAX的表达式。现在,是时候学习如何使用迭代器在DAX表达式中创建行上下文了

您可能还记得,在第2章“引入DAX”中,所有以x结尾的函数都是迭代器,也就是说,它们遍历表,并对每一行进行评估,最后用不同的算法聚合结果。例如,看看下面的DAX表达式:

[IncreasedSales] := SUMX ( Sales, Sales[SalesAmount] * 1.1 )

SUMX是一个迭代器,它迭代销售表,对于表的每一行,它计算销售额增加10%到它的值,最后返回所有这些值的总和。为了计算每一行的表达式,SUMX在Sales表上创建行上下文,并在迭代期间使用它。DAX在包含当前迭代行的行上下文中计算内部表达式(SUMX的第二个参数)

需要注意的是,在完整的计算流程中,SUMX的不同参数使用不同的上下文。让我们更仔细地看一下相同的表达式

= SUMX (
 Sales, ← 外部上下文
 Sales[SalesAmount] * 1.1 ← 外部上下文 + 新的行上下文
)

第一个参数,Sales,是使用来自调用者的上下文来计算的(例如,它可能是一个透视表单元格,另一个度量值,或者查询的一部分),而第二个参数(表达式)是使用外部上下文加上新创建的行上下文来计算的。

所有迭代器的行为都是一样的:

1、为作为第一个参数接收的表的每一行创建一个新的行上下文

2、在新创建的行上下文中(加上在迭代开始之前存在的任何其他上下文),对表的每一行进行评估。

3、聚合在步骤2中计算的值。

重要的是要记住,原始上下文在表达式中仍然有效:迭代器只添加新的行上下文;它们不会以任何方式修改现有的。这条规则通常是有效的,但是有一个重要的例外:如果前面的上下文已经包含了同一个表的行上下文,那么新创建的行会上下文隐藏了先前存在的行上下文。我们将在下一节中更详细地讨论这个问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容