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

理解数据沿袭 Data Lineage

Lineage 一词最常用于指血统,意为“来自祖先的直系血脉”,这个词的翻译有很多版本,这里我参照微软官方文档,将其译作数据沿袭,如果你看到类似“数据血统”、“数据血脉”、“数据继承”、“数据谱系”等词汇,它们大概率都指向 Data Lineage。在数据库领域,Lineage 用来追踪经过转换、加工后的数据的源头,Power BI Service 的数据流服务就提供类似的功能

与上面的描述不同,Lineage 在 DAX 中有更丰富的含义,以下我们都将围绕 DAX 中 Lineage 的进行介绍

DAX 中的 Lineage

在 DAX 中,数据沿袭是一个标记(Tag)。表的每一列都会分配这样一个标记,它的作用是标识数据模型中的原始列。我们已经知道表函数可以对模型中的一列或多列进行操作,当操作对象对应于数据模型的一个物理列时,因为每列都具有特定的沿袭,即使经过某些转换和加工,引擎仍然可以识别这种沿袭关系,从而可以更快地进行筛选操作。沿袭不取决于列的名称,也不取决列的值,从技术上讲,它是可以唯一标识列的内部标准。你无法通过 DAX 显示列的沿袭,但可以观察这种效果。

数据沿袭是一个隐式生效的 DAX 特性,设计的十分巧妙,以至于大多数用户可以在不了解它的情况下使用它

数据沿袭不取决于列值

观察下面这个由两个颜色值组成的匿名表

{ "Red", "Blue" }

我们知道它们代表颜色,假设你正在模型中分析相关问题,你可以更具体的理解为这是在引用产品的颜色。但对于 DAX 引擎来说,它只表示包含两个字符串的表,仅此而已。所以,下面的的度量值将始终得到总销售额,因为这个匿名表不能筛选模型:

Test := CALCULATE ([Sales Amount],{ "Red", "Blue" })

这个度量值没有任何报错,筛选器参数使用了匿名表,在下图中,你可以看到结果与 Sales Amount 相同,因为 CALCULATE 没有进一步应用任何筛选

使用匿名表筛选不会产生任何有效筛选

实际上,你可以将任何表用作筛选上下文中的筛选器,但是那些没有数据模型物理列沿袭的列将被忽略

对于想要筛选模型的值,DAX 需要验证值本身的数据沿袭。一个容易理解的知识是,数据模型中的列值具备该列的数据沿袭与之相对的,如果一个值没有链接到数据模型中的任何列,那么它就是一个匿名值。在前面的示例中 Test 度量值使用一个匿名表来筛选模型,因此,它不能筛选数据模型的任何列。

下面是应用筛选器的正确方法(这里使用 CALCULATE 筛选器参数的完整语法是出于演示目的)。我们只需要一个条件就能筛选 Product[Color]:

Test :=
CALCULATE (
    [Sales Amount],
    FILTER ( ALL ( 'Product'[Color] ), 'Product'[Color] IN { "Red", "Blue" } )
)

数据沿袭不取决于列名

对列的重命名不会破坏原有的沿袭。例如,下面的查询为每一行返回不同的值:

EVALUATE
ADDCOLUMNS (
    SELECTCOLUMNS (
        VALUES ( 'Product'[Category] ),
        "New name for Category", 'Product'[Category]
    ),
    "Amt", [Sales Amount]
)

尽管 New name for Category 列与原始列名不同,但它保留了 Product[Category]的数据沿袭,所以查询得到了正确的结果,按类别划分的销售额。

对于能否筛选模型,列名和列值都不重要,真正重要的只是列的数据沿袭,也就是这些值的源头所在的列

即使一个表包含了来自不同表的列,每列也仍然保持自己的数据沿袭。正因如此表表达式的结果可以一次将筛选器应用于多个表通过下面的查询你可以清楚的观察到这一现象,查询同时包含 Product[Category]和 Date[Calendar Year]列,这两列都通过上下文转换产生的筛选上下文将其筛选器应用于 Sales Amount 度量值。

EVALUATE
FILTER (
    ADDCOLUMNS (
        CROSSJOIN (
            VALUES ( 'Product'[Category] ),
            VALUES ( 'Date'[Calendar Year] )
        ),
        "Amt", [Sales Amount]
    ),
    [Amt] > 0
)

结果显示了给定类别和年份的销售额,表表达式的两列都有效的筛选了度量值

改变数据沿袭

修改列表达式失去沿袭

当表达式只有一个列引用时,会保持数据沿袭,而一旦加入其他表达式,情况就可能发生变化。例如,向之前表达式中的 Product[Category]添加空字符串不会改变列值,但是会破坏数据沿袭。在下面的代码中,New name for Category 的源头变成了一个表达式,不再是列引用。因此,这个新列具备了一个新的数据沿袭,与模型的任何列都不相关。

EVALUATE
ADDCOLUMNS (
    SELECTCOLUMNS (
        VALUES ( 'Product'[Category] ),
        "New name for Category", 'Product'[Category] & ""
    ),
    "Amt", [Sales Amount]
)

与预期相同,查询结果每行都返回相同的值

使用 TREATAS 改变沿袭

数据沿袭由引擎以完全自动的方式继承和维护,但你仍然可以修改表的数据沿袭,这就是 TREATAS 函数的用处。TREATAS 接受表作为第一参数,后跟一列或多列的引用列表,返回表继承参数列的数据沿袭。并且对于第一参数的每一列,TREATAS 剔除其在各自的输出列中不存在的值。例如,下面的查询构建了一个包含产品类别列表的表,其中高亮行的值“Computers and Geeky Stuff”与模型中的任何类别都不匹配。我们使用 TREATAS 强制将表的数据沿袭映射到 Product[Category]。

EVALUATE
VAR Categories =
    DATATABLE (
        "Category", STRING,
        {
            { "Category" },
            { "Audio" },
            { "TV and Video" },
            { "Computers and geeky stuff" },
            { "Cameras and camcorders" },
            { "Cell phones" },
            { "Music, Movies and Audio Books" },
            { "Games and Toys" },
            { "Home Appliances" }
        }
    )
RETURN
    ADDCOLUMNS (
        TREATAS (
            Categories,
            'Product'[Category]
        ),
        "Amt", [Sales Amount]
    )

结果返回按类别划分的销售额,但没有“Computers and Geeky Stuff”的记录,因为模型的原始数据中没有这个类别,数据沿袭改变后,TREATAS 从结果中删除了这一行。

在实战中修改数据沿袭

现在你已经了解了什么是数据沿袭以及如何使用 TREATAS 对其进行操作,下面我们来演示如何借助 TREATAS 和数据沿袭来生成优雅的 DAX 代码。

要求:只计算每个产品第一天的销售。当然你也可以按客户、商店或任何其他有意义的维度进行统计,这里我们只考虑产品维度。每种产品都有不同的首次销售日期。一种方法是逐个产品计算首次销售日期,然后计算该日期的销售额,最后汇总所有产品的结果。根据这个逻辑可以定义如下度量值

FirstDaySales v1 :=
SUMX (
    'Product',
    VAR FirstSale =
        CALCULATE (
            MIN ( Sales[Order Date] )
        )
    RETURN
        CALCULATE (
            [Sales Amount],
            'Date'[Date] = FirstSale
        )
)

FirstDaySales 度量的结果显示了每个品牌销售额的子集

公式的结果正确,但是写法不是最优的。它迭代 Product 表,每计算一个产品都进行上下文转换,并且没有利用已有的关系。这种写法并没有太大问题,只是不够优雅而已,我们可以用更高效的写法返回相同的结果。

第一步是构建一个包含产品名称和对应的首次销售日期的表,然后使用这个表作为计算销售额的筛选器参数。与前面的代码相比,下面的代码有所改进,但仍然不是最优的,因为 SUMX 仍然会为每个产品进行上下文转换:

FirstDaySales v2 :=
VAR ProductsWithSales =
    SUMMARIZE (
        Sales,
        'Product'[Product Name]
    )
VAR ProductsAndFirstDate =
    ADDCOLUMNS (
        ProductsWithSales,
        "Date First Sale", CALCULATE (
            MIN ( Sales[Order Date] )
        )
    )
VAR Result =
    SUMX (
        ProductsAndFirstDate,
        VAR DateFirstSale = [Date First Sale]
        RETURN CALCULATE (
            [Sales Amount],
            'Date'[Date] = DateFirstSale
        )
    )
RETURN Result

仔细观察上面这个查询,你可能会注意到,变量 ProductsAndFirstDate包含了产品名称和首销日期,如果直接将它用作 CALCULATE 的筛选器参数,似乎是一种更简洁的写法:

FirstDaySales v3 wrong :=
VAR ProductsWithSales =
    SUMMARIZE (
        Sales,
        'Product'[Product Name]
    )
VAR ProductsAndFirstDate =
    ADDCOLUMNS (
        ProductsWithSales,
        "Date First Sale", CALCULATE (
            MIN ( Sales[Order Date] )
        )
    )
RETURN CALCULATE (
        [Sales Amount],
        ProductsAndFirstDate
    )

很遗憾,这种写法是错误的,公式没有应用任何筛选器,返回与 Sales Amount 相同的值,问题的原因就在于数据沿袭:

公众号二维码加载失败时的替代文字
此处内容已经被作者无情的隐藏,请输入验证码查看内容
验证码:
请关注“PowerBI极客”公众号,回复关键字“lineage”,获取验证码。 【注】手机扫描二维码快速关注“PowerBI极客”官方公众号。

对于高级 DAX 用户,理解并灵活运用数据沿袭是一项重要的技能。它不像行上下文筛选上下文上下文转换那样基础。但它是将你和其他普通 DAX 用户区分开的重要概念之一。

测试你对 Lineage 的理解

DAX 引擎在内部跟踪物理列的沿袭,以确保 DAX 表达式应用了某些转换后仍然保留对原始列的引用。你可以用下面的问题测试自己对数据沿袭的理解,对两个相似查询的结果做出判断

Currency 表包含索引,货币代码和货币名称三列

基于此数据编写两个查询,请分别判断它们的结果

CALCULATETABLE (
    Currency,
    FILTER (
        CROSSJOIN (
            VALUES ( Currency[Currency Code] ),
            SELECTCOLUMNS (
                VALUES ( Currency[Currency Code] ),
                "Another", Currency[Currency Code] 
            )
        ),
        [another] <> Currency[Currency Code]
    )
)
CALCULATETABLE (
    Currency,
    FILTER (
        CROSSJOIN (
            VALUES ( Currency[Currency Code] ),
            SELECTCOLUMNS (
                VALUES ( Currency[Currency Code] ),
                "Another", Currency[Currency Code] & ""
            )
        ),
        [another] <> Currency[Currency Code]
    )
)

从下面三个选项中选择你认为正确的结果

  • 返回错误
  • 返回空表
  • 返回完整的 Currency 表

本题考查对数据沿袭和筛选上下文的理解,请仔细推导查询中每一步得到的结果

  • 查询 1 返回空表
  • 查询 2 返回完整的 Currency 表

本文隐藏内容查看价格为5G币,请先
注:加入VIP会员可享受全站权益,性价比更高。单独购买的内容长期有效,不受时间限制。

参考阅读:

 

11
说点什么

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

老师,请问查询语句在哪个平台写代码?写完的结果可以在Bi里作为变量调用吗?

还有上文查询2,筛选器一列具有沿袭,一列不具备沿袭,那只有具备的沿袭列有筛选器作用,另一列忽略了?我个人理解两列and关系,所以返回空白表?请指导,多谢

成员
153****0115

看了最后这个例子对于这句话有具体的理解了,不然觉得好抽象,谢谢老师!

SELECTCOLUMNS keep the data lineage of the columns assigned to a simple column reference. Any different expression breaks the data lineage.

成员
139****3194

老师,Calculatetable我们知道返回表,在形成表之前,若筛选器为一列值的表,且有多行,如何筛选计值?谢谢

成员
lyliuyouyang

感谢高飞老师这章的内容,其实这章里面个人觉得最有收获的就是通过treatas去建立已经失效的lineage,看了那个示例,我也根据自己的数据模型去写了一个类似的查询,见图片,大致内容为table1得到劳务队名称的不重复列,table2在table1的基础上添加上最早日期,table3改变了table2的数据沿袭,table4将table3作为筛选表得到想要的结果汇总。得到正确的结果后,我有个问题就是treatas这个函数之前我只会把它当作查找函数使用(类似于lookupvalue),在这里它的作用更大了,直接让一个原本无效的查询表(table2)改变其沿袭。我的疑问在于treatas的用法只能按照这个table3的写法这样写吗?其实在table2中,劳务队名称这一列是具有数据沿袭的,只有最早时间这一列没有,但是在table3中,劳务队名称在一列也在treatas的第二参数中写了出来,感觉有点重复,这感觉应该是treatas的语法问题?
麻烦高飞老师解释一下!

批注 2020-05-10 105846.jpg
成员
warwick3518

输了验证码,还是看不到,仍然是原先的界面

DAX 圣经

导读

初识 DAX

DAX 基础知识

DAX 原理

DAX 高级原理

基础函数类型

迭代函数

CALCULATE 函数

CALCULATE 调节器

基础表函数

条件判断函数

查找匹配函数

时间智能函数

统计类函数

投影函数

分组/连接函数

集合函数

其他函数