Power BI之DAX神功:第3卷第2回 处理父子层级结构
声明:父子层级结构难点不在于DAX函数,在于表格的数据清洗,洗不出来就很难做出来。99%的情况下是不需要使用度量值生成父子层级结构的。直接在矩阵上用鼠标拖拽就可以满足大部分需求,只有精益求精以及特殊需求才会用到。
一、理解层级结构

不同的表格会有不同的深度,从单位到家庭,都有它自己的层级,数量均不相同。例如:左图有3个层级,右图有2个层级。我们以左图为例,做出如下表格:

// 爸爸和叔叔的上一级是爷爷,小明和小红上一级是爸爸,叔叔还没有孩子
二、制作层级序号
众所周知,PowerBI中有DAX和M两种语言,M函数负责数据清洗,DAX负责建模分析。而这件事情,就应该在导入数据时先进入PowerBI中的PowerQuery模块进行数据清洗。
视频名称是: 《孙兴华 PowerQuery M函数》全107集
https://www.bilibili.com/video/BV1oa4y1j75e

现在我们使用模拟数据:为什么说它是模拟数据,因为真实数据肯定有大类编码、中类编码、商品编码等。

我们想将模拟数据表标注层级关系,如下图所示:将原表变成目标表

PowerQuery M函数 代码如下:方法不唯一
Ps: 这里只是举例,不同的表格有不同的处理方法,而且方法有很多种。如果你没有学过我讲的PowerQuery课程,不易理解上面的代码。建议系统学习。
三、PATH函数
2021年11月2日我在《Power BI DAX 论数据清洗的重要性》视频中首次使用
https://www.bilibili.com/video/BV13R4y1E774?spm_id_from=333.999.0.0
语法:PATH ( <当前行序号>, <父级序号> ) 作用:返回路径

【新建列】路径 = PATH('Sheet3'[序号],Sheet3[父级])

就个函数就好比,有一个文件:孙兴华.txt 我想知道文件的绝对路径:
C:\免费讲课\喜欢nueco\酷爱dbykt\孙兴华.txt
解读:
第1层:C盘根目录
第2层:文件夹:免费讲课
第3层:文件夹:喜欢nueco
第4层:文件夹:酷爱dbykt
第5层:文件:孙兴华.txt
四、PATHITEM函数
语法:PATHITEM ( 路径, 位置, [返回类型]) // 从左向右
语法:PATHITEMREVERSE ( 路径, 位置, [返回类型]) // 从右向左
例如: PATHITEM ( "1|3|5", 3) // 返回:5
例如: PATHITEMREVERSE ( "1|3|5", 3) // 返回:1
常用返回类型:
整数 (Integer)、十进制数 (Float)、货币 (Currency)、日期 (DateTime)、布尔值 (TRUE/FALSE)、文本 (String)
【新建列】第1级 = LOOKUPVALUE('Sheet3'[名称],Sheet3[序号],PATHITEM('Sheet3'[路径],1,INTEGER))
【新建列】第2级 = LOOKUPVALUE('Sheet3'[名称],Sheet3[序号],PATHITEM('Sheet3'[路径],2,INTEGER))
【新建列】第3级 = LOOKUPVALUE('Sheet3'[名称],Sheet3[序号],PATHITEM('Sheet3'[路径],3,INTEGER))
//《火力全开》第2课 重点讲了LOOKUPVALUE
// 语法:Lookupvalue(把哪张表的哪个列拿过来,找那张表上的谁?,找自己表里的谁?)

如果只有3列,你就按上面的方法做就好了,如果有100列,你要新建100列吗?我建议,将数据复制到Excel中使用PowerQuery分成N列后再回到PowerBI中
注意:不能从PowerBI进入PowerQuery因为路径这一列是你在DAX中新建的列,PowerBI中的PQ不识别。以下代码可以满足不同需求使用
Power Query M函数:不填充

PowerQuery M函数 :填充

友情提示:如果没有看过我的PowerQuery 107集合集,很难看懂M函数的代码
五、PATHLENGTH 函数
语法:PATHLENGTH ( 路径) 返回:路径有几层

六、PATHCONTAINS ( 路径, 查找项目 )

七、我们使用真实案例


【1】我们将商品表处理成目标表(带有本级序号和父级序号)

PowerQuery M函数代码如下:
【2】处理销售表,让销售表匹配商品表中的序号
实现商品表序号是主键,销售表序号是外键
商品表与销售表做合并查询,实现下面的样子:

【3】建立关系(一对多关系)
现在可以实现连线关系了

【4】对商品表新建列
函数我们在上面都介绍过了
【新建列】路径 = PATH('商品表'[序号],'商品表'[父级序号])
【新建列】第1级 = LOOKUPVALUE('商品表'[商品名称],'商品表'[序号],PATHITEM('商品表'[路径],1,INTEGER))
【新建列】第2级 = LOOKUPVALUE('商品表'[商品名称],'商品表'[序号],PATHITEM('商品表'[路径],2,INTEGER))
【新建列】第3级 = LOOKUPVALUE('商品表'[商品名称],'商品表'[序号],PATHITEM('商品表'[路径],3,INTEGER))
友情提示:序号和父级序号数据类型必需是整数

如果你看着第2级和第3级为空不舒服,可以通过公式补全他。
用DAX函数处理方法,修改第2级和第3级新建列公式:
有两个思路任选其一:
思路1:if(PATHITEM(路径,2)=Blank(),第1级,第2级)
// 如果路径第2层是空的,就返回第1级,否则返回第2级
思路2:if(PATHLENGTH(路径)=1,第1级,第2级)
// 如果路径的层级是1,就返回第1级,否则返回第2级
我们以学习为目的,将2个思路分别应用于第2级和第3级,效果相同,你任选其一
【新建列】第2级 = if(PATHITEM([路径],2)=Blank(),'商品表'[第1级],LOOKUPVALUE('商品表'[商品名称],'商品表'[序号],PATHITEM('商品表'[路径],2,INTEGER)))
【新建列】第3级 = if(PATHLENGTH('商品表'[路径])<=2,'商品表'[第2级],LOOKUPVALUE('商品表'[商品名称],'商品表'[序号],PATHITEM('商品表'[路径],3,INTEGER)))
忠告:没有作弊的公式,只能不停锻炼的大脑,思考最重要。

【5】制作层级关系



【6】放到矩阵

向下钻取:我们在《跟着孙兴华学习数据分析第五季》图表篇中讲过

我们发现红框内的显示是多余的,如果不是为了去掉它们,我们根本没必要这么麻烦,搞什么层级结构

【7】在商品表中新建列
【新建列】有几层 = PATHLENGTH('商品表'[路径])
// 函数我们在上面讲过了,返回路径有几层

【8】找规律
【度量值】
第1层是否被筛选 = ISFILTERED('商品表'[第1级])
第2层是否被筛选 = ISFILTERED('商品表'[第2级])
第3层是否被筛选 = ISFILTERED('商品表'[第3级])
筛选深度 = [第1层是否被筛选]+[第2层是否被筛选]+[第3层是否被筛选]
// True是1,False是0,加在一起,看看筛选了几个
层次深度 = max('商品表'[有几层])
总销量 = sum('销售表'[销量])

【9】解决问题
我们找到上图的两个规律,问题就解决了
【度量值】筛选等于最大层级返回空 = if([筛选深度]=[层次深度],BLANK(),[总销量])

现在我们的目的是,返回普通黄瓜、秋黄瓜、国产土豆、荷兰土豆对应的值,而不返回子级苹果、西瓜、香蕉对应的值。
以下两种新建列的方法,都可以使用,在商品表中新建列
// VAR 指定某列时,代表当前行指定单元格的值,详见《DAX神功》第1卷第14回

【度量值】第1种方法唯一值 = SELECTEDVALUE('商品表'[第一种方法])
// 《DAX神功》第2卷第19回 关于SELECTEDVALUE函数的原理

【度量值】满足两个条件 = if([筛选深度]=[层次深度] && [第1种方法唯一值],[总销量])
// 这样就返回我们需要的层级结果

我们将刚才用过的两个公式写成一个:
筛选等于最大层级返回空 = if([筛选深度]=[层次深度],BLANK(),[总销量])
满足两个条件 = if([筛选深度]=[层次深度] && [第1种方法唯一值],[总销量])
写成
结果 = if([筛选深度]=[层次深度] && [第1种方法唯一值],[总销量],if([筛选深度]=[层次深度],BLANK(),[总销量]))
终于成功了!真累!

《孙兴华讲PowerBI火力全开》PowerBI必学课程
https://www.bilibili.com/video/BV1qa4y1H7wp
《DAX神功》文字版合集:
https://www.bilibili.com/read/readlist/rl442274
《DAX神功》视频版合集:
https://www.bilibili.com/video/BV1YE411E7p3
PowerBI(DAX函数)、PowerQuery(M函数)、Python办公自动化、Python爬虫、Python数据分析、ExcelVBA、WordVBA、AccessVBA、MySQL等等