在Power Pivot中进行的部分,接下来这半节,我们要进入最激动人心的
第1个:DAX 函数——查找父级索引
DAX函数是在 Power BI 或 Power Pivot (Excel) 中使用的一套函数。
= LOOKUPVALUE(
'BOM表'[索引],
'BOM表'[BOM实例ID], 'BOM表'[BOM实例ID],
'BOM表'[BOM层级], 'BOM表'[父级层级]
)
此处这个公式将用于创建一个“新建列(计算列)”。
为了让你彻底明白,我们采用“剥洋葱”的方式,由表及里、从宏观到微观一层层来拆解这段代码函数:
🧅 第一层:最外层的直观目的(它是干嘛的?)
这段公式的终极目的是:“找爸爸的身份证号”。
在 BOM(物料清单)这种树状结构的数据中,每一行代表一个零件。这段代码的作用是:去原表中寻找当前零件的“父级零件”,并把那个父级零件的“索引号(ID)”提取过来,填在当前行。
🧅 第二层:核心函数的作用(LOOKUPVALUE)
LOOKUPVALUE 是 DAX 中非常强大的查找函数。你可以把它理解为 Excel 里增强版的 VLOOKUP 或 XLOOKUP。它的超能力在于可以同时满足多个条件进行精确查找。
它的基本语法结构是:LOOKUPVALUE( 要返回哪个列的值, 条件列1, 查找值1, 条件列2, 查找值2, ... )
🧅 第三层:逐个参数拆解(代码详解)
我们把你的公式对号入座,看看它包含了哪些条件:
'BOM表'[索引](要返回的结果)
- 指令:如果找到了符合条件的那一行,请把那一行的
[索引]值给我拿过来。
'BOM表'[BOM实例ID], 'BOM表'[BOM实例ID](查找条件 1)
- 指令:去目标表的
[BOM实例ID]列里面找,找什么呢?找和当前行的[BOM实例ID]一模一样的数据。 - 业务含义:BOM表里可能存了成百上千个不同的产品结构(实例)。这个条件确保了:“我们只在当前属于的这个大产品(同一个BOM树)里面找,别串号找到别的产品那里去了。”
'BOM表'[BOM层级], 'BOM表'[父级层级](查找条件 2)
- 指令:在满足了上面条件的基础上,再去目标表的
[BOM层级]列里面找,找和当前行的[父级层级]数值一样的数据。 - 业务含义:假设当前零件是 3 级(BOM层级=3),它的父亲是 2 级(父级层级=2)。那么我就要去表里找
[BOM层级]列等于 2 的那一行。这个条件确保了:“找到的必须是我的上一级(父级)。”
不理解第2个条件的话进一步看下面的解释:
你在这里有困惑非常常见!这是几乎所有刚接触 DAX 函数的人都会卡住的地方(专业术语叫“行上下文”的理解)。
你觉得“3”和“2”不可能相等,对吧?你的直觉是对的,但你误解了公式匹配的对象。
核心原因在于:你误以为它是在“当前行自己跟自己比”。
实际上,LOOKUPVALUE 的工作模式是:“拿着当前行的线索,去整张表里找别人”。
为了让你秒懂,我们换个“警察帮小明找爸爸”的场景来模拟这个过程:
🕵️♂️ 角色分配
- 当前行(小明):就是公式正在计算的这一行。
- 整张表(茫茫人海):就是你要去查找的目标范围。
- LOOKUPVALUE(警察):负责拿着线索找人的机器。
🔍 公式拆解:逗号前后的秘密
公式的这一段是:'BOM表'[BOM层级], 'BOM表'[父级层级]
这两个参数代表着完全不同的视角:
- 逗号前面的
'BOM表'[BOM层级]: 代表“茫茫人海中,每个人的自身属性”(警察要查验的身份证字段)。 - 逗号后面的
'BOM表'[父级层级]: 代表“当前行(小明)手里拿着的线索”。
🎬 情景模拟(为什么是 2 匹配 2,而不是 3 匹配 2)
现在,公式运行到了小明(当前零件)这一行:
- 小明(当前行)的真实数据是:
[BOM层级]= 3 (意思是:我是第 3 代,孙子辈)[父级层级]= 2 (意思是:我爸爸是第 2 代,儿子辈)
- 小明去警察局报案(代入公式):
- 小明说:“警察叔叔,我要找我爸。不用管我是第几代(不用管我的3),我只提供一个线索——我爸是第 2 代(我的父级层级是2)。”
- 警察(LOOKUPVALUE)开始去街上找人:
- 警察拿着小明给的线索“2”,去街上(整张表)拦住路人挨个查身份证。
- 警察问路人甲:“你的
[BOM层级]是几?” - 路人甲:“我是 4。” (4 不等于 2,匹配失败,跳过)
- 警察又问路人乙(目标行):“你的
[BOM层级]是几?” - 路人乙:“我是 2。” (2 等于小明给的线索 2,匹配成功!)
- 最终结果:
- 警察确认路人乙就是小明要找的人,然后把路人乙的
[索引](也就是身份证号)提取出来,交给了小明。
- 警察确认路人乙就是小明要找的人,然后把路人乙的
💡 总结
所以,根本不是让 3 去匹配 2。当前行的 [BOM层级]=3 这个信息,在这个查找动作中根本就没有被用到!
系统是拿着“当前行的 [父级层级](也就是 2)”,去寻找“另一行的 [BOM层级](等于 2 的那一行)”。
是 2 去匹配 2。 只是这两个 2 属于不同的行(一个是找人的线索,一个是被找人的自身属性)。
这样解释,是不是拨云见日了?
🧅 第四层:逻辑重组(大白话翻译)
把上面的条件组合起来,这段公式对数据库说的话就是:
“请在整个‘BOM表’里帮我找一行数据。
这行数据必须和我属于同一个产品(条件1:BOM实例ID相同),
并且这行数据的层级刚好是我的父级层级(条件2:BOM层级 = 我的父级层级)。
找到了之后,把那个父级零件的『索引』告诉我。”
🧅 最里层:隐藏的DAX机制与潜在风险(高手视角)
1. 为什么看起来像自己等于自己?
你可能会疑惑 ..., 'BOM表'[BOM实例ID], 'BOM表'[BOM实例ID], ... 这种写法。
在 DAX 的计算列中,存在“行上下文”。逗号前面的 'BOM表'[BOM实例ID] 代表整张表的这一列;逗号后面的 'BOM表'[BOM实例ID] 代表的是正在计算的当前这单行的这个值。系统会在后台自动区分。
2. 这个公式报错的风险在哪里?LOOKUPVALUE 有一个死穴:它要求找出的结果必须是唯一的。
如果在同一个 [BOM实例ID] 下面,有两个相同的 [BOM层级](比如同一个产品下面有两个并列的父级节点),系统就不知道该返回哪一个人的索引了,此时公式就会报错。
因此,写这个公式的前提是:在同一个 BOM实例中,每一个父级层级只能对应唯一的一个索引节点。 如果存在一个父级下有多个同层级节点的情况,这个公式需要修改逻辑或结合其它辅助列来保证唯一性。
第2个:DAX 函数——绘制家谱路径
接下来,结合上一张图,我们现在来到了处理 BOM(物料清单)或组织架构等“父子层级结构(Parent-Child Hierarchy)”时最核心、最经典的一步。
= PATH('BOM表'[索引], 'BOM表'[父项索引])
我们继续用“剥洋葱”的方式,层层拆解这个非常神奇的 PATH 函数:
🧅 第一层:最外层的直观目的(它是干嘛的?)
这段公式的终极目的是:“查户口” 或者说 “画出这个零件的完整族谱”。
在数据表里,零件之间只有“谁是谁的直接爸爸(父项)”这种单线联系。但实际业务中,我们经常需要知道:“这个底层的小螺丝钉,到底是属于哪个大组件,最终又属于哪个成品的?”PATH 函数的作用,就是把这种碎片化的父子关系,串成一条从老祖宗到当前零件的完整路径。
这个新计算而成的 [家谱路径] 计算列会生成类似 9726|9804 这样的爷 | 爸 | 孙的层级关系文本串。
🧅 第二层:核心函数的作用(PATH)
PATH 是 DAX 中专门为解决“层级扁平化”而量身定制的专用函数。
如果没有它,要在关系型数据表里找出一个节点的所有上级,需要写极其复杂的循环代码。而 PATH 能够自动顺藤摸瓜,一直往上找,直到找到没有上级的“老祖宗(根节点)”为止。
它的语法极其简单,只有两个参数:PATH( 当前节点的ID列, 当前节点的父级ID列 )
🧅 第三层:逐个参数拆解(代码详解)
对应到你的公式 = PATH('BOM表'[索引], 'BOM表'[父项索引]),我们来看看它要求传入什么:
'BOM表'[索引](参数1:我是谁?)
- 指令:告诉系统,代表当前这一行数据(这个零件)唯一身份的身份证号(ID)是哪一列。在你的表里,就是
[索引]。
'BOM表'[父项索引](参数2:我爸爸是谁?)
- 指令:告诉系统,记载当前零件的直接上一级(父亲)身份的列是哪一个。在你的表里,这正是上一张图那个逻辑(或者是类似的查找逻辑)找出来的
[父项索引]。
🧅 第四层:底层运行逻辑(结合这个实际BOM计算案例)
比如第一行数据来做个“慢动作回放”,看看系统在后台是怎么跑的:
- 系统看第一行:当前零件的
[索引]是 9804。 - 找爸爸:系统看了眼
[父项索引],发现它的直接上级是 9726。 - 继续往上找(递归):系统不会停下,它会在整张表里继续找
[索引]为 9726 的那一行,想看看 9726 的爸爸是谁。 - 到达顶点:假设 9726 就是顶层成品(例如那个“儿童学习椅”),它的
[父项索引]是空的(或者为 0,表示没有上一级了)。 - 生成结果:系统停止查找,把刚才经过的节点按从大到小的顺序用竖线
|连起来。 - 最终输出:于是这一行就得到了结果:
9726|9804。(意思是:我是 9804,我是 9726 的直接下属)。
如果是更深的层级,比如 9804 下面还有一个 10001,那么 10001 的路径就会变成 9726|9804|10001。
🧅 最里层:隐藏的DAX机制与潜在风险
看似简单的两个参数,其实埋着几个非常严格的“暗雷”,这也是很多新手经常报错的地方:
1. 数据类型必须绝对一致![索引] 和 [父项索引] 这两列的数据类型必须完全一样。你要么都是“文本型”,要么都是“整数型”。如果一个是文本一个是数字,哪怕看起来长得一样,PATH 也会立刻报错罢工。
2. 绝对不能出现“死循环”(Circular reference)!
大自然中,儿子不能是爸爸的爸爸。在数据里也一样:如果 A 的父项是 B,B 的父项是 C,结果你数据填错了,C 的父项填了 A。PATH 在顺藤摸瓜时就会陷入无限死循环,此时 DAX 引擎会直接报错。
3. 一个儿子只能有一个亲生爸爸!PATH 函数不支持“多重父级”结构。在同一个体系下,一个 [索引] 只能对应唯一的一个 [父项索引]。如果有交叉,它处理不了。
💡 进阶延伸:
生成了这个带有竖线的 9726|9804 有什么用呢?
它的最大价值是作为“跳板”。接下来,你可以配合 PATHITEM 函数(提取路径中的第几个节点)、PATHLENGTH 函数(计算这个零件处在第几个层级)或者 PATHCONTAINS 函数,极其方便地在 Power BI 或数据透视表中实现诸如“展开/折叠”、“计算某一大类下的所有物料成本”等高级功能!下面的计算穿透用量就是用到这个PATHCONTAINS 函数!
第3个:DAX函数——计算穿透用量
到这个函数,我们就来到了 BOM(物料清单)数据建模中最高级、也是最激动人心的一步了。
前面两步,你先是找到了“爸爸”(LOOKUPVALUE),然后画出了“整条家谱”(PATH)。现在这个公式,正是利用那条“家谱”来计算最终的成本或用量。
= VAR CurrentPath = 'BOM表'[家谱路径]
RETURN
PRODUCTX(
FILTER(
ALL('BOM表'),
PATHCONTAINS(CurrentPath, 'BOM表'[索引])
),
'BOM表'[标准用量]
)
我们继续“剥洋葱”,来看看这个精妙的公式是怎么运转的:
🧅 第一层:最外层的直观目的(它是干嘛的?)
这段公式的终极目的是:“计算为了造出一个最终成品,到底需要多少个当前的底层零件?”
业务背景:在制造业中,用量是层层相乘的。
比如:造 1 辆【自行车】需要 2 个【轮子】,造 1 个【轮子】需要 30 根【辐条】。
那么在这个 BOM 表里,【辐条】那一行的“标准用量”通常只写着 30(因为它是相对于轮子的用量)。
但老板问的是:“造 1 辆自行车到底需要几根辐条?” 答案显然是 1 X 2 X 30 = 60 根。
这个公式,就是为了算出这个 60(累计标准用量/穿透标准用量)。
🧅 第二层:核心函数的作用(PRODUCTX)
最外层的核心动作是 PRODUCTX。
SUMX是把一堆数字加起来;PRODUCTX则是把一堆数字乘起来。
它的语法是:PRODUCTX( 一张表, 要相乘的列 )
意思是:“请帮我把这张表里每一行的某个数字,连乘起来。”
🧅 第三层:逐行拆解(它是怎么工作的?)
这个公式分为准备阶段和执行阶段:
1. 准备阶段(VAR … RETURN)
VAR CurrentPath = 'BOM表'[家谱路径]
- 动作:定义一个变量(相当于找个小本子记下来)。把当前这行零件的“家谱路径”(比如上面所说的
9726|9804|10001)抄写在变量CurrentPath里备用。
2. 核心过滤阶段(FILTER + ALL + PATHCONTAINS)
FILTER(
ALL('BOM表'),
PATHCONTAINS(CurrentPath, 'BOM表'[索引])
)
这段代码是整个公式的灵魂,它的目的是“把这个零件的所有直系祖先(包括它自己)揪出来,单独成立一张虚拟的临时表”。
ALL('BOM表'):解除当前行的限制,把目光投向整张大表。PATHCONTAINS:核心侦探!它拿着刚才记在小本子上的家谱9726|9804|10001,去整张表里挨个问:“你的 ID(索引)在这串家谱里吗?”- 问到成品 9726,在里面!留下。
- 问到组件 9804,在里面!留下。
- 问到零件 10001(自己),在里面!留下。
- 问到隔壁老王的零件 9999,不在里面,滚蛋。
3. 执行连乘阶段(PRODUCTX 的收尾)
PRODUCTX( 刚才那张只有祖先和自己的临时表, 'BOM表'[标准用量] )
- 现在,
PRODUCTX拿到了一张只有 3 行数据的迷你表(成品9726、组件9804、零件10001)。 - 它把这 3 行对应的
[标准用量]提取出来:- 成品的用量:1
- 组件的用量:2
- 零件的用量:30
- 最后,把它们乘在一起:1 X 2 X 30 = 60。输出最终结果!
🎬 第四层:情景模拟(再体会一下)
我们回到“小明找亲戚”的故事里:
- 小明(底层零件 10001):想知道自己到底对家族有多大贡献。
- 写下族谱(VAR):小明掏出族谱看了一眼,上面写着“爷爷(9726)|爸爸(9804)|我(10001)”。
- 召集直系亲属(FILTER + PATHCONTAINS):小明对着全村人(
ALL('BOM表'))喊:“只要名字在我这张族谱上的,都给我站出来!” 于是爷爷、爸爸和小明自己,三个人站成了一排。 - 相乘计算(PRODUCTX):村长(
PRODUCTX)走过来,问爷爷要了 1 份钱,问爸爸要了 2 份钱,问小明要了 30 份钱。村长把这些数字乘在了一起,得出了 60。
💡 最里层:为什么这段代码极其巧妙?
如果你用传统的 Excel VLOOKUP 或者 SQL 来做这件事,简直是一场噩梦,因为层级深浅是不固定的(有的零件在第 2 层,有的在第 8 层),你不知道要写多少个相乘的公式。
但有了 PATHCONTAINS 和 PRODUCTX 的组合:
不管你这个零件藏得有多深(无论家谱路径有多长),它都能精准地把你路径上的每一个节点的用量抓出来,然后动态地连乘在一起!
这就是 DAX 处理层级结构(Hierarchy)最经典的黄金组合:
PATH铺路PATHCONTAINS寻亲PRODUCTX算账
看懂了这个公式,恭喜你,你已经迈入了 DAX 高阶应用的门槛!
第4个:DAX函数——计算物料成本
这个函数便是到了“过关斩将”的最后一步。
如果说前面几步我们是在“找亲戚”(LOOKUPVALUE/PATH)和“算人头”(PRODUCTX),那么这一步就是最终的“算总账”!这是我们千辛万苦把 BOM 展平的最终目的——核算成本。
= 'BOM表'[穿透用量] * RELATED('物料价格表'[含税单价])
我们继续用“剥洋葱”的方式,来看看这个既简单又极其强大的公式:
🧅 第一层:最外层的直观目的(它是干嘛的?)
这段公式的终极目的是:“计算当前这个零件,在整个最终成品里,一共要花多少钱?”
比如老板问:“造这一辆自行车,上面所有的【辐条】加起来要花多少钱?”
公式的逻辑就是最简单的小学数学:总用量 X 单价 = 总成本。
🧅 第二层:核心函数的作用(RELATED)
这个公式里只有一个真正的 DAX 函数:RELATED。
你可以把它理解为 “Excel 数据透视表(Power Pivot)里的超级 VLOOKUP”。
它的作用是:“顺着网线过去,把隔壁表里的数据给我拿过来。”
语法极其简单,只有一个参数:RELATED( 目标表里的目标列 )
🧅 第三层:逐个部分拆解(它是怎么算账的?)
我们把公式拆成乘号前后的两部分:
1. 乘号前面:'BOM表'[穿透用量]
- 这是什么?:这是我们在上一步(即
PRODUCTX函数计算出来的穿透用量)辛辛苦苦算出来的“最终累计用量”。 - 举个例子:造 1 辆自行车需要 2 个轮子,1 个轮子需要 30 根辐条。这里的
[穿透用量]就是 2 X 30 = 60 根。
2. 乘号后面:RELATED('物料价格表'[含税单价])
- 这是什么?:这是用
RELATED函数去另外一张叫'物料价格表'的表格里,查到的这个零件的“单价”。 - 举个例子:去价格表里查到,一根辐条的价格是 1.5 元。
3. 结合起来:
当前行的公式就是:60 X 1.5 = 90 元。
这 90 元,就是这行物料为整个项目贡献的总成本!
🎬 第四层:情景模拟(为什么它比 VLOOKUP 爽一万倍?)
如果你在普通的 Excel 表格里做这件事,你的公式大概得长这样:= [穿透用量] * VLOOKUP([物料编码], '物料价格表'!A:D, 4, FALSE )
你需要告诉 Excel:拿什么去查、去哪里查、返回第几列、还要精确匹配。每次写都特别烦!
但在 DAX(Power Pivot)里,小明(当前零件)找物料部拿价格的流程变了:
- 小明:对着对讲机喊了一句
RELATED('物料价格表'[含税单价])(给我报价!)。 - 系统(DAX):完全不需要问小明“你的物料编码是多少”,系统直接顺着后台早就连好的一根“隐形电缆”,瞬间去价格表里找到了对应小明的那一行,把价格扔了回来。
💡 最里层:隐藏的魔法与绝对前提
你可能会问:系统怎么这么聪明,它怎么知道去价格表里拿哪一行的价格?
这就是 Power Pivot/Power BI 最核心的魅力——数据模型(Data Model)中的“关系(Relationship)”,也就是我们连的那根1对多的线条!!!
这个公式能成功运行,有一个绝对的先决条件(隐藏前提):
在你的 Power Pivot 的“关系图视图”中,你一定早就把 'BOM表' 和 '物料价格表' 用线连起来了(通常是用“物料编码”这个字段作为桥梁,建立了一个 “一对多 (1:N)” 或 “一对一” 的关系)。
'物料价格表'是 “一” 的一端(字典表,每个零件只有一个唯一的报价)。'BOM表'是 “多” 的一端(事实表,同一个零件可能在不同层级、不同产品中出现多次)。
RELATED 函数的底层机制就是:它只能从“多”的这一端,顺着关系线,去“一”的那一端拿数据。
只要模型连线建好了,你以后需要取任何价格表里的字段(比如供应商名称、采购周期),不用再写复杂的条件,无脑用 RELATED(列名) 就能像变魔术一样直接拿过来用🎉。
第5个:DAX函数——识别是否末级
前面的步骤我们算出了路径、算出了用量、算出了单价。但如果在数据透视表里把所有的成本直接加起来,老板可能会被吓晕,因为成本会被“重复计算”!
为什么?因为“自行车的成本 = 轮子的成本 + 车架的成本”,而“轮子的成本 = 辐条的成本 + 轮胎的成本”。如果你把自行车、轮子、辐条的成本全加在一起,金额就翻倍膨胀了。
= VAR CurrentIndex = 'BOM表'[索引]
RETURN
IF(
CONTAINS('BOM表', 'BOM表'[父项索引], CurrentIndex),
"父项",
"末级"
)
为了解决这个问题,我们需要给每个零件打上一个“身份标签”,这就是这段公式的终极使命!我们继续“剥洋葱”:
🧅 第一层:最外层的直观目的(它是干嘛的?)
这段公式的目的是:判断当前这个零件,到底是“最年轻一辈(最底层零件)”,还是“底下还有后代的层级(组件/父项)”。
它会在表格里新增一列文本,要么显示 “父项”,要么显示 “末级”。
🧅 第二层:核心函数的作用(IF + CONTAINS)
这个公式组合了两个非常好用的基础函数:
IF( 条件, 满足时返回啥, 不满足时返回啥 ):最经典的逻辑判断,不用多说。CONTAINS( 去哪张表找, 搜查哪个字段, 搜查什么线索 ):这个函数是个“超级神探”。它的作用是去指定的表和列里扫街,只要发现哪怕一个匹配的线索,就立刻大喊一声“找到了!(返回 TRUE)”,如果全扫遍了都没有,就沮丧地说“没找到(返回 FALSE)”。
🧅 第三层:逐行拆解(它是怎么查户口的?)
1. 第一步:记住自己是谁(定义变量)
VAR CurrentIndex = 'BOM表'[索引]
- 动作:当前零件(比如一个小齿轮)把自己的身份证号
[索引]抄下来,存进名叫CurrentIndex的小本子里。
2. 第二步:去全村广播寻亲(核心判断)
CONTAINS('BOM表', 'BOM表'[父项索引], CurrentIndex)
- 动作:神探
CONTAINS拿着小齿轮的身份证号(CurrentIndex),去整张'BOM表'里的[父项索引](也就是记载“爸爸是谁”的那一列) 挨个比对。 - 灵魂拷问:“全村的人听着,你们的
[父项索引]列里,有没有人填的是我(CurrentIndex)的名字?”
3. 第三步:贴上身份标签(IF 返回结果)
IF( 刚才的判断结果, "父项", "末级" )
- 情况 A(找到了):只要神探在别人的
[父项索引]里发现了当前零件的 ID,说明什么?说明这村里有人喊当前零件叫爸爸!既然有儿子,那当前零件就是个组件/半成品。所以给它贴上标签:“父项”。 - 情况 B(没找到):神探把全村的
[父项索引]都看了一遍,没有任何一个人认当前零件做爸爸。说明什么?说明它就是个最底层的螺丝钉/采购件,没有下级辈了。于是贴上标签:“末级”。
🎬 第四层:情景模拟(再体会一下)
- 零件 A(轮子,ID=9804):去查户口。警察看了一下全村档案,发现“辐条”和“内胎”的爸爸一栏填的都是 9804。警察说:“有人认你当爹,你是个组件,给你盖章:父项。”
- 零件 B(辐条,ID=10001):去查户口。警察翻遍了全村的档案,没有任何一个零件的爸爸一栏填的是 10001。警察说:“你是最底层的小兵了,没有人叫你爹,给你盖章:末级。”
💡 最里层:为什么这一步在业务上极其关键?
回到我开头说的问题——如何防止成本重复计算?
有了这个 ["父项", "末级"] 的标签列,你在用 Power Pivot 画透视表或者写最终核算度量值的时候,只需要加上一个极其简单的过滤条件:
只计算 标签 = "末级" 的成本金额
为什么只算末级?
因为一个成品的总成本,在物理形态上,就是由所有最底层的采购件(螺丝、钢管、塑料粒子)堆积而成的!中间的组件(父项)只是物理组装的过程,它们的成本已经被包含在底层零件里了。如果你把末级和父项的成本一起加总,你的总金额会大得离谱。这个公式,就是你剔除虚假膨胀、拿到真实BOM材料成本底表的最后一把钥匙!🔑











暂无评论内容