如何在BOM动态层级结构中实现子节点关联多个父节点?
物料清单(BOM)中子节点关联多父节点的实现方案
一、先说说你当前方案的问题
你现在用逗号分隔ParentID(比如ID8、9的ParentID填6,7)的方式,虽然能实现多父关联,但实际用起来会踩很多坑:
- 不符合数据库设计规范,没法用外键约束保证数据的有效性,很容易出现无效的父ID
- 查询父子关系时得写字符串拆分逻辑,不仅麻烦,数据量上来后性能会很差
- 想统计某物料被多少父节点使用、追溯物料来源时,操作会非常繁琐
二、标准实现方案:新增关联中间表
这是处理多对多父子关系的常规操作,核心是把物料基础信息和父子关联关系分开存储:
1. 物料主表(保留原有核心字段,移除ParentID)
| ID | BOMLevel | BOMType | Name |
|---|---|---|---|
| 1 | 1 | EndProduct | PName1 |
| 2 | 1 | EndProduct | PName2 |
| 3 | 2 | Assemblies | AsseName1 |
| 4 | 2 | Assemblies | AsseName2 |
| 5 | 2 | Assemblies | AsseName3 |
| 6 | 3 | SubAssemblies | SubAsseName1 |
| 7 | 3 | SubAssemblies | SubAsseName2 |
| 8 | 4 | RawMaterial | RawMName1 |
| 9 | 4 | RawMaterial | RawNName1 |
2. BOM关联表(专门存父子关系)
每一条记录对应一对父子关联,比如你的两种原材料关联两个子组件,就拆成4条记录:
| ParentID | ChildID |
|---|---|
| 1 | 3 |
| 1 | 4 |
| 2 | 5 |
| 3 | 6 |
| 4 | 7 |
| 6 | 8 |
| 7 | 8 |
| 6 | 9 |
| 7 | 9 |
三、这个方案的好处和常用查询示例
优势
- 数据结构清晰,符合数据库设计范式
- 可以加外键约束,避免出现不存在的父/子ID
- 查询、统计操作简单高效,不用处理字符串拆分
常用查询示例
1. 查某物料的所有父节点(比如RawMName1的父节点)
SELECT m.Name AS 父节点名称 FROM BOM_Relationships br JOIN Material m ON br.ParentID = m.ID WHERE br.ChildID = 8;
2. 查某父节点的所有子节点(比如SubAsseName1的子节点)
SELECT m.Name AS 子节点名称 FROM BOM_Relationships br JOIN Material m ON br.ChildID = m.ID WHERE br.ParentID = 6;
3. 递归查询成品的全层级BOM(比如PName1的所有物料)
用递归CTE可以直接拉出完整层级:
WITH RECURSIVE BOM层级 AS ( SELECT m.ID, m.Name, m.BOMLevel, m.BOMType, CAST(m.Name AS VARCHAR(1000)) AS 路径 FROM Material m WHERE m.ID = 1 UNION ALL SELECT m.ID, m.Name, m.BOMLevel, m.BOMType, CONCAT(bh.路径, ' -> ', m.Name) AS 路径 FROM BOM层级 bh JOIN BOM_Relationships br ON bh.ID = br.ParentID JOIN Material m ON br.ChildID = m.ID ) SELECT * FROM BOM层级;
四、退而求其次的方案:拆分重复记录
如果不想新增中间表,也可以把同一个子节点拆成多条记录,每条对应一个父ID:
| ID | ParentID | BOMLevel | BOMType | Name |
|---|---|---|---|---|
| 1 | NULL | 1 | EndProduct | PName1 |
| 2 | NULL | 1 | EndProduct | PName2 |
| 3 | 1 | 2 | Assemblies | AsseName1 |
| 4 | 1 | 2 | Assemblies | AsseName2 |
| 5 | 2 | 2 | Assemblies | AsseName3 |
| 6 | 3 | 3 | SubAssemblies | SubAsseName1 |
| 7 | 4 | 3 | SubAssemblies | SubAsseName2 |
| 8 | 6 | 4 | RawMaterial | RawMName1 |
| 8 | 7 | 4 | RawMaterial | RawMName1 |
| 9 | 6 | 4 | RawMaterial | RawNName1 |
| 9 | 7 | 4 | RawMaterial | RawNName1 |
但这种方案会让物料表出现重复行,后续维护物料的规格、库存等信息时会很麻烦,所以优先推荐中间表方案。
内容的提问来源于stack exchange,提问作者newby
相关产品推荐
相关产品推荐

