递归WITH需显式设MAXRECURSION(SQL Server)或CYCLE(PostgreSQL),锚点与递归成员字段必须严格一致,JOIN须用INNER,用量需累乘,且BOM表须建覆盖索引支撑递归查询。

递归WITH在SQL Server中必须声明MAXRECURSION
不加限制的递归可能栈溢出或无限循环,SQL Server默认只允许100层,BOM层级深时会直接报错Msg 530。必须在查询末尾显式加OPTION (MAXRECURSION n),n建议设为实际最大深度+10,比如预估最多12层就写OPTION (MAXRECURSION 20)。
常见错误是把OPTION放在CTE定义里或UPDATE语句后——它只能紧贴在最终SELECT语句末尾。另外,MAXRECURSION 0虽表示无限制,但生产环境严禁使用,BOM环路(比如A→B→A)会导致死循环卡住连接。
BOM递归CTE必须包含锚点和递归成员的明确分隔
锚点部分要严格筛选顶层物料(如WHERE ParentItem IS NULL或WHERE Level = 0),不能漏掉根节点;递归成员必须用INNER JOIN关联到上一层的子项,且JOIN条件里必须引用递归CTE自身别名(如FROM BOM_CTE b INNER JOIN dbo.BillOfMaterials bom ON b.ItemID = bom.ParentItem)。
容易踩的坑:
- 锚点用了
SELECT *但递归成员字段顺序/类型不一致 → 报错Types don't match between the anchor and the recursive part - 递归JOIN写成
LEFT JOIN→ 产生NULL行,后续层级全崩 - 没对
Quantity做累积计算(如子项用量 = 父项用量 × 本级倍数),导致最终汇总数量错误
PostgreSQL用WITH RECURSIVE语法,但需注意cycle detection
PostgreSQL不支持MAXRECURSION,改用CYCLE子句防环路:WITH RECURSIVE bom AS ( ... ) CYCLE item_id SET is_cycle USING path。这个path字段会自动记录遍历路径,is_cycle为true即发现闭环。
实际使用中:
-
CYCLE必须声明在CTE定义末尾,且path字段类型要匹配(通常是text[]或varchar[]) - 若BOM表主键是
varchar,path初始化得写ARRAY[parent_id],不能漏ARRAY构造函数 - MySQL 8.0+也支持
WITH RECURSIVE,但不支持CYCLE,得靠应用层校验或加LEVEL字段人工截断
性能关键:BOM表必须有复合索引支撑递归JOIN
递归过程每层都要按父项查子项,最常走的查询模式是WHERE ParentItem = ?。如果只有单列ParentItem索引,而实际查询还带WHERE ParentItem = ? AND Status = 'Active',就会索引失效。
推荐建复合索引:
- SQL Server:
CREATE INDEX IX_BOM_ParentItem_Status ON dbo.BillOfMaterials (ParentItem, Status) INCLUDE (ComponentItem, Quantity) - PostgreSQL:
CREATE INDEX CONCURRENTLY ON bill_of_materials (parent_item, status) INCLUDE (component_item, quantity) - 没覆盖索引时,递归10层可能触发数十万次表扫描,响应从毫秒级变成秒级
BOM拆解真正难的不是语法,而是环路检测、用量累乘精度、以及索引没建对时那种“看着SQL没错但跑得奇慢”的排查——这些地方一漏,上线后第一张大BOM单就卡住整个MRP跑批。

















