EP024 PQ、PP、CUBE函数综合实战案例:制造业BOM展开卷算(上)

在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 里增强版的 VLOOKUPXLOOKUP。它的超能力在于可以同时满足多个条件进行精确查找

它的基本语法结构是:
LOOKUPVALUE( 要返回哪个列的值, 条件列1, 查找值1, 条件列2, 查找值2, ... )


🧅 第三层:逐个参数拆解(代码详解)

我们把你的公式对号入座,看看它包含了哪些条件:

  1. 'BOM表'[索引] (要返回的结果)
  • 指令:如果找到了符合条件的那一行,请把那一行的 [索引] 值给我拿过来。
  1. 'BOM表'[BOM实例ID], 'BOM表'[BOM实例ID] (查找条件 1)
  • 指令:去目标表的 [BOM实例ID] 列里面找,找什么呢?找和当前行[BOM实例ID] 一模一样的数据。
  • 业务含义:BOM表里可能存了成百上千个不同的产品结构(实例)。这个条件确保了:“我们只在当前属于的这个大产品(同一个BOM树)里面找,别串号找到别的产品那里去了。”
  1. 'BOM表'[BOM层级], 'BOM表'[父级层级] (查找条件 2)
  • 指令:在满足了上面条件的基础上,再去目标表的 [BOM层级] 列里面找,找和当前行[父级层级] 数值一样的数据。
  • 业务含义:假设当前零件是 3 级(BOM层级=3),它的父亲是 2 级(父级层级=2)。那么我就要去表里找 [BOM层级] 列等于 2 的那一行。这个条件确保了:“找到的必须是我的上一级(父级)。”

不理解第2个条件的话进一步看下面的解释:

你在这里有困惑非常常见!这是几乎所有刚接触 DAX 函数的人都会卡住的地方(专业术语叫“行上下文”的理解)。

你觉得“3”和“2”不可能相等,对吧?你的直觉是对的,但你误解了公式匹配的对象。

核心原因在于:你误以为它是在“当前行自己跟自己比”
实际上,LOOKUPVALUE 的工作模式是:“拿着当前行的线索,去整张表里找别人”

为了让你秒懂,我们换个“警察帮小明找爸爸”的场景来模拟这个过程:

🕵️‍♂️ 角色分配

  • 当前行(小明):就是公式正在计算的这一行。
  • 整张表(茫茫人海):就是你要去查找的目标范围。
  • LOOKUPVALUE(警察):负责拿着线索找人的机器。

🔍 公式拆解:逗号前后的秘密

公式的这一段是:'BOM表'[BOM层级], 'BOM表'[父级层级]

这两个参数代表着完全不同的视角:

  1. 逗号前面的 'BOM表'[BOM层级] 代表“茫茫人海中,每个人的自身属性”(警察要查验的身份证字段)。
  2. 逗号后面的 'BOM表'[父级层级] 代表“当前行(小明)手里拿着的线索”

🎬 情景模拟(为什么是 2 匹配 2,而不是 3 匹配 2)

现在,公式运行到了小明(当前零件)这一行:

  1. 小明(当前行)的真实数据是:
    • [BOM层级] = 3 (意思是:我是第 3 代,孙子辈)
    • [父级层级] = 2 (意思是:我爸爸是第 2 代,儿子辈)
  2. 小明去警察局报案(代入公式):
    • 小明说:“警察叔叔,我要找我爸。不用管我是第几代(不用管我的3),我只提供一个线索——我爸是第 2 代(我的父级层级是2)。”
  3. 警察(LOOKUPVALUE)开始去街上找人:
    • 警察拿着小明给的线索“2”,去街上(整张表)拦住路人挨个查身份证。
    • 警察问路人甲:“你的 [BOM层级] 是几?”
    • 路人甲:“我是 4。” (4 不等于 2,匹配失败,跳过)
    • 警察又问路人乙(目标行):“你的 [BOM层级] 是几?”
    • 路人乙:“我是 2。” (2 等于小明给的线索 2,匹配成功!
  4. 最终结果:
    • 警察确认路人乙就是小明要找的人,然后把路人乙的 [索引](也就是身份证号)提取出来,交给了小明。

💡 总结

所以,根本不是让 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表'[父项索引]),我们来看看它要求传入什么:

  1. 'BOM表'[索引] (参数1:我是谁?)
  • 指令:告诉系统,代表当前这一行数据(这个零件)唯一身份的身份证号(ID)是哪一列。在你的表里,就是 [索引]
  1. 'BOM表'[父项索引] (参数2:我爸爸是谁?)
  • 指令:告诉系统,记载当前零件的直接上一级(父亲)身份的列是哪一个。在你的表里,这正是上一张图那个逻辑(或者是类似的查找逻辑)找出来的 [父项索引]

🧅 第四层:底层运行逻辑(结合你的截图数据)

我们拿你截图里的第一行数据来做个“慢动作回放”,看看系统在后台是怎么跑的:

  1. 系统看第一行:当前零件的 [索引]9804
  2. 找爸爸:系统看了眼 [父项索引],发现它的直接上级是 9726
  3. 继续往上找(递归):系统不会停下,它会在整张表里继续找 [索引]9726 的那一行,想看看 9726 的爸爸是谁。
  4. 到达顶点:假设 9726 就是顶层成品(例如那个“儿童学习椅”),它的 [父项索引] 是空的(或者为 0,表示没有上一级了)。
  5. 生成结果:系统停止查找,把刚才经过的节点按从大到小的顺序用竖线 | 连起来。
  6. 最终输出:于是这一行就得到了结果: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 函数——计算穿透用量

第4个:DAX 函数——计算物料成本

第5个:DAX 函数——识别是否末级

© 版权声明
THE END
喜欢就支持一下吧
点赞786 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容