SQLite 3.8.3+ 支持 WITH RECURSIVE,旧版本(如3.7.x)不支持会报错“near 'RECURSIVE'”;替代方案包括多层 LEFT JOIN 查祖先链、UNION ALL 拼接固定深度后代,但深度需硬编码且维护性差。

SQLite里不能用WITH RECURSIVE?先确认版本和替代思路
SQLite 3.8.3+ 支持 WITH RECURSIVE,但很多嵌入式环境(比如旧版 Android、某些 IoT 设备)仍跑着 3.7.x 或更早版本,这时候 WITH RECURSIVE 直接报错:near "RECURSIVE": syntax error。别急着升级——非递归子查询确实能模拟有限层树形查询,关键是控制层级深度、避免笛卡尔爆炸。
用多层 JOIN 模拟固定深度的祖先链
假设表 categories 有 id、name、parent_id,你想查出某节点及其所有直接/间接父节点(最多 4 层),就不能靠单个子查询套娃,而要用显式 JOIN 链:
SELECT c0.name AS level_0,
c1.name AS level_1,
c2.name AS level_2,
c3.name AS level_3
FROM categories c0
LEFT JOIN categories c1 ON c0.parent_id = c1.id
LEFT JOIN categories c2 ON c1.parent_id = c2.id
LEFT JOIN categories c3 ON c2.parent_id = c3.id
WHERE c0.id = 123;这种写法本质是“展开树”,每层 JOIN 对应一个祖先层级。注意:
• 必须用 LEFT JOIN,否则缺失某层祖先时整行消失
• 列别名要区分层级,否则字段名冲突
• 深度上限硬编码在 SQL 里,5 层就得加第 5 个 JOIN —— 超过 4 层就明显难维护
用 UNION ALL 拼接各层结果(适合查“某节点的所有后代”)
如果目标是查 ID=42 的节点及其全部子节点(非递归方式),可以用多个子查询分别查第 1 层子节点、第 2 层子节点……再用 UNION ALL 合并:
SELECT id, name, 0 AS depth FROM categories WHERE id = 42 UNION ALL SELECT c1.id, c1.name, 1 FROM categories c1 WHERE c1.parent_id = 42 UNION ALL SELECT c2.id, c2.name, 2 FROM categories c2 JOIN categories c1 ON c2.parent_id = c1.id WHERE c1.parent_id = 42 UNION ALL SELECT c3.id, c3.name, 3 FROM categories c3 JOIN categories c2 ON c3.parent_id = c2.id JOIN categories c1 ON c2.parent_id = c1.id WHERE c1.parent_id = 42;
要点:
• 每个 SELECT 必须列数、类型一致,所以补了 depth 字段便于排序
• 第 2 层以后的查询必须通过 JOIN 追溯到根,不能只写 WHERE c2.parent_id IN (SELECT id FROM categories WHERE parent_id = 42) —— 那样会漏掉跨层匹配
• 性能随层数指数增长,3 层还行,5 层以上建议换方案
为什么不用 EXISTS + 多层子查询?它容易出错
有人尝试用 EXISTS 套子查询判断某节点是否为指定节点的祖先,例如:
SELECT * FROM categories c
WHERE EXISTS (
SELECT 1 FROM categories p1 WHERE p1.id = c.parent_id AND p1.id = 42
OR EXISTS (
SELECT 1 FROM categories p2 WHERE p2.id = p1.parent_id AND p2.id = 42
)
);这段 SQL 在 SQLite 中会报错:no such column: p1.parent_id —— 因为内层子查询无法引用外层子查询的别名 p1。SQLite 不支持相关子查询的多层嵌套引用。真要走 EXISTS 路线,只能拆成独立条件用 OR 并列,但可读性和维护性迅速崩坏。
真正实用的边界是:如果你的树深基本 ≤ 3,JOIN 展开或 UNION ALL 是可控的;一旦业务要求动态深度或树可能深达 10 层,就该考虑客户端侧递归或升级 SQLite 版本启用 WITH RECURSIVE —— 硬扛非递归写法只会让 SQL 变成不可测的状态机。

















