第04章:基本列表筛选
介绍
通过前面几章的介绍,你已经了解了DAX语言的基础知识。知道如何创建计算列和度量,并且对在DAX中使用的常用函数有了较好的理解。
本章中,将转到DAX更高一级水平知识:即学习DAX综合语言的一个坚实的理论背景,之后,你就能成为一个真正的DAX高手了。
到目前为止,通过所获得知识的了解,可以创建许多有用的业务报告。但是,还需要学习列表筛选(行筛选和列筛选),以便创建更复杂的报告。
列表筛选是DAX中所有高级特征的基础。
这里有几句提醒:列表筛选是一个简单的概念,你很快就会学习并理解它。不过,还需要基于一些微妙的细节考虑,以便彻底了解它。否则,在你的DAX学习路径中,你会随时都会感到迷茫。
我们在公共和私人课程中向许多用户教授DAX。我们知道这是绝对正常的。在某种程度上,你会觉得公式像魔法一样有效,因为它们将起到应有的作用。
但是,你可能并不明白这其中的内部原因是什么。不要担心:你将在一个好公司里信任你的工作,大多数DAX商学院的学生都达到了这一水平,许多其他学生将来也会达到这个目标。
首先,这仅仅意味着列表筛选对他们来说还不够清晰。在这一点上,解决方案很简单:读完本章后,再次回到本章,再读一遍,你可能会发现一些在你第一次读到时错过了的新东西。
译者:这是事实,许多人就停留在看第一篇。这个简体译本,我是2018年9月份就完成了的,期间也只是看了一遍,现在回过头来,发现好多理解还是不对。
此外,一方面,函数CALCULATE在使用列表筛选时起着重要的作用,这可能是DAX里最强大、最难以学习的函数。我们将在第5章 “理解CALCULATE 和 CALCULATETABLE中介绍”,然后在本书的其余部分也将使用到它。所有这些,如果没有扎实的列表筛选背景,理解CALCULATE会有难度。
译者:参阅本人《CALCULATE庖丁解牛108式》系列(未完)。大多数内容也是来源于《DAX圣经》,但做了最通俗的、啰嗦式讲解。这里已经引出了DAX的两个做核心的知识:筛选+计算
另一方面,在不尝试实验、建立在实际操作的情况下理解列表筛选的重要性,并使用CALCULATE几乎是不可能的。
因此,这一章和随后的一章,是我们在以前的书中也曾经写过的,你需要标记出来,或者把书页的角折起来作为记号。
DAX的计算环境介绍
首先,让我们从理解什么是当前列表筛选开始。
任何DAX表达式都在一个筛选条件下进行计算。筛选是公式计算所在的“环境”。
例如,考虑一个很简单的公式:例如 [Sales Amount]度量:
[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )
你已经知道,这个公式在销售表中(不涉及其他表)计算[Quantity] * Sales[UnitPrice]--数量乘以价格的值的总和。可以将该度量拖入透视表中,并查看结果,如图4-1所示:
这个数字看起来没有一点意义,对吧? 但是,如果你能够仔细想想,该公式它计算的到底是什么?它计算的应该是:销售额的总和。这是一个最大的数字(总数),这并没有实际意义。
但是,当我们使用一些列来分解这个总数,并处理这个分解的结果时,这个数据透视表就开始变得更有用了。例如,可以使用产品颜色,将其放在行中,而透视表(数值区)会突然显示出一些有意义的业务洞察,如图4-2所示:
总数(最后一行总计—Grand Total)还在,但现在,每个行值显示了更小的值([Sales Amount]列的每个行值)以及每个值的总和(Grand Total),这些值都有一个实际意义。
然而,如果你再仔细想想,应该注意到,这个奇怪的事情的发生,同时也是我们心中的疑问:[Sales Amount]度量,现在已经没有按我们想象的方式在计算。
本来我们认为,这个公式是计算“所有销售额的总和--[Sales Amount]的总数”,但在数据透视表的每个值单元中,公式结果并不是计算所有销售的总和,而是计算了具有特定COLOR--颜色行所对应的每个products-产品的销售总额。
然而,问题是,我们自始至终都没有指定(定义)计算必须在数据模型的该子集上工作。换句话说,并没有明确指定公式按这种方式来处理数据的子集。更通俗的讲:你并没有这种想法或按这个想法向DAX下达过任何一个该如何执行的命令。
但是,为什么公式会在不同的行(透视表单元格)中却计算出了不同的值呢?
答案很简单,事实上:因为DAX计算公式是在当前计算的环境下(当前筛选环境下)。你可以把一个公式的当前列表筛选,看作是公式计算所在的单元区域(当前列表环境)。
由于product color--产品颜色被放置在透视表的行上,因而透视表中的每一行上将出现所有的product color列值,相当于从整个数据库中,筛选出了该产品的子集:即特定的color--颜色(每一行是一种颜色,而不是整个颜色)。这才是公式所在的列表环境区域(当前列表子集),计算就发生在这个列表子集里。
也就是说,这是在公式计算之前,就已经应用于数据库的一组筛选器。
当公式计算 [Sales Amount]时,其实际还是计算其所有值,但这时候,因为并没有查询整个数据模型所有行的选项,因而,它不会在整个数据库中计算(计算整个值)。比如,当DAX公式计算到行值为White--白色所对应的行时,只有White白色的产品是可见的,因此,计算只针对那些白色产品所对应的销售,计算透视表中的其他任何不是白色产品的行都被忽略(即其他颜色的行是被筛选掉了的),这时候, [Sales Amount] 实际还是计算其“所有值”,只不过,现在这个“所有值”已经成为只有白色产品的[Sales Amount]。
译者:原文的该处,比较拗口。因为这段话比较重要,稍作了一点通俗、简体式修改。希望更容易理解。
任何一个DAX公式都可以理解为:定义一个计算,但是,DAX在一个当前筛选中对这个计算进行了改变,从而定义并决定了DAX最终的计算值,这就是一个数据模型里DAX公式的奥秘和强大之处:
相同的DAX公式,可能会产生不同的结果值。因为DAX针对的是不同的数据子集在计算。
唯一的情况下,如果公式的行为方式已经被定义为Grand total—求全局最大的总数。在这个级别上,因为没有筛选,将针对整个数据模型计算。
请注意
为了简单起见,在这些示例中,我们使用了一个透视表。显然,也可以使用查询定义一个当前列表筛选(这将在后续的章节中进一步介绍)。现在,最好保持该简单方式,只考虑数据透视表,以便对概念有一个简化、直观的理解。
现在,让我们把[YEAR]-年份放置到透视表的列上,使透视表更丰富一些。该报告如图4-3:
首先,通过前面说明的DAX规则,应该清楚的一点: 即使任然还是同一个公式,现在每个值单元里已经都显示出不同的值。这是因为,数据透视表被行和列两个筛选条件所定义。显然,2008年白色产品的销售与2007年的白色产品的销售不可能是两个相同的值。
此外,因为可以同时在行和列中放置更多个字段,所以最好表述应为:透视表行上的字段集和列上的字段集共同定义出筛选子集。
图4-4说明了这一点:
表中每个单元格都有不同的值。因为行上有两个字段,[color[ 和 [brand] name-颜色和品牌名称列。行和列上的完整字段定义了筛选计算子集。
例如,图4-4中突出显示的单元格的行列筛选对应于:
color=Black;
brand=Contoso
以及Calendar=Year 2007。
请注意
字段是否在行上或列上(或在切片器/页面筛选器上,或在任何其他类型的筛选器中,甚至是由查询创建)这都不重要。重要的是:所有这些筛选器都只是为了有助于定义出一个单一的当前筛选,这是DAX公式用来计算的集。将字段放置在行或列上(任何筛选),只是一种布局方式的结果,DAX计算值的方式没有任何变化。
译者:初学者往往把DAX计算值的方式(总是按固定的一种方式计算,这是由内部引擎的性质决定的)与筛选方式混淆。正如前面所说:DAX计算值的方式永远没有变化—总是计算全集的值—计算一整列的值和计算只有一行的列值并没有什么区别。这在后续的高级列表筛选计算中将进一步加深学习。也可以参阅本人的《CALCULATE的108式》系列。
现在,让我们看看整个透视表。在图4-5中,我们在一个切片器上添加了产品类别-Catagory,在筛选器上添加了monch-月份名称,并选择了12月份。
图4-5在一个典型报告中,筛选由许多方面所定义,包括切片器和筛选器。
显然,在这一点上,计算每个单元格中的值具有由行、列、切片器和筛选器等共同定义的当前列表筛选。所有这些筛选器都有助于在公式计算之前将DAX应用于数据模型的当前列表筛选定义。
此外,重要的是要了解,不是所有的单元格都具有相同的筛选器子集,不仅是在值方面,而且在字段方面也是如此。
例如,列上的Grand total只包含类别、月份以及年份的筛选器,但是它并不包含[color[ 和 [brand]--颜色和品牌的筛选器。因为颜色和品牌的字段在行上,它们并没有筛选Grand total--总数。这同样适用于数据透视表中的颜色小计:这些单元格值没有筛选制造商,唯一有效的筛选来自行的Color-颜色。
我们将此筛选称为列表筛选,顾名思义,它筛选表或列表。
任何一个公式,由于你的业务维度需求不同都会有不同的值,这取决于DAX用来进行计算的当前筛选列表。这种行为虽然很直观,但需要去很好地理解它。
现在,已经了解了什么是一个当前列表筛选,知道以下DAX公式表达式应该解读为:“当前筛选条件下可见的销售金额的总和”:
[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )
稍后将学习如何读取、修改和清除列表筛选。到目前为止,已经足够了解这个事实,即当前列表筛选始终存在于数据透视表的任何单元格或报告/查询中的任何值里。你总是需要考虑当前列表筛选,以便理解DAX公式是如何计算的。
了解行筛选
列筛选是在DAX中存在的两个筛选中的一个,它的伙伴则是行筛选,在本节中,将了解它是什么?以及它是如何工作的。这次,我们使用一个不同的公式:
Sales[GrossMargin] = Sales[SalesAmount] - Sales[TotalCost]
你可能会在计算列中,按顺序写出这样的表达式:计算[GrossMargin]-毛利。只要在计算列中定义这个公式,就会得到结果列表,如图4-6所示:
图示结果显示DAX公式计算了表中所有行的值,或者说,对于每一行,它计算的值与预期的不同。
为了理解这个行环境,在阅读时,我们需要先使用比较笨的计算公式:即要求两列相减,但我们告诉DAX具体是列表中的哪一行的值相减了吗? 没有!你可能会说,这时使用的行是隐式的,因为它是一个计算列,因此,DAX将逐行计算,对于每一行,它都将得出一个不同的结果值,这中理解是正确的。但是,从DAX表达式的角度来看,关于具体使用哪一行的信息仍然缺失(译者注:并没有定义或声明)。
实际上,这里用于执行计算的行并不存储在公式内部。它是由另一种筛选定义:行筛选。
当定义某个计算列时,DAX从表的第一行开始迭代:它创建了包含该行的行值筛选(译者注:更好的称呼应该为:值列表筛选),并对表达式进行了计算。然后它移到第二行,再次对表达式求值,依此循环,直到计算完表中的所有行。如果有一百万行,你可以认为,DAX运行了100万次行筛选来计算公式100万次。
显然,为了优化计算,这并不是实际发生的情况:否则,DAX将是一种非常缓慢的语言。
但是,不管怎样,从逻辑的角度来看,这就是它的工作原理。
让我们试着更准确些解释它:行筛选是一个始终包含一行的筛选,而DAX在创建过程中会自动定义它。比如计算列,可以使用其他技术创建一个行筛选,这将在本章后面讨论,但是,解释行筛选的最简单方法是查看计算列,在这些列中,引擎总是自动创建它。
行、列筛选关系共存
到目前为止,你已经了解了行筛选和列表筛选是什么。它们是构建DAX唯一的计算环境。因此,它们也是修改公式结果的唯一方法。
任何DAX计算公式都包含这两个不同的筛选:行筛选和列筛选环境。
我们将这两个筛选称为“计算筛选”,因为它们是改变公式的计算方式的筛选,即为相同的公式提供不同的结果。
一个非常重要的问题是:你很难同时思考两个筛选,但公式的结果却又总是取决于这两种筛选。在这一点上,你可能认为这是显而易见的,非常自然的,在你学习DAX的过程中,也许经常会遇到,这种想法也许是对的。
但是,在后面章节内容中,你会发现,如果你不记得两个筛选的共存关系,即其中的任一种筛选都可能会改变公式的结果,这对于你今后书写DAX将是一大挑战。
测试对行、列表筛选的理解
在我们继续讨论关于计算筛选的更复杂的讨论之前,我们希望通过几个例子来测试对筛选的理解。请不要立即看解释:在问题结束后,试着回答它。然后阅读解释,这样更会有意义。
计算列中使用SUM(聚合计算)
第一个测试非常简单。如果你用这个代码定义一个计算列,在销售中,会发生什么?
Sales[SumOfSalesAmount] = SUM ( Sales[SalesAmount] )
因为它是一个计算列,对于每一行,它将逐行计算,将得到一个个结果。你想看什么号码? 从这些选项中选择一个:
(1)这一行的SalesAmount的值,即每一行的不同值:
(2)所有行的总销售额,即所有行的 一个相同值:
(3)一个错误:不能在计算列中使用SUM。
请停止阅读,我们等待你的猜测结果,然后继续。
现在,让我们详细说明当这个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:需要引用Sales表中SalesAmount列的值来计算。这个定义缺少什么呢? 你应该还记得,从前面的公式参数中,这里缺少的信息是:从哪里获取到当前[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是一个迭代器,它遍历Sales表(函数第一个参数为某个表),对于表的每一行,它计算销售额增加了10%(*1.1)的值,最终返回所有这些值的聚合。为了计算每一行的表达式,SUMX在Sales表上创建行筛选,并在整个迭代期间使用它。在包含当前迭代行的行筛选中,DAX将计算内部表达式(SUMX的第二个参数:表达式)。
需要注意的是,在整个计算流程中,SUMX的不同参数使用了不同的筛选。让我们深入分析一下这个表达式:
= SUMX (Sales, ←外部环境
Sales[SalesAmount] * 1.1 ← 外部筛选+新的行筛选。内部环境
第一个参数Sales是引用来自数据模型的列表筛选 (例如,它可能是一个透视表单元,另一个度量,或查询结果列表的一部分),而第二个参数(表达式)是使用外部筛选加上新创建的行筛选来计算的。
所有迭代器都具有相同的方式:
l 为第一个参数的表的每一行创建一个新的行筛选;
l 第二个参数为新创建的行筛选(以及在迭代开始之前存在的任何其他筛选),以此计算表的每一行:
l 聚合步骤2中计算的值。
重要的是要记住,原始的筛选表达式仍然存在并有效,迭代器只是添加了一个新的行筛选:它们不以任何方式修改现有的筛选。
这个规则通常是有效的,但有一个重要的例外:如果前面的情况下已经包含一个相同的表的行筛选,则新创建的行筛选将隐式先前存在的行筛选。我们将在下一节中更详细地讨论这个问题。
使用 EARLIER
在同一个表上嵌套多个行筛选的场景可能看起来很少见,但实际上,这种情况却经常发生。让我们以一个例子来看看这个概念。
假设你希望计算每一种产品的价格与其他产品的价格比较(比如出现的次数),这将产生一种基于价格的产品排名。
为了解决这个问题,我们使用了前一章学过的FILTER函数。可能还记得,FILTER是一个迭代器,它循环遍历表的所有行,并返回一个包含满足第二个参数条件定义下的新表。例如,如果需要检索表中产品价格高于100美元的产品,可以使用:
= FILTER ( Product, Product[UnitPrice] > 100 )
请注意
细心的读者会注意到,FILTER是一个迭代器,因为Product[UnitPrice] > 100这个表达式中,假如有且仅当一个有效的行筛选存在于产品表中,就可以被直接计算。否则,单位价格的有效值将是不确定的。FILTER是一个迭代器函数,它在第一个参数中为表的每一行都将创建一个行筛选,因而也会包含第二个参数中的计算条件,从而使得在这个条件下的行计算成为可能。
现在,让我们回到我们最初的例子:创建一个计算列。
Product[UnitPriceRank] =
COUNTROWS (
FILTER (Product,
Product[UnitPrice] > PriceOfCurrentProduct)) –大于当前产品的结果
我们希望计算出那些比当前价格高的产品的数量。如果你想使用名称PriceOfCurrentProduct(大于当前产品的价格)作为计算的当前产品的价格的筛选条件,那么,很容易看到这个pseudo-伪DAX公式将采取一切必要的行为来达到这一计算目的:
我们将公式正确表述为:希望FILTER只返回价格高于当前值的产品,然后COUNTROWS计数这些产品。这时,唯一剩下的问题是用有效的DAX语法来表示、替换这个:PriceOfCurrentProduct--当前产品的价格。
定义“current”-现在的、当前的行筛选条件,我们的意思是想告诉DAX:计算到这一列的每一行时引用该列的当前行值。这似乎比你想象的要难。
因为是在Product—产品表中定义这个新的计算列,因此,DAX在一个行筛选中计算表达式(具有隐式行筛选)。但是,表达式使用了FILTER()函数,它在同一个表(产品表)上创建了一个新的行筛选。
实际上,前一个表达式的第5行中使用的Product[UnitPrice]是筛选器--公式内部所迭代的当前行的单价,因此,这个新的行筛选隐式了计算列引入的原始行筛选(Product[UnitPrice]列自带的隐式行筛选)。
你发现问题了吗?我们希望访问当前单元格(行)的价格来计算,但不希望使用由此引入的这个行筛选。相反,还是希望使用原有的计算列中的隐式行筛选。
DAX提供了一个函数来表示这种当前行的行为: EARLIER。
通过使用EARLIER表示 当前行筛选 而不是最后定义的行筛选来检索列值。因此,可以使用EARLIER(Product[UnitPrice])来表达:PriceOfCurrentProduct--当前产品的价格。
EARLIER是DAX中最奇怪的函数之一。许多用户在使用EARLIER时候总感到疑惑,这也许是因为没有考虑到行筛选行为,而且也没有考虑到通过在同一个表上创建多个迭代来嵌套行筛选的事实。
实际上,EARLIER是一个非常简单的函数,它将会在很多场景下有用。下面的代码是最终解决场景:
Product[UnitPriceRank] =
COUNTROWS (
FILTER (Product,
Product[UnitPrice] > EARLIER ( Product[UnitPrice] ))) + 1
在图4-8中,可以看到Product表中定义的计算列,该列已按降序对单价进行了排序:
因为有14个单价相同的产品,因此排名总是1,接下来才是第15款与此前价格不同的产品的排名,结果是15,这有4个连续价格相同的产品。我们建议你仔细研究和理解这个小示例,因为它是一个非常好的测试,它可以检查使用和理解行筛选的能力,以及如何使用迭代器(在本例中是FILTER)创建它们,以及如何通过使用EARLIER方法访问外部的值。
请注意
EARLIER接受第二个参数,即跳过的步骤数,这样可以跳过两个或多个行筛选。此外,还有一个名为EARLIEST的函数,它允许直接访问为表定义的最外层的行筛选。坦白说,EARLIER 或EARLIEST的第二个参数都不是经常使用的:虽然有两个嵌套的行筛选的场景很常见,但是有三个或更多的场景是很少发生的事情。
在抛开这个示例之前,值得注意的是,如果想要将这个值转换为一个更好的排序(也就是说,从1开始并依次增加1的值,即创建一个1、2、3……序列),然后计数价格而不是计数产品就足够了。
通过在前面章节中的函数学习,帮助你写出如下公式:
Product[UnitPriceRankDense] =
COUNTROWS (
FILTER ( VALUES ( Product [UnitPrice] ),
Product[UnitPrice] > EARLIER ( Product[UnitPrice] ))) + 1
在图4-9中,可以看到新的计算列结果:
我们强烈建议学习和理解EARLIER,因为你将经常使用它。然而,重要的是要注意到在许多场景中可以使用变量来代替—以避免使用EARLIER。此外,谨慎使用变量会使代码更容易阅读。这取决于你。
例如,可以使用这个表达式来计算之前的计算列:
Product[UnitPriceRankDense] =
VAR CurrentPrice = Product[UnitPrice]
RETURN
COUNTROWS ( FILTER (
VALUES ( Product[UnitPrice] ), Product[UnitPrice] > CurrentPrice)) + 1
该公式使用一个变量,将当前的Product[UnitPrice]存储在CurrentPrice变量中,稍后将使用(RETURN取出)它来执行比较。
你可以为变量提供一个有用的名称,以使代码更容易阅读,这样,不必每次读写表达式时需要理解遍历行筛选传递的计算。