[知识体系] 理解数据沿袭 Data Lineage

  [复制链接]
查看118949 | 回复124 | 2021-2-21 19:32:15 | 显示全部楼层 |阅读模式
本帖最后由 XF 于 2021-2-22 14:51 编辑

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



7141211936391.png



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

DAX 中的 Lineage

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

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

数据沿袭不取决于列值

观察下面这个由两个颜色值组成的度量值将始终得到总销售额,因为这个CALCULATE 没有进一步应用任何筛选


7141211936392.png

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

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

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


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

  1. Test :=
  2. CALCULATE (
  3.     [Sales Amount],
  4.     FILTER ( ALL ( 'Product'[Color] ), 'Product'[Color] IN { "Red", "Blue" } )
  5. )
复制代码

数据沿袭不取决于列名

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

  1. EVALUATE
  2. ADDCOLUMNS (
  3.     SELECTCOLUMNS (
  4.         VALUES ( 'Product'[Category] ),
  5.         "New name for Category", 'Product'[Category]
  6.     ),
  7.     "Amt", [Sales Amount]
  8. )
复制代码


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





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


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

  1. EVALUATE
  2. FILTER (
  3.     ADDCOLUMNS (
  4.         CROSSJOIN (
  5.             VALUES ( 'Product'[Category] ),
  6.             VALUES ( 'Date'[Calendar Year] )
  7.         ),
  8.         "Amt", [Sales Amount]
  9.     ),
  10.     [Amt] > 0
  11. )
复制代码


7141211936393.png

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

改变数据沿袭
修改列表达式失去沿袭

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

  1. EVALUATE
  2. ADDCOLUMNS (
  3.     SELECTCOLUMNS (
  4.         VALUES ( 'Product'[Category] ),
  5.         "New name for Category", 'Product'[Category] & ""
  6.     ),
  7.     "Amt", [Sales Amount]
  8. )
复制代码


7141211936394.png

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

使用 TREATAS 改变沿袭



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

  1. EVALUATE
  2. VAR Categories =
  3.     DATATABLE (
  4.         "Category", STRING,
  5.         {
  6.             { "Category" },
  7.             { "Audio" },
  8.             { "TV and Video" },
  9.             { "Computers and geeky stuff" },
  10.             { "Cameras and camcorders" },
  11.             { "Cell phones" },
  12.             { "Music, Movies and Audio Books" },
  13.             { "Games and Toys" },
  14.             { "Home Appliances" }
  15.         }
  16.     )
  17. RETURN
  18.     ADDCOLUMNS (
  19.         TREATAS (
  20.             Categories,
  21.             'Product'[Category]
  22.         ),
  23.         "Amt", [Sales Amount]
  24.     )
复制代码


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



7141211936395.png


在实战中修改数据沿袭

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


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

  1. FirstDaySales v1 :=
  2. SUMX (
  3.     'Product',
  4.     VAR FirstSale =
  5.         CALCULATE (
  6.             MIN ( Sales[Order Date] )
  7.         )
  8.     RETURN
  9.         CALCULATE (
  10.             [Sales Amount],
  11.             'Date'[Date] = FirstSale
  12.         )
  13. )
复制代码


7141211936396.png

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


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


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

  1. FirstDaySales v2 :=
  2. VAR ProductsWithSales =
  3.     SUMMARIZE (
  4.         Sales,
  5.         'Product'[Product Name]
  6.     )
  7. VAR ProductsAndFirstDate =
  8.     ADDCOLUMNS (
  9.         ProductsWithSales,
  10.         "Date First Sale", CALCULATE (
  11.             MIN ( Sales[Order Date] )
  12.         )
  13.     )
  14. VAR Result =
  15.     SUMX (
  16.         ProductsAndFirstDate,
  17.         VAR DateFirstSale = [Date First Sale]
  18.         RETURN CALCULATE (
  19.             [Sales Amount],
  20.             'Date'[Date] = DateFirstSale
  21.         )
  22.     )
  23. RETURN Result
复制代码


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

  1. FirstDaySales v3 wrong :=
  2. VAR ProductsWithSales =
  3.     SUMMARIZE (
  4.         Sales,
  5.         'Product'[Product Name]
  6.     )
  7. VAR ProductsAndFirstDate =
  8.     ADDCOLUMNS (
  9.         ProductsWithSales,
  10.         "Date First Sale", CALCULATE (
  11.             MIN ( Sales[Order Date] )
  12.         )
  13.     )
  14. RETURN CALCULATE (
  15.         [Sales Amount],
  16.         ProductsAndFirstDate
  17.     )
复制代码


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

游客,如果您要查看本帖隐藏内容请回复


        

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

测试你对 Lineage 的理解

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


7141211936397.png

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


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

  1. CALCULATETABLE (
  2.     Currency,
  3.     FILTER (
  4.         CROSSJOIN (
  5.             VALUES ( Currency[Currency Code] ),
  6.             SELECTCOLUMNS (
  7.                 VALUES ( Currency[Currency Code] ),
  8.                 "Another", Currency[Currency Code]
  9.             )
  10.         ),
  11.         [another] <> Currency[Currency Code]
  12.     )
  13. )
复制代码
  1. CALCULATETABLE (
  2.     Currency,
  3.     FILTER (
  4.         CROSSJOIN (
  5.             VALUES ( Currency[Currency Code] ),
  6.             SELECTCOLUMNS (
  7.                 VALUES ( Currency[Currency Code] ),
  8.                 "Another", Currency[Currency Code] & ""
  9.             )
  10.         ),
  11.         [another] <> Currency[Currency Code]
  12.     )
  13. )
复制代码


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

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


本题考查对数据沿袭和筛选上下文的理解,请仔细推导查询中每一步得到的结果
  • 查询 1 返回空表
  • 查询 2 返回完整的 Currency 表


        
   
两个查询都在内层的 CROSSJOIN 中生成了 Currency Code 自身的笛卡尔积,已知 Currency Code 有 28 个值,所以 CROSSJOIN 生成的表一共有 28*28=784 行,结果如下



7141211936398.png

CROSSJOIN 的结果



接下来 FILTER 对 CROSSJOIN 的结果进行过滤,剔除了具有相同值的行,经过这一步操作,表中没有任何一行的值是相同的。



7141211936399.png

FILTER 过滤之后的表



过滤后的表被作为 CALCULATETABLE 的筛选器参数,用于计算第一参数 Currency 表,到这一步我们终于可以开始讨论两个查询唯一的不同之处,第 8 行的 Another 表达式的写法:

  • 查询 1:“Another”, Currency[Currency Code]
  • 查询 2:”Another”, Currency[Currency Code] & “”


通过阅读本文之前的内容,你应该已经了解到,查询 2 用连接符增加了一个空表达式,这种写法虽然不会改变列值,但却更改了Currency Code 列的沿袭,所以查询 2 的 Another 列不再具有源表 Currency 的沿袭,而查询 1 仍然保留了这种沿袭。


一旦理解了这一点,接下来的就是筛选上下文的考察了:查询 1 的两列都有具有源表 Currency code 列的沿袭,可以有效的筛选 Currency 表,但由于 FILTER 过滤了相同值的行,查询 1 的筛选器参数试图得到货币代码等于 A 的同时又等于 B 的记录,这当然不会筛选出任何结果,所以查询 1 返回空表;而查询 2 只有一列具备 Currency 表的沿袭,且其中包含所有 Currency code 的值,所有查询 2 最终得到完整的 Currency 表。


71412119363910.zip (67.72 KB, 下载次数: 0)
回复

使用道具 举报

2046 | 2021-4-24 18:35:14 来自手机 | 显示全部楼层
呵呵,低调,低调!
回复

使用道具 举报

抄起一板砖 | 2021-6-1 12:13:21 来自手机 | 显示全部楼层
楼主呀,,,您太有才了。。。
回复

使用道具 举报

hbdhua | 2021-6-7 13:31:19 | 显示全部楼层
呵呵,明白了
回复

使用道具 举报

剑心 | 2021-7-8 07:25:02 | 显示全部楼层
是爷们的娘们的都帮顶!大力支持
回复

使用道具 举报

BG7LHT | 2021-8-26 07:43:33 来自手机 | 显示全部楼层
回个帖子支持一下!
回复

使用道具 举报

不圆不方 | 2021-10-16 15:30:27 来自手机 | 显示全部楼层
发发呆,回回帖,工作结束~
回复

使用道具 举报

lagxxxx | 2021-10-18 23:16:03 | 显示全部楼层
努力学习中
回复

使用道具 举报

luanmm | 2021-10-19 13:12:21 来自手机 | 显示全部楼层
不错 支持一个了
回复

使用道具 举报

天天压马路 | 2021-10-29 21:45:29 来自手机 | 显示全部楼层
呵呵。。。.....
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则