从数据到信息
从信息到洞察

理解筛选上下文

通过初识筛选上下文进阶筛选上下文,你已经对筛选上下文有了一定的理解,本文将继续深入阐述筛选上下文,在开始之前,让我们先对之前的内容做一个简单的回顾:

  1. 筛选上下文为公式提供计值环境,无论度量值还是计算列,都在各自的筛选上下文环境中进行计算
  2. 计值环境由透视表行、列标签,筛选器、切片器、图表坐标轴等因素共同决定,其中最重要的是由 CALCUALTE 以编程方式创建的筛选上下文,它可以覆盖外部环境中相同的筛选条件。
  3. 当使用列作为筛选器时,DAX 不会对包含这一列的整张表应用筛选器。它只对列应用筛选器。然后,因为列是表的一部分,因此表也会有一个筛选器。然而,筛选器一次只对一列生效。

理解元组

现在你已经理解了扩展表和它在 DAX 中的工作方式,现在是时候对计值上下文交互进行更深入的分析,并给出计值上下文的最终定义了。让我们从定义一个元组(tuple)开始。元组是一组列值。例如,在日期表中,一个元组可能是这样的:

« Year = 2007, Month = January »

你可以将元组看作单行表,元组中的列可以属于不同的表,元组为模型的列定义一个值。因此,直观地说,元组的行为就像数据模型中的筛选器。例如将元组 «2007,January » 应用于包含销售表的模型,它将筛选 2007 年 1 月的销售数据。

元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性

要查看用作筛选器的元组示例,请查看以下表达式:

CALCULATE (
    ……
    FILTER (
        CROSSJOIN ( ALL ( Date[Year] ), ALL ( Date[Month] ) ),
        OR (
            AND ( Date[Year] = 2007, Date[Month] = "January" ),
            AND ( Date[Year] = 2006, Date[Month] = "December" )
        )
    )
)

CALCULATE 使用以下元组列表作为筛选器参数:

« Year=2006, Month=December »

« Year=2007, Month=January »

元组的列表组成了筛选器。单个筛选器通常不构成最终的筛选上下文,筛选上下文实际上是一组筛选器。事实上,如果你观察下面的代码:

CALCULATE(
    ……
    FILTER (
        CROSSJOIN ( ALL ( Date[Year] ), ALL ( Date[Month] ) ),
        OR (
            AND ( Date[Year] = 2007, Date[Month] = "January" ),
            AND ( Date[Year] = 2006, Date[Month] = "December" )
        )
    ),
    OR ( Product[Color] = "Black", Product[Color] = "Yellow" )
)

最终的筛选上下文包含两组不同的元组:一组筛选日期表中的两列,另一组筛选产品颜色。从视觉上看,筛选器与表是相同的。为了使两者的区别更加明显,当图中的表用来表示筛选器时会增加一个筛选器图标。前一个公式生成的筛选器如下图所示

左侧筛选器图标将筛选器与普通的表区分开

类似上图中的一组筛选器构成了筛选上下文。这让筛选上下文看上去像是多个表。如果某列位于筛选器中,我们就说这个列被筛选上下文过滤了。因此,示例中的年份、月份和颜色这三列都属于筛选上下文。

乍一看,元组看起来像一个普通的行,筛选器看起来像一个表。这两个概念非常相似,但考虑到使用场景我们使用了不同的名称。例如,在之前的代码中,FILTER 的结果是一个表。仅当你将其用作 CALCULATE 的筛选器参数时,它才会成为筛选上下文。在此过程中 DAX 将表转换为筛选上下文,表的各个行成为元组。两者的差别很小,但在描述筛选上下文行为时很有用。

表筛选器和列筛选器的区别

例如,当你将物理表或列做为筛选器时,这两种结果存在很大的区别,当上文中的公式使用 CROSSJOIN 的结果作为筛选上下文时,包含由 CROSSJOIN 生成的各个列,而当你使用完整的物理表作为筛选上下文时,将包含原始表扩展版本的所有列。如果查看以下两个表达式,它们使用的筛选器参数具有不同的元组:

CALCULATE (
    SUM ( Sales[Quantity] ),
    'Product Subcategory'[Subcategory] = "Radio"
)

CALCULATE (
    SUM ( Sales[Quantity] ),
    FILTER (
        'Product Subcategory',
        'Product Subcategory'[Subcategory] = "Radio"
    )
) 

实际上,第一个筛选器参数生成一个由单列元组组成的筛选上下文:

« ‘Product Subcategory'[Subcategory] = Radio »

第二个表达式对表设置了筛选器。这样一来,得到的筛选上下文将包含 Product Subcategory 表的扩展版本的所有列构成的元组

« ‘Product Subcategory'[Subcategory] = Radio,

‘Product Subcategory'[Subcategory] Code] = “0103”,

‘Product Subcategory'[ProductSubcategoryKey] = 3,

‘Product Subcategory'[ProductCategoryKey] = 1,

‘Product Category'[ProductCategoryKey] = 1,

‘Product Category'[Category Code] = “01”,

‘Product Category'[Category] = Audio »

正如你在扩展表中了解到的,这种差异非常重要,如果忽略这种差异,将表用作筛选器时很可能会得到意料之外的结果。

列筛选器和表筛选器对比,黄色区域为扩展表

理解筛选上下文的运算符

现在你已经了解了什么是元组和筛选上下文,现在我们将定义筛选上下文中唯二的两种运算符:它们是交集(INTERSECTION)和覆盖(OVERWRITE)。

交集运算

给定 A、B 两个筛选上下文,A 和 B 的交集是通过将A 中的筛选器添加到 B 的筛选器后得到的。交集运算在 CALCULATE 需要合并筛选器参数时使用。以下示例对于澄清这一概念非常有用:

CALCULATETABLE (
    ……
    Date[Year] = 2008,
    OR (
        Product[Color] = "Black",
        Product[Color] = "Yellow"
    )
)

公式有两个筛选器,一个是关于年份的,另一个是关于颜色的。我们一直以来的描述口径是 CALCULATE 的所有筛选器参数以 AND 逻辑组合到一起,但现在讨论已经深入到元组,我们可以更具体地说,它们以相交(INTERSECTION)的方式组合到一起。实际上,最终的筛选上下文如图所示

两个筛选器的交集是将两者合并,彼此之间是 AND 关系

交集运算是一种非常简单的操作,实际上,你可以将交集看作是由筛选器定义的集合之间的一种简单的 AND 运算。此外,交集运算具有一种优雅的特性,即它可以很好地在复杂筛选器中生效。比如,如果观察下面的表达式,你将注意到日期表使用两个条件进行筛选。第一个筛选条件得到 2007 年 1 月和 2006 年 12 月,第二个筛选条件得到销售量大于 100 的日期(无论年份和月份)。最终,交集运算得到了正确的结果(日期在 2007 年 1 月和 2006 年 12 月,且销售数量大于给定值 100 的日期)。

CALCULATE (
    ……
    FILTER (
        CROSSJOIN ( ALL ( Date[Year] ), ALL ( Date[Month] ) ),
        OR (
            AND ( Date[Year] = 2007, Date[Month] = "January" ),
            AND ( Date[Year] = 2006, Date[Month] = "December" )
        )
    ), 
    FILTER ( ALL ( Date[Date] ), 
             CALCULATE ( SUM ( Sales[Quantity] ) ) > 100 
    )
)

最后,交集运算具备对称性:A 与 B 相交得到的结果和 B 与 A 相交得到的结果一致。这与预期吻合,但马上你会了解到,覆盖运算是不对称的,你需要更加小心地使用它。

覆盖运算

现在你已经学习了交集运算,接下来介绍 DAX 在筛选上下文中执行的第二种运算:覆盖(OVERWRITE)。覆盖是 CALCULATE 在合并生成新的筛选上下文时使用的运算方式。例如,查看以下表达式:

CALCULATE (
    CALCULATE (
        ……
        Product[Color] = "Yellow"
    ),
    Product[Color] = "Black"
)

内层的 CALCULATE 覆盖外层使用的筛选器,公式计算黄色产品的结果,忽略黑色筛选器。这在预料之中,但它表明,在这种情况下,两个筛选上下文没有相交,而是使用了覆盖。覆盖的定义很简单,不过一旦涉及复杂筛选器,会导致非常棘手的局面。让我们从覆盖运算的定义开始:

在计算 A 覆盖 B 时,我们将 B 定义为之前的筛选上下文,将 A 作为覆盖 B 的筛选上下文。这样我们在阅读“A 覆盖 B”时可以获得统一的理解,因为新筛选器(A)覆盖了旧筛选器(B)。在上一个示例中,A 是黄色筛选器,B 是黑色筛选器。

为了计算 A 覆盖 B 这一过程,DAX 执行两步操作:

  1. 从 B 的所有筛选器中移除在 A 中被筛选的列,从而生成一个新的筛选上下文,我们称之为 B – Cleaned。
  2. A 与 B – Cleaned 进行合并

在之前的示例中,筛选器 B 包含黑色。B – Cleaned 变为空因为 DAX 移除了其唯一的颜色列,并最终生成只包含黄色筛选器的新筛选上下文。通过下图你可以看到覆盖操作的图形表示。

从语义角度,覆盖移除了 B 中的列,然后将得到的空筛选器与 A 合并

覆盖是一种强大的运算,但使用它需要特别注意。事实上,对于标准形态的筛选器(Well-Shaped Filter),它以一种直观的方式工作,而对于固化形态的筛选器,它开始变得复杂得多。在继续下面的内容之前,是时候介绍固化形态筛选器了,因为它们对于解释覆盖运算起着重要作用。

理解固化筛选器

标准形态的筛选器可以表示为单列筛选器之间的笛卡儿积(CROSSJOIN)。示例如下:

CALCULATETABLE (
    ……
    OR (
        Date[Year] = 2007, 
        Date[Year] = 2006
    ),
    OR (
        Product[Color] = "Black",
        Product[Color] = "Yellow"
    )
)

实际上,这个示例与下面的写法等价:

CALCULATETABLE (
    ……
    CROSSJOIN (
        FILTER (
            ALL ( Date[Year] ),
            OR (
                Date[Year] = 2007,
                Date[Year] = 2006
            )
        ),
        FILTER (
            ALL ( Product[Color] ),
            OR (
                Product[Color] = "Black",
                Product[Color] = "Yellow"
            )
        )
    )
)

你可以很容易地将标准形态的筛选器图形化表示为作用于单列的一组筛选器,如图所示(左上角漏斗图标表示这里展示的筛选条件的值,并非普通的表格)。

标准形态的筛选器可视为为来自单列的筛选器

这种筛选器总是指向简单的表达式,无论交集还是覆盖在这种状态下都以直观的方式工作。

另一方面,固化的筛选器是非标准形态的筛选器。换句话说,你不能将它表示为单列筛选器之间的笛卡儿积,比如你在下面这个表达式中看到的:

CALCULATE(
    ……
    FILTER (
        CROSSJOIN ( VALUES ( Date[Year] ), 
                    VALUES ( Date[Month] ) 
    ),
        OR (
            AND ( Date[Year] = 2007, Date[Month] = "January" ),
            AND ( Date[Year] = 2006, Date[Month] = "December" )
        )
    )
)

此筛选器的图形化结果如下图所示。正如你所看到的,你不能将这类筛选器显示为两个单独的列。相反,你需要将列放在同一个表中,因为它们具有存储在筛选器内部的关系

固化筛选器不等于简单的列筛选器的笛卡尔积

不能将包含 2007 年一月份和 2006 年十二月份的筛选器表示为简单筛选器的交叉相乘。实际上,DAX 必须同时考虑这两列才能准确计值筛选器也就是说,固化筛选器定义了自身列之间的关系。事实上,在学术文献中,我们称筛选器(或者更笼统地称为表)是一种关系。我们倾向于在书中避免使用这个术语,因为它与表间关系这个更广泛使用的概念相冲突。然而,表(在本例中是筛选器)定义了列之间的关系。

固化筛选器和上下文运算

当需要对筛选上下文执行覆盖运算时,固化筛选器可能会带来问题。事实上,虽然交集运算可以很好地与固化筛选器一起工作,但是覆盖运算会导致更复杂的局面。例如,让我们以前一个表达式为例,补充一个关于年份的筛选条件,如:

CALCULATE(
    ……
    FILTER (
      CROSSJOIN ( VALUES ( Date[Year] ), VALUES ( Date[Month] ) ),
      OR (
        AND ( Date[Year] = 2007, Date[Month] = "January" ),
        AND ( Date[Year] = 2006, Date[Month] = "December" )
      )
   ),
   Date[Year] = 2007
) 

正如你所预期的,这个表达式计算 2007 年 1 月的值,因为第一个筛选器返回 2007 年 1 月和 2006 年 12 月,与第二个筛选器相交,只返回 2007 年 1 月。在图 10-29 中可以看到交集运算的图形表示。

固化筛选器可以安全的执行交集运算 

但如果你用这种方式改写表达式,情况就会有所不同:

CALCULATE (
    CALCULATE (
        ……
        Date[Year] = 2007
    ),
    FILTER (
    CROSSJOIN ( VALUES ( Date[Year] ), 
                VALUES ( Date[Month] ) ),
    OR (
        AND ( Date[Year] = 2007, Date[Month] = "January" ),
        AND ( Date[Year] = 2006, Date[Month] = "December" )
    )
    )
)

在这个表达式中,我们把年份筛选器移动到了内层 CALCULATE。两个版本的区别是,现在 CALCULATE 使用覆盖运算合并两个筛选器,从第一个筛选器中删除日历年,只留下月份标签上的筛选器,然后将这个筛选器与年份上的筛选器相交,如图:

对固化筛选器集合应用覆盖运算可能会导致意外的结果

生成的筛选器包含 2007 年 1 月和 12 月,因为年份列的新筛选器从之前筛选器中删除了这一列。这样做后,它破坏了存储在筛选器中的关系,并将其替换为标准筛选器,这可能不是你在编写公式时所期望的。

解决方案

CALCULATE 调节器章节,你已经学习了 KEEPFILTERS 的行为。它可以很好的解决固化筛选器在覆盖运算时遇到的问题

现在你已经对筛选上下文和筛选上下文的运算符有了扎实的理解,你可以以一种更简单的方式来考虑 KEEPFILTERS:当你使用 KEEPFILTERS 时,CALCULATE 通过交集运算(而不是覆盖)将新的筛选上下文与之前的筛选上下文合并。这使得你可以保持固化形态筛选器集合的完整性,因为相交会保留关系,而覆盖可能会破坏它们。

总结

我们可以将这种规则总结如下:对标准筛选器上执行上下文运算时,交集和覆盖都以一种直观的方式工作。当使用固化筛选器时,交集通过与结果相交来保持筛选器的形态,而覆盖可能会中断筛选,从而丢失存储在原始筛选器中的某些关系,产生错误结果。

本文的重点是理解筛选上下文的运算符,它们完整的描述了筛选上下文之间的运算方式,只有准确的计算出最终的筛选上下文,公式才能在这个环境中执行最终计算,由此可见,对筛选上下文的计算将贯穿整个 DAX 使用过程。请记住,无论多么复杂的公式,它所依赖的筛选上下文都离不开这两种运算方式,并且也只有这两种运算方式。


关于筛选上下文的介绍到这里告一段落,目前的知识已经足以解决实践中的所有问题,但是从技术角度,对筛选上下文的底层解释仍然缺失,在完成 DAX 圣经第二版的翻译之后,我将继续补充这部分内容,请继续关注。

63
说点什么

1000
 
鼓掌微笑开心憧憬爱你色并不觉得吃瓜doge二哈喵喵思考笑哭捂脸悲伤大哭抓狂汗偷笑打脸捂眼黑线问号晕拜拜闭嘴衰咒骂ok作揖
23 评论数
44 被回复的评论
18 订阅评论的人数
 
查看最近回复
查看最热评论
  订阅本文评论  
最新 最旧 得票最多
提醒
成员
似水无痕

高老师,《DAX权威指南》书中P314~315 使用ISEMPTY函数 中的 NonBuyingCustomers 度量值,第一个变量的写法貌似笔误。
VAR SelectedCustomers =
CALCULATETABLE(
DISTINCT( Customer[CustomerKey] ), –这里貌似应该是 DISTINCT( Sales[CustomerKey] )
ALLSELECTED( )
)

成员
jb870610

高老师,上面的文字只有5列,图片中有7列

1.png
成员
jb870610

高老师,红框里面描述的,“关系是一张表”是什么意思啊?这个关系是两张表之前的那个一对多或者多对多那个关系吗?

1.png
游客
lrypower

不知道上面提问时的代码会被隐藏一部分,刚才我试着在这条帖子下面重新粘贴也不管用,改格式加回车空行都会被隐藏

游客
lrypower

有个困扰多日的问题求教高老师,我翻看了好多资料,找不到解决办法!我有一个对原表经过复杂筛选后求和,求和结果是正确的,目前的问题是无法用时间来筛选,除时间外的其它项都能筛选,我的数据跨度好几年,用的是前面的时间筛选后面的结果,用2016年-2018年的数据得出值筛选2019年到今年的数据。若需进一步的详细说明,请告之

成员
小本本

在完成 DAX 圣经第二版的翻译之后,我将继续补充这部分内容,请继续关注。 日常催更

游客
芒果

高老师,上面的插图在解释“覆盖”操作原理时,图片下面的一句话“从语义角度,覆盖移除了 B 中的列,然后将得到的空筛选器与 A 相交”,这里面的“相交”按照前面的交集操作说法,是AND逻辑“与”操作。那么请问A覆盖了B,删除了B列,B列为空,那么A与空进行AND操作,结果不就是空吗? 为什么是A呢?我认为覆盖操作其实就是替换操作,不是文中说的这样的操作。容易让人误解。文中的某些说法不严谨的很。另外,集合学中,任何非空集合跟空集相交,就是空集啊。我认为“覆盖”这里不应该用“相交”这个概念。严格意义上讲不通。

成员
wind_pbi

对于以下的2种写法,实在看不出,筛选之后的结果有何不同,对于第2种写法,即使增加的1表中的多个筛选列,但是筛出来的还是同样的结果,因为1对多的原因,1端永远筛出的是唯一值,麻烦请说一下这2种写法的目的或者可以举出反例吗?谢谢
—————————————————————————————————————————————–
CALCULATE (
SUM ( Sales[Quantity] ),
‘Product Subcategory'[Subcategory] = “Radio”
)
CALCULATE (
SUM ( Sales[Quantity] ),
FILTER (
‘Product Subcategory’,
‘Product Subcategory'[Subcategory] = “Radio”
)
)

成员
此夕洛溯

3.当使用列作为筛选器时,DAX 不会对包含这一列的整张表应用筛选器。它只对列应用筛选器。然后,因为列是表的一部分,因此表也会有一个筛选器。然而,筛选器一次只对一列生效。
————————————————————————————————
以上摘自文章开头。
请教一下高飞老师,
1、如何理解”当使用列作为筛选器时,DAX 不会对包含这一列的整张表应用筛选器,它只对列应用筛选器”?比如有单列筛选器对列A进行筛选值为“A1”的行,那么列B和列C是不是也会只会保留1-3行,4-7行都会被筛选掉?如果列B和列C只保留了1-3行,那岂不是列A的筛选器对整个表(只考虑列A所在的自身表,不考虑扩展表)产生了影响。
2、如何理解“因为列是表的一部分,因此表也会有一个筛选器。”?表也会有一个筛选器是指用在列上的那个筛选器吗?如果对一个表中的两列使用了筛选器,那么表是不是会有两个筛选器了?
望老师解惑,谢谢。

企业微信截图_16400716602483.png
游客
芒果

“而当你使用完整的物理表作为筛选上下文时,将包含原始表扩展版本的所有列。”这句话里面的“原始表扩展版本“是什么意思呢?我看到正文举的例子里面,扩展版本怎么还包含了1端的表‘Product Category’里面的两个字段呢?

———–以下引自正文——————–
第二个表达式对表设置了筛选器。这样一来,得到的筛选上下文将包含 Product Subcategory 表的扩展版本的所有列构成的元组

« ‘Product Subcategory'[Subcategory] = Radio,

‘Product Subcategory'[ProductSubcategoryKey] = 3,

‘Product Subcategory'[ProductCategoryKey] = 1,

‘Product Category'[ProductCategoryKey] = 1,

‘Product Category'[Category] = Audio »
———————————————-

成员
芒果

原文:“在这个表达式中,我们把年份筛选器移动到了内层 CALCULATE。两个版本的区别是,现在 CALCULATE 使用覆盖运算合并两个筛选器,从第一个筛选器中删除日历年,只留下月份标签上的筛选器,然后将这个筛选器与年份上的筛选器相交,如图:”
———————————————–
“从第一个筛选器中删除日历年,只留下月份标签上的筛选器,然后将这个筛选器与年份上的筛选器相交”,这句话没有搞明白。从一个筛选器删除日历年,只留下月份标签的筛选器,那么第一个筛选器就是图中右侧的那个吗? 然后将这个筛选器与年份上的筛选器相交?我认为按照集合上的相交概念,应该是两个都有共同元素才是相交。既然第一个删除了日历年份,怎么相交?所以,这里文章内的相交的意思,应该就是“并集”的意思吧。也就是把剩下的月份筛选器跟那个外层的年份筛选器进行合并。形成了一个新的筛选器。

我不明白,为什么这么严谨的文章,用INTERSECT这个单词表示一个逻辑上的AND的意思?让人费解啊。会误导很多人的吧?明明两个筛选条件根本不可能进行INTERSECTION操作,只能进行AND操作。

成员
芒果

为了计算 A 覆盖 B 这一过程,DAX 执行两步操作:
1.从 B 的所有筛选器中移除在 A 中被筛选的列,从而生成一个新的筛选上下文,我们称之为 B – Cleaned。
2.A 与 B – Cleaned 进行合并

——————————————————————
上述原文内的“1.从 B 的所有筛选器中移除在 A 中被筛选的列,…”这句话没有理解。怎么理解从B的所有筛选器中移出在A中被筛选的列?真是理解不了。你举得例子,B是BLACK列,怎么删除A的黄色的列?B里面哪有黄色?

成员
舒鹏

第四遍,这次又有新的收获。DAX圣经真的需要反复看。

成员
xifeng

老师,我想问一下,将表用作筛选器参数或者某张表的行上下文转换成了筛选上下文,那么这些情况都是固化筛选器对吗? 此外,如果用固化筛选器去覆盖一个单列的标准筛选器,那么会破坏固化筛选器的关系吗?

成员
奇缘

高老师,固化筛选器 和 表筛选器 是什么关系?两个filter单独创建的单列筛选器 和 一个filter创建的包含两列的筛选器,就是固化和非固化的区别吗? 至于外层被内层先删除相同列后组合,这个和固化筛选器是不是有关系,如果外层不是固化筛选器而是单列的非固化筛选器,这个过程会有什么不一样呢? 内层始终都会覆盖外层的相同列的筛选啊?
可能问的很小白, 我是决定要学再学一遍原理了。

成员
sankingg

当使用列作为筛选器时,DAX 不会对包含这一列的整张表应用筛选器。它只对列应用筛选器。然后,因为列是表的一部分,因此表也会有一个筛选器。然而,筛选器一次只对一列生效。
———————————————————————–
难道说还有不对列生效,直接对表生效的筛选吗?
列是表的一部分,筛选了列,就等于筛选了表,难道不是吗?
推而广之就是,你筛选了一列,就筛选了整个模型。

成员
sankingg

计值环境由透视表行、列标签,筛选器、切片器、图表坐标轴等因素共同决定,其中最重要的是由 CALCUALTE 以编程方式创建的筛选上下文,它可以覆盖外部环境中相同的筛选条件。

————————–
外部环境中“相同的”筛选条件。请问一下什么是相同?

成员
139****3194

老师,上下文的运算方式之一:覆盖,即先删除再合并,对吗?

成员
walkman

把这里的固化改为前面的物化,能更好的保持文章内容的一致性,非常期待第二版

成员
bbzhdlp

高老师,这6个点是表示啥意思啊

390.png
DAX 圣经

导读

初识 DAX

DAX 基础知识

DAX 原理

DAX 高级原理

基础函数类型

迭代函数

CALCULATE 函数

CALCULATE 调节器

基础表函数

条件判断函数

查找匹配函数

时间智能函数

统计类函数

投影函数

分组/连接函数

集合函数

其他函数