sqlite 3.8.3+虽支持with recursive,但若编译未启用sqlite_enable_recursive则失效;此时需用应用层循环调用单层视图模拟bom递归展开,或使用临时视图封装递归查询。

SQLite 不支持标准递归 CTE 的变通方案
SQLite 从 3.8.3 版本起确实支持 WITH RECURSIVE,但很多嵌入式场景(如旧版 Android、某些 Electron 应用或精简 SQLite 构建)仍可能禁用该功能。若你执行 WITH RECURSIVE ... 报错 no such function: recursive 或直接提示语法错误,说明当前 SQLite 编译时未启用 ENABLE_RTREE 和/或 ENABLE_FTS5 之外的关键开关 —— 实际上是 ENABLE_RECURSIVE_TRIGGERS 并不相关,真正需要的是编译时定义 SQLITE_ENABLE_recursive(注意下划线)。别急着重编译,先确认版本和能力:
SELECT sqlite_version(), compile_options LIKE '%ENABLE_RECURSIVE%';
返回 0 表示不可用,此时视图本身无法递归,必须换思路。
用普通视图 + 应用层循环模拟 BOM 展开
视图在 SQLite 中是静态查询封装,不能带参数、不能递归、也不能引用自身。所以「通过视图实现递归 BOM」的准确理解是:用视图封装单层子项查询,再由外部代码反复调用。假设你的 BOM 表结构为:
CREATE TABLE bom ( parent TEXT, child TEXT, qty INTEGER );
那么可建一个清晰的单层展开视图:
CREATE VIEW bom_level_1 AS SELECT parent, child, qty FROM bom;
后续操作交给应用逻辑,例如 Python 中:
- 初始化栈:
stack = [('A', 1)](根物料 A,需求数量 1) - 每次 pop 一个
(item, multiplier),查SELECT child, qty FROM bom_level_1 WHERE parent = ? - 将结果乘以
multiplier后推入栈(如 A→B qty=2,则 B 入栈为('B', 1*2)) - 重复直到栈空;累计各
child的总需用量
这种模式完全绕过 SQLite 递归限制,且易于调试、支持条件过滤(如只展开到第 3 层)、便于加缓存。
启用递归 CTE 后,视图仍不能递归,但可封装 WITH 查询
即使 SQLite 支持 WITH RECURSIVE,你也**不能**在视图定义里写递归 CTE —— SQLite 明确禁止视图中使用 WITH(报错 near "WITH": syntax error)。正确做法是把完整递归逻辑写成独立查询,或封装为临时视图(仅会话有效):
CREATE TEMP VIEW bom_full AS WITH RECURSIVE tree(parent, child, qty, level) AS ( SELECT parent, child, qty, 1 FROM bom WHERE parent = 'A' UNION ALL SELECT b.parent, b.child, b.qty * t.qty, t.level + 1 FROM bom b JOIN tree t ON b.parent = t.child WHERE t.level <p>注意几点:</p>
-
CREATE TEMP VIEW只在当前连接有效,关闭连接即消失 -
WHERE parent = 'A'是硬编码,无法参数化;如需动态根节点,必须拼 SQL 或用绑定参数重执行整个WITH查询 level 是安全兜底,防止环形引用导致无限循环- 递归分支中
b.qty * t.qty是典型 BOM 数量叠加逻辑,别漏乘上层数量
环形引用检测与性能陷阱
BOM 数据常含隐性循环(如 A→B→C→A),递归 CTE 默认不检测,会卡死或超限报错 too many levels of recursion。手动加环检测需额外字段或临时表,成本高。更务实的做法是:
- 入库前用应用层校验图结构(如 DFS 记录路径 Set)
- 查询时强制加
LIMIT 1000到递归 CTE 的顶层查询,而非仅靠level - 对高频查询的根物料,预计算并缓存扁平化结果到新表,用触发器维护一致性
- 避免在递归 CTE 中 JOIN 大表或用 LIKE 模糊匹配,每层都会放大数据集
视图在这里只是壳,真正的复杂度在数据建模和查询边界控制上——别指望一个 CREATE VIEW 解决 BOM 递归的所有问题。











