Lineage 一词最常用于指血统,意为“来自祖先的直系血脉”,这个词的翻译有很多版本,这里我参照微软官方文档,将其译作数据沿袭,如果你看到类似“数据血统”、“数据血脉”、“数据继承”、“数据谱系”等词汇,它们大概率都指向 Data Lineage。在数据库领域,Lineage 用来追踪经过转换、加工后的数据的源头,Power BI Service 的数据流服务就提供类似的功能
与上面的描述不同,Lineage 在 DAX 中有更丰富的含义,以下我们都将围绕 DAX 中 Lineage 的进行介绍
DAX 中的 Lineage
在 DAX 中,数据沿袭是一个标记(Tag)。表的每一列都会分配这样一个标记,它的作用是标识数据模型中的原始列。我们已经知道表函数可以对模型中的一列或多列进行操作,当操作对象对应于数据模型的一个物理列时,因为每列都具有特定的沿袭,即使经过某些转换和加工,引擎仍然可以识别这种沿袭关系,从而可以更快地进行筛选操作。沿袭不取决于列的名称,也不取决列的值,从技术上讲,它是可以唯一标识列的内部标准。你无法通过 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 ) )
公式的结果正确,但是写法不是最优的。它迭代 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 相同的值,问题的原因就在于数据沿袭:
对于高级 DAX 用户,理解并灵活运用数据沿袭是一项重要的技能。它不像行上下文、筛选上下文和上下文转换那样基础。但它是将你和其他普通 DAX 用户区分开的重要概念之一。
测试你对 Lineage 的理解
DAX 引擎在内部跟踪物理列的沿袭,以确保 DAX 表达式应用了某些转换后仍然保留对原始列的引用。你可以用下面的问题测试自己对数据沿袭的理解,对两个相似查询的结果做出判断
基于此数据编写两个查询,请分别判断它们的结果
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 表
参考阅读:
请教两个地方:
1,“每计算一个产品都进行上下文转换”。上下文转换怎么了?有什么不好的地方吗?
2,“没有利用已有的关系。”这里的关系指的是啥?解这类问题必须要利用关系吗?
老师请问测试里哪里表示在筛选既等于A又等于B了啊
高老师,请教一个关于同比的问题,如下图,问题一:如何只计算(可比产品)就是2年都生产的产品同比;问题二:忽略产品,还是计算可比产品的同比,就是只要总计,不要明细,用来做汇总表。谢谢老师!忘了说,是有日期表关联的。
您好,在实战中修改数据沿袭第一个公式中,我将var定义在sums函数外面,为什么两个度量值出来的每行数据是一致,但是总计是不一样的呢?
“Computers and geeky stuff”跟其他有啥不一样?都是string啊
高老师,”而 Date First Sale 列中的日期没有日期表的数据沿袭,它只是 MIN 表达式的结果,有自己的数据沿袭,与数据模型中的表无关,无法有效筛选模型。解决方案是将这一列的数据沿袭更改为 Date[Date]”, 这里有点没明白,望解惑。
1.MIN ( Sales[Order Date] 为什么和数据模型中的表无关呢,Sales 和 Product 表不在一个模型中吗?
2. “Date First Sale”的沿袭来自Sales[Order Date],与’Product'[Product Name] 沿袭不同,所以他们组成的表作为筛选器失效。 那为什么将它映射到Date'[Date] 就可以了呢? Date'[Date] 和’Product'[Product Name] 有相同的沿袭吗?
老师,问一下DAX圣经第二版的情况,预计什么时候出版,哪个出版社
老师请帮忙看一下以下代码,计算新客户数量,输出的结果是累加的客户数量,为什么不是新客户的数量呢?,是数据沿袭不对吗?
新客户数1 =
VAR SUNMARIZE =
SUMMARIZE ( ‘订单明细’, ‘客户表'[客户名称] )
VAR firstorderdate =
ADDCOLUMNS ( SUNMARIZE, “首次订单日期”, CALCULATE ( MIN ( ‘订单明细'[日期] ) ) )
VAR lineage =
TREATAS ( firstorderdate, ‘客户表'[客户名称], ‘日期表'[日期] )
RETURN
CALCULATE ( DISTINCTCOUNTNOBLANK ( ‘客户表'[客户名称] ), lineage )
发现Q2算出来的计算表是跟Currency表是一模一样的。不是很理解CALCULATETABLE这个函数的意义是什么,或者是什么样的计值场景需要用它?另外,CALCULATETABLE这个函数,第一个参数是表,第二参数是布尔值表达式的话,返回的计算表结果倒是好理解,如果是表的表达式,那么是怎么理解表的表达式筛选第一参数表? 。还有,老师能不能取消一下验证码,对需要反复看的会员很不友好….
老师,请问查询语句在哪个平台写代码?写完的结果可以在Bi里作为变量调用吗?
还有上文查询2,筛选器一列具有沿袭,一列不具备沿袭,那只有具备的沿袭列有筛选器作用,另一列忽略了?我个人理解两列and关系,所以返回空白表?请指导,多谢
看了最后这个例子对于这句话有具体的理解了,不然觉得好抽象,谢谢老师!
SELECTCOLUMNS keep the data lineage of the columns assigned to a simple column reference. Any different expression breaks the data lineage.
老师,Calculatetable我们知道返回表,在形成表之前,若筛选器为一列值的表,且有多行,如何筛选计值?谢谢
感谢高飞老师这章的内容,其实这章里面个人觉得最有收获的就是通过treatas去建立已经失效的lineage,看了那个示例,我也根据自己的数据模型去写了一个类似的查询,见图片,大致内容为table1得到劳务队名称的不重复列,table2在table1的基础上添加上最早日期,table3改变了table2的数据沿袭,table4将table3作为筛选表得到想要的结果汇总。得到正确的结果后,我有个问题就是treatas这个函数之前我只会把它当作查找函数使用(类似于lookupvalue),在这里它的作用更大了,直接让一个原本无效的查询表(table2)改变其沿袭。我的疑问在于treatas的用法只能按照这个table3的写法这样写吗?其实在table2中,劳务队名称这一列是具有数据沿袭的,只有最早时间这一列没有,但是在table3中,劳务队名称在一列也在treatas的第二参数中写了出来,感觉有点重复,这感觉应该是treatas的语法问题?
麻烦高飞老师解释一下!
输了验证码,还是看不到,仍然是原先的界面