《DAX权威指南》简体版 (第四章)02
附加:EARLIER与EARLIEST运用讲解
附加:SUMX类迭代器函数
附加:FILTER、ALL与筛选转换
译者:从本章起,不再提供额外附加的内容,保持原版内容。
在前面的示例中,使用FILTER作为筛选某个表的一种方便的方式。FILTER是一个非常常见的函数,每当想要应用一个筛选器,它将进一步限制现有筛选。
假设希望创建一个计算Red-红色产品数量的度量。到目前为止,用你获得的知识可写出一个简单的公式:
[NumOfRedProducts]:=
COUNTROWS (
FILTER (Product, Product[Color] = "Red"))
这个公式很好用,你可以将它放置在一个透视表中: 例如,将品牌放在行上以生成图4-10所示的报表。
关系列表筛选
在继续这个示例之前,先停下来仔细思考一下DAX是如何计算这些值的。品牌是产品表的一列。[NumOfRedProducts]引擎在品牌行筛选定义的每一个单元内计算值。因此,每个单元格都显示了相对应的行列值为红色产品的数量。
这是因为,当要求迭代产品时,实际上是在要求迭代Product--产品表,因为它在当前筛选器条件下是可见的,即只包含具有特定品牌的产品(当然包括红色产品)。这看起来很琐碎,但是记住它比忽略它要有用。如果将一个切片器放在包含颜色的工作表上,这一点就更加明显了。
在图4-11中,我们创建两个相同的透视表,并都使用Color-颜色列作为切片器。你可以看到左边的颜色选择的是Red-红色,其结果与图4-10是一样的,而右边的主元表结果则是空的,因为切片器的颜色是**Green-绿色**的。
图4-11 DAX对NumOfRedProducts进行了计算,考虑到由切片器定义的外部筛选。在右边的透视表中,传递到FILTER()的Product表只包含**Green-绿色** 产品,因为没有同时为红色和绿色的产品,所以它总是计算为空(也就是说,FILTER的结果没有用于COUNTROWS处理的任何行)。
这个例子的重要部分是,在同一个公式中,有一个来自外部的切片器筛选(透视表单元,它同时接受切片器和公式中引入的行筛选的影响。两种筛选同时工作,并修改公式结果。DAX使用列表筛选,以及在迭代过程中筛选行的行筛选来计算产品表。
此时,可能需要定义另一个公式,用以返回仅包含红色产品的数量,而不考虑在切片器上所做的选择。因此,这需要忽略在切片器上做出的选择,并且总是返回红色产品的数量。
可以通过使用ALL函数轻松实现这一点。
ALL()返回一个表,该表忽略了行、列筛选。也就是说,它总是返回表的所有行。
你可以定义一个[NumOfAllRedProducts[的新度量,使用如下表达式:
[NumOfAllRedProducts]:=
COUNTROWS (
FILTER (ALL ( Product ),Product[Color] = "Red"))
这一次,我们不再只引用Product-产品表,而是使用ALL (Product),这意味着要忽略现有的行、列表筛选,并且总是遍历所有产品表。结果肯定不是我们所期望的,如图4-12所示:
图4-12,[numofallred]返回奇怪的结果。
有一些有趣的事情要注意在这里:结果总是99,不管行中选择的是什么品牌,**尽管左侧透视表中的品牌内容与右边的不同。**
让我们研究一下这两个主题:
首先,**99**是数据库中红色产品的总数。使用ALL()的筛选器后,从产品表中删除了所有的筛选器。当然,也同时去掉了Coler-颜色筛选器以及品牌的筛选器,这是一种非理想的结果。 但是,不幸的是,我们并没有其他的选择,因为目前为止,我们拥有的DAX知识有限。ALL()非常强大,但它同时也是一个孤注一掷的功能:如果使用,它将删除所有的筛选器,而不是只选择删除某一部分。更具体地说,我们想要去除颜色上的筛选器,让所有其他的筛选器保持不变。
在下一章中,将学习如何通过引入CALCULATE()来解决这个问题。
其次,更容易理解一些:因为选择了绿色,所以只出现绿色产品的制造商,而不是所有产品的制造商。因此,最右边的数据透视表显示绿色产品制造商,以及数据模型中的红色产品总数。发生这种情况,是因为用于填充数据透视表的行、列,是在原始的筛选器环境--制造商的列表中计算的,它包含一个颜色为绿色的筛选器。一旦定义出了坐标轴,就会计算出这些值,结果总是返回99。
*请注意:*DAX的这种行为被称为“AutoExists logic--自动逻辑”。它看起来很自然,因为它隐式了不存在的值,尽管有些内部的复杂性。在第10章“高级计算筛选”中,我们将使用一定篇幅内容来详细描述这种“AutoExists logic”自动存在的行为。
我们现在不想解决这个问题。当在接下来学习CALCULATE部分时,解决方案就会出现,它有特定的特性行为来解决像这样的场景。
到现在为止,我们已经使用这个示例来说明,可能会发现来自相对简单的公式的奇怪结果,这是因为:
相同的表达式中,存在列表筛选和行筛选的交互和共存。
### ALLEXCEPT()
假设在一个表中有12个列,你想要将ALL( )应用到12个列中其中的一个或多个列。然后,你可以使用ALLEXCEPT(<表>,< 要取消筛选的列1>,< 要取消筛选的列2>…)
例如:与列出Sales-销售表中除[ProductKey]列之外的每一列相同:
ALL (Sales[OrderQuantity], Sales[UnitPrice], Sales[ProductCost], Sales[CustomerKey],Sales[OrderDate], Sales[MonthNum],
…
< ProductKey之外的所有列>)
在这种情况下,使用ALLEXCEPT( )更方便: ALLEXCEPT (Sales,**Sales[****ProductKey]**)
另一个不同之处,除了方便之外,如果你随后添加一个新的列到Sales表,ALLEXCEPT( )将“提取它”,并将ALL( )行为同样应用到它,而不需要你更改度量公式。在编辑公式之前,ALL(<列出每个列>)的方法显然不会应用到新列。*
涉及不同表之间的筛选
首先,刚刚开始学习了筛选,这让我们发现了一些有趣的(和令人惊讶的结果。可能你已经注意到,我们故意只使用一个表:只有一个产品表,只需要面对同一表达式中进行行筛选和列表之间的交互。
其次,事实上数据模型很少只包含一个表。很有可能数据模型中存在许多具有关系的表。因此,一个有趣的问题是“这两个筛选如何通过这种关系联系?” 此外,因为关系有一个方向,我们需要了解关系的一端与多端发生了什么”。
最后,为了增加点难度,请回想一下,关系可以是单向的,也可以是双向的,这取决于如何定义交叉筛选器关系本身的关系方向。
如果在关系的多端创建一个行筛选,你希望它能让你使用一端的列吗?
此外,如果在关系的一端创建了行筛选,那么,是否希望能够从关系的多端访问表中的列呢?
这种情况如果针对的是列表筛选呢?那又会如何呢? 比如希望在多个表上放置一个筛选器,并将其传递到另一端的表中?
任何这些答案都可能是正确的,但我们感兴趣的是学习DAX如何在这些情况下的表现,也就是说,理解DAX语言如何通过关系定义筛选的传递。
正如你将要学习的,在筛选和关系中有一些微妙的相互作用,学习它们需要一些耐心。为了检查这个场景,我们使用一个包含6个表的数据模型,可以在图4-13中看到。
图4-13在这里可以看到用于学习筛选和关系的交互的数据模型。
**关于这个模型,有几点需要注意:**
• 从ProductCategory表和Product Subcategory表开始,再从Product表到Sales表,为一对多的关系链:
• 唯一的双向关系是sales-销售表和Product-产品表之间的关系:
• 所有剩余的关系都被设置为单向的交叉筛选方向。
既然我们已经定义了该模型,那么,让我们开始观察研究筛选是如何通过一些DAX公式来表现的。
行筛选和关系
行环境之间的相互作用和关系很容易理解,因为这不需要理解:
行筛选不以任何方式交互,至少不会自动交互。
假设希望在Sales表中创建一个计算列,其中包含存储在事实表中的单价与产品表中存储的产品价格之间的差异。你可以试试这个公式:
Sales[UnitPriceVariance] = **Sales**[UnitPrice] - **Product**[UnitPrice]
这个表达式使用来自两个不同表的不同的两列,而DAX在同一个行筛选(译者注:[UnitPrice]列的隐式行筛选)中对它进行了计算。因为在该表中引用了计算列(**Sales**[UnitPrice]),产品表是销售表关系的一端(销售表为关系的多端),所以,可能期望能够获得当前产品表中相关行的单价(**Product**[UnitPrice])。
不幸的是,这种情况并没有发生。因为Sales中的行筛选不会自动传递到产品表,如果试图使用上面的一个公式来创建计算列,那么它将返回一个错误。
如果希望从关系的多端的表中访问关系的一端的列,就像本例中的情况一样,必须使用RELATED函数。RELATED接受一个列名称作为参数,并从当前行筛选开始,从多对一方向的一个或多个关系中找到相应行中的列的值。下面是前面那个公式的正确写法:
Sales[UnitPriceVariance] = Sales[UnitPrice] - RELATED ( Product[UnitPrice] )
RELATED行为,当行筛选在数据列表关系的多端。如果主动的行筛选(引用)在关系的一方,那么你不能使用它(从一对多方向引入数据),因为可能会被检测到关系的许多行。在这种情况下,需要使用另一个跟RELATED类似的函数:RELATEDTABLE。使用RELATEDTABLE从关系的一端,返回所有的行(在多端表)与当前相关的列值。例如,如果要计算每个产品的销售数,可以使用以下公式, 在产品表里定义一个计算列:
Product[NumberOfSales]= COUNTROWS ( RELATEDTABLE ( Sales ) )
这个表达式计算对应于当前产品的Sales表中的行数。
译者:你可以在任何一个关系参数表里写出该公式,以计算符合某个事实表计算度量的条件数。其通用公式为:
= COUNTROWS ( RELATEDTABLE ( 事实表 ) )
可以在图4-14中看到结果:
图4-14关系表在关系的一端有行筛选时**非常有用**。值得注意的是:**RELATED 和 RELATEDTABLE表都可以遍历一长链的关系来收集它们的结果:不限于单节点连接关系(一段关系)。**例如,可以创建具有相同的代码的一个计算列,但这一次,我们在Product Category-产品类别表里创建它:
'Product Category'[NumberOfSales] =
COUNTROWS ( RELATEDTABLE ( Sales ) )
结果是该类别的销售数,它遍历了从产品类别到产品子类的关系链,然后再到产品表最终回到销售表。
*请注意*
RELATED 和RELATEDTABLE的一般规则的唯一例外是一对一关系。如果两个表共享一个1:1的关系,那么,可以使用RELATED和RELATEDTABLE两个中的任何一个,得到一个列值或一个单行的表,这取决于你用的哪一个函数。
对关系链的唯一限制是,所有的关系都必须是相同的类型(即一对多或多对一),所有的关系都朝着同一个方向。
如果有两个表,通过一对多和多对一关联后,有一个桥表在中间,既非一对多,也不是多对一。因此,如果使用1:1的关系作为表现方式的同时,可以再有一个1:1的一对多的关系而不中断关系链。
让我们以一个例子来说明这个概念。你可能认为客户表与产品表有关,因为客户表和销售表之间存在一对多的关系,然后是销售表和产品表之间存在多对一关系。因此,关系链将这两个表连接起来。
然而,这两种关系并不是同一方向的。我们把这种情形称为多对多关系。换句话说,一个客户与许多产品(购买的产品)有关,而一个产品也可以同时与许多客户(购买产品的人)有关。稍后将了解如何使多对多关系工作的细节:现在,让我们关注行筛选。如果尝试通过多对多关系应用RELATEDTABLE,结果可能不是所期望的。例如,在产品列中考虑一个计算列:
Product[NumOfBuyingCustomers] = COUNTROWS ( RELATEDTABLE ( Customer ) )
可能期望看到每一行里,购买该产品的客户数量。出乎意料的是,结果将永远是18869,即数据库中客户的总数,如图4-15所示。
图4-15如果你想遍历一个多对多的关系,RELATEDTABLE将不工作RELATEDTABLE无法跟随该关系链,因为他们不是在同一方向:一个是一对多,另一个是多对一。
因此,产品表无法传递筛选到客户表。值得注意的是,如果公式在相反的方向,也就是说,计算每个客户购买产品的数量,其结果将是正确的:因为每一行有一个不同的数字代表客户采购的产品的数量。这种行为的原因不是一个筛选器的传递环境,但相反,筛选转换在RELATEDTABLE内隐式计算。为了完整性,我们添加了这最后的注意。但还没有时间去详细说明:阅读第五章“理解CALCULATE和CALCULATETABLE之后, 你将会有一个更好的理解”
列表筛选与关系
你已经了解到,行筛选不与关系交互,如果想要遍历关系,则有两个不同的函数可供使用,这取决于在访问目标表时所使用的关系的哪一方。与筛选计算环境的行为方式不同的是:它们以自动方式相互作用关系,以及它们有不同的行为取决于你如何设置筛选的关系。一般规则是,筛选通过关系传递,如果在关系本身上设置的筛选方向是同一个方向,则使传递可行。 通过使用简单的一个数据透视表,可以很容易地理解这种行为。在图4-16中,可以看到一个透视表浏览到目前为止使用的数据模型,其中定义了三个非常简单的度量。
[NumOfSales] : = COUNTROWS ( Sales )
[NumOfProducts] : = COUNTROWS ( Product )
[NumOfCustomers] := COUNTROWS ( Customer )
筛选器在产品的颜色列上。产品与销售是一对多关系的来源,因此行、列表筛选从产品表传递到销售表,可以看到这一点,因为NumOfSales度量只计算具有特定颜色的产品的销售,同样,NumOfProducts显示每种颜色的产品数量,并且每一行(颜色)的不同值也是所期望的,因为筛选器位于正在计算的同一张表上。
另一方面,numofcustomers所计算的客户数量,总是显示相同的值,即客户总数。这是因为客户和销售之间的关系,你可以看到如图4-17,箭头指向的方向。
筛选器从产品表开始,然后传递到销售表(从产品到销售的箭头,这是启用的),但是,当它试图传递给客户表时,没有找到让它继续的箭头方向。因此,它停了下来。单向关系允许在单一方向上传递行、列表筛选,而不允许两者同时使用。
你可以认为关系中的箭头就像信号灯。如果它们被激活,那么信号灯为绿色,传递就会发生,如果没有启用箭头,则信号灯为红色,筛选器不能传递。箭头总是从一端到任意关系的多端。
你可以选择让它从多端到一端。如果你让箭头失效,那么传递就不会从多端到一端。如果查看图4-18所示的透视表,可以更好地理解该行为。这次我们不使用product color--产品颜色,而是通过customer education。
图4-18 customer education被筛选,产品表也被筛选。
这一次筛选器从客户开始。它可以传递到销售表,因为箭头在对应关系中已被启用。然后,从销售表,它可以进一步传递到产品表,因为销售表和产品表的关系是双向的。
现在你添加到模型类似的度量,已计算出子分类的数量,如下:
NumOfSubcategories := COUNTROWS ( 'Product Subcategory' )
将其添加到报表中,将看到子类别的数量没有被customers cannot筛选,如图4-19所示。
图4-19如果关系是单向的,客户表不能筛选子类别表。这是因为,产品和产品子类别之间的关系是单向的,即它只能让筛选器在一个方向上传递。只要你启动了从产品开始,然后到产品子类别的箭头,这时,将看到筛选器传递,如图4- 20所示。
引入VALUES
前面的例子非常有趣,因为它展示了如何通过使用筛选的方向来计算购买产品的客户数量。然而,如果你有兴趣只计算客户的数量,还有一个有趣的替代,我们作为契机,引入另一个强大的函数功能:VALUES。
VALUES是一个表函数,它只返回一个列表,其中包含当前在列表筛选中可见的所有列值。我们将在后面介绍VALUES的许多高级用法。现在,开始使用VALUES来了解它的行为是很有帮助的。
在之前的透视表中,可以修改NumOfCustomers定义以下DAX表达式:
[NumOfCustomers]:= COUNTROWS ( VALUES ( Sales[CustomerKey] ))
这个表达式不在Customer表里计算客户的数量。相反,它在Sales表里计算当前筛选下CustomerKey列的数量值。因为它只使用了Sales表,因此,表达式并不依赖于销售和客户之间的关系。
当你将筛选器放在产品表里时,它也总是筛选sales表,因为筛选器只从产品表传递到销售表。因此,并不是所有CustomerKey的值都是当前可见的,只有在当前行对应的被销售筛选的产品(即被销售表筛选的那部分产品)。
该表达式的含义是**“计算与所选产品相关的有销售的当前客户的数量”**。因为CustomerKey的值代表了顾客数,这个表达式有效地计算了购买了产品的顾客数量。
请注意:可以使用**DISTINCTCOUNT**实现与上个公式相同的结果,它计算一个列的不重复值的数量。一般来说,使用DISTINCTCOUNT比使用COUNTROWS要好。
在这里,使用COUNTROWS和VALUES作为了解,因为VALUES是一个非常有用的函数,即使它最常用的用法,也只有在后面的章节中才会进一步了解清楚。
**使用VALUES而不是利用关系的方向是有利有弊的。**
**译者:这句话大有文章。一般情况下,在参数表里使用维度来计算符合来自事实表的某个条件计数时,多使用关系的方向:** COUNTROWS ( RELATEDTABLE ( Customer)),而作为筛选器参数,或比较清晰的以事实表计算的多采用**VALUES模式。**
当然,在模型中设置筛选要灵活得多,因为它使用关系。因此,不仅可以使用CustomerKey来计算客户,还可以使用关系的方向来计算客户的任何其他属性值(例如,客户类别的数量等)。
这样说,可能有一些原因迫使你使用单向筛选,或者可能需要使用VALUES以提高性能等原因。无论使用VALUES计数还是将VALUES用作筛选器,你都将在编写DAX时经常使用它。这将在第12章“高级关系处理”中更详细地讨论这些主题。
介绍 ISFILTERED、ISCROSSFILTERED
这两个函数非常有用:
(1)可以帮助更好地理解行、列表筛选的传递;
(2)也可以很好地了解数据透视表计算中最有用的概念之一:检测你正从DAX内部计算的某个值所在的层级级别。例如,了解在当前筛选中是否某列的所有值都是可见的:
ISFILTRERED。 依据作为参数通过的列是否直接被筛选来返回True/Flase--真或假;也就是说,它应该已经被置于行、列、切片器或筛选器中了,而且对于当前单元格而言筛选正在发生。
ISCROSSFILTERED。根据该列是被某个直接行、列表筛选还是由另一个筛选器自动传递而筛选,返回True/Flase-真或假。
此外,学习它们是介绍数据透视表计算中最有用的概念之一:检测DAX内部计算值的单元格的好方法,
这两个函数允许检测一个列的所有值是否在当前筛选中可见。
**ISFILTERED**返回TRUE或FALSE(真或假),这取决于该列作为直接筛选的一个参数传递,也就是说,它被放在行、列、切片器、筛选器和筛选对当前发生的行(单元格)。
**ISCROSSFILTERED**返回TRUE或FALSE,这取决于该列是否存在有一个筛选器,该筛选器来自另一个筛选器的自动传递,而不是直接筛选器。
在本节中,我们感兴趣的是使用这两个函数来理解筛选的传递。因此,我门将创建一些虚拟的表达式,它只作为学习参考用。
让我们定义一个新的度量:
[CategoryFilter] := ISFILTERED ( 'Product Category'[Category])
这个简单的度量返回ISFILTERED函数应用于'Product Category'[Category]列的值。然后再创建第二个度量,该度量与产品颜色进行相同的测试。因此,代码是:
[ColorFilter] := ISFILTERED ( Product[ColorName] )
如果将这两种方法创建的度量都添加到一个透视表中,将类别放在一个切片器中,并且在行上添加颜色,结果将类似于图4-21:
图4-21你可以看到,[CategoryFilter]从来没有被筛选过,[ColorFilter]却都被筛选掉了,但是在Grand Total上显示正确(应该为FALSE)。有趣的是,[CategoryFilter]从来没有被筛选过,因为,一方面,即使我们添加了一个Category-切片器,也并没有在它上面做出过任何筛择。
另一方面,Color-颜色总是在行中进行筛选,因为每一行都是当前特定的颜色,但在总量中没有,因为列表筛选不包含任何产品的选择。
**请注意**
总计的这种行为,也就是说,从行和列中没有筛选得到的,当你想要修改一个公式的行为时,它是非常有用的。
因此,在总计上,它显示了一个不同的值。实际上,这将检查在透表报告中存在的属性,以了解正在计算的单元格是否在数据透视表的内部,或者是在总计上。
如果现在从Category-类别切片器中选择一些值,结果会发生变化。
现在这个类别总是有一个筛选器,如图4-22所示。事实上,切片器引入的列表筛选即使在数据透视表的总计上也是有效的。
图4-22切片器引入的筛选器也在总计上工作。当一个直接筛选器在一个列上工作时,ISFILTERED发挥作用:有些情况下,不显示所有的列值,不是因为筛选了列表,而是因为在另一个列上放置了一个筛选器。**例如,如果你筛选颜色,并计算产品品牌的值,得到的结果将只有当前特定颜色的产品的品牌。
当一个列的筛选来自另一个列时,我们说这个列被交叉筛选。ISCROSSFILTERED函数用于检测这个场景。
如果使用这两个新度量查询数据模型,这一次,使用ISCROSSFILTERED筛选了颜色和类别列:
[CrossCategory]*:= ISCROSSFILTERED ( 'Product Category'[Category] )
[CrossColor]: = ISCROSSFILTERED ( Product[Color] )
然后将看到如图4-23所示的结果。
图4-23使用ISCROSSFILTERED可以看到交叉筛选的结果。可以看到,color-颜色是被交叉筛选的(有来自本例中Cotegory列切片器的筛选),而category类别没有。在这一点上,一个有趣的问题是“为什么**[CategoryFilter]**没有被筛选?”
当筛选一种颜色时,你可能只会看到特定颜色的产品的类别。要回答这个问题,需要清楚,类别并不来自产品表的列,相反,它只是产品类别的一部分,关系中的箭头不会让关系传递。
如果更改数据模型--在从产品到产品类别的整个关系链上启用双向筛选,则结果将会不同,如图4-24所示。
图4-24启用双向筛选后,现在 **[CategoryFilter]** 显示的是交叉筛选,即不是直接筛选。在本节中,已经看到了一些ISFILTERED 和 ISCROSSFILTERED的示例,主要用于理解目的,因为,使用它们只是为了更好地理解列表筛选是如何通过关系传递的。在接下来的章节中,通过编写高级DAX代码,你将了解为什么这两个函数是如此有用。
筛选环境回顾
让我们回顾一下我们所学到的关于计算筛选的知识。
· **计算筛选是通过筛选数据模型并提供当前行的概念(当需要访问列值时)来修改DAX表达式的值的筛选。**
· **计算环境由两部分组成:行筛选和列表筛选。它们共存在所有的DAX公式中。当定义计算列时,DAX会自动创建行筛选,还可以使用迭代器函数以编程方式创建行筛选。所有迭代器都定义行筛选。**
· **可以嵌套行筛选,在这种情况下,EARLIER函数有助于访问原行筛选中前一行的筛选。**
· **当使用数据透视表时,通过在行、列、切片器和筛选器上使用字段来创建一个列表筛选;另一种方法,可以通过使用CALCULATE来编程创建行、列表筛选,但是,我们还没有学习到它。在这一点上,你应该非常想知道更多关于它的介绍!**
· **行筛选不会自动通过关系传递。它通过使用RELATED 和 RELATEDTABLE来手动进行传递。正确使用这些功能的前提是列表具有一对多关系: RELATED用于关系的多端表,RELATEDTABLE用于关系的一端表。**
· **列表筛选伴随着关系的筛选而自动传递****,并总是从关系的一端传递到多端。之外,还可以选择从多端扩展到一端。没有可用的功能是强制传递的: 根据关系数据模型的定义,一切都自动在引擎内部发生。**
· **VALUES()返回一个表,其中包含当前筛选下可见的列的所有唯一值。可以将生成的表用作任何迭代器的参数。**
至此,已经了解了DAX语言中最复杂的概念主题。这些规定了公式的所有评价流,它们是DAX语言的支柱。无论何时,如果遇到表达式计算结果不是自己希望的,很大的原因,是因为你还没有完全理解这些规则。
正如我们在介绍中所说的,在第一次阅读时,所有这些主题看起来都很简单。事实上,让他们变得复杂的是在一个复杂的环境中!表达式的不同部分中可能有多个活动的计算筛选。掌握计算筛选是一种你将获得DAX经验的技能,我们将在接下来的章节中展示许多例子来帮助你。在自己写出一些DAX公式之后,将直观地知道哪些筛选被使用,以及它们需要哪些功能,这样你将最终掌握DAX语言。
创建一个参数表
在本章中,学习了许多关于计算筛选的理论概念。现在是时候使用其中的一些来解决一个实用的场景,并学习一个非常有用的技术:**使用参数表**。
参数表的思想是创建一个与其他数据模型无关系的表,但是可以在DAX表达式内部使用它来修改定义的行为。一个例子可能有助于说清这一点:假设已经创建了一个报告,它显示了销售额的总和,并且由于公司销售了许多商品,报告中显示的数字非常大。
因为我们的示例不是使用SalesAmount列,数据库没有受到这个问题的影响。我们已经创建了一个用来计算SalesAmount的立方的度量,所以这个数字更大,描述的场景更加实际。在图4-25中,可以看到这个报告。
图4-25大数字的阅读报告有时很困难。这份报告的问题在于,这些数字很大,而且很难读懂。他们是亿万、数十亿、甚至万亿?此外,在报告中使用了大量的篇幅,而缺少有用信息。对于这种报告,一个常见的需求是使用不同的单位规格来显示数字。例如,可能希望将显示值除以1000或100万,这样它们就会将数字缩小,但结果仍然是一样。
可以通过修改度量并将其除以1000来轻松地解决这个场景。唯一的问题是,根据数字的相对规模,可能希望将它们视为实际值(如果它们足够小的话),除以数千或除以数百万。在这一点上,如果创建三个度量似乎很麻烦,我们希望找到一个更好的解决方案,以消除创建许多不同度量的需要。
这个想法是让用户决定使用切片器选择在报表中应用哪个比例。在图4-26中,可以看到我们想要构建的报告的示例。
图4-26切片器在这里不筛选值。它只用来改变数字的显示方式。
报告的有趣做法是,不使用ShowValueAs slicer来筛选数据。相反,将使用它来更改数字使用的比例。当用户选择真正的某个值时,然后显示实际的数字。如果选择了数千个,那么实际的数字将都会除以1000,并且在相同的度量中显示,而不需要改变数据透视表的布局。这同样适用于Millions 和Billions--数百万和数十亿。
要创建这个报告,首先需要的是一个包含希望在切片器上显示的值的表。在我们的例子中,是使用Excel做的,我们使用Excel表来存储这些表。在更专业的解决方案中,最好将表存储在SQL数据库中。在图4-27中,可以看到这样一个表的内容。
图4-27这个Excel表将是报表中切片器的源表。显然,无法使用其他表与该表建立任何关系,这里用于计算的Sales表也一样,因为Sales表不包含任何可以用于与此表相关的列。不过,一旦表在数据模型中,可以使用ShowValueAs列作为源列表的切片器。是的,你最终得到的是一个什么都不做的切片器,但是,包含该筛选器的DAX代码将执行读取用户选择的魔力,并使DAX表达式依据选择进一步修改报告的内容。需要使用步骤如下:
[ScaledSalesAmount] := IF (HASONEVALUE ( Scale[DivideBy] ), DIVIDE ( [Sales Amount], VALUES ( Scale[DivideBy] ) ), [Sales Amount])
在这个公式中有两点值得注意:
**IF函数的测试条件是: HASONEVALUE ( Scale[DivideBy]。这种模式很常见:即检查Scale[DivideBy]列是否只有一个可见值(是否被选择)。**
如果用户没有在切片器中选择任何内容,那么在当前行、列表筛选中,列的所有值都是可见的:也就是说,HASONEVALUE将返回FALSE(因为该列有许多不同的值,不符合条件)。
如果用户选择了某个值,那么只有一个值是可见的,这时HASONEVALUE将返回TRUE。
因此,度量条件是这样的:
“如果用户为ShowValueAs属性选择了某个单一值。那么,单个行就是可见的,这时计算VALUES ( Scale[DivideBy] ),可以确定生成的表只包含一个列和一行(在列表筛选中可见的一个行)。如果使用VALUES转换为一个标量值,DAX结果将返回单行的一列表:当结果是包含多个行的表时,使用VALUES读取单个值时,将会得到一个错误。但是,在本例中,我们需要返回的值将是唯一的值,是否为唯一值这个条件是由IF测试的。
因此,可以将表达式理解为:“如果用户在切片器里选择了一个值,那么,显示相应销售额除以分母,否则显示原来的销售额”。结果是,它改变了一份报告显示的值,使用切片器就好像一个按钮。
显然,因为报告只使用了标准的DAX公式,所以在部署到SharePoint或Power BI时也会起作用。
参数表在构建报表时非常有用。我们已经展示了一个非常简单(但非常普通)的例子,唯一的限制是你的想象力。可以创建参数表来修改计算数字的方式,更改特定算法的参数,或者执行其他复杂的操作,这些操作将改变DAX代码所返回的值。