postgresql 和 mysql 8 均支持 with recursive,但 mysql 必须显式声明 recursive 关键字,而 postgresql 可省略;两者锚点查询均禁用 order by/limit/group by,递归终止依赖无新行生成而非 where 截断,且列数、类型、顺序必须严格一致。

PostgreSQL 和 MySQL 8 都支持 WITH RECURSIVE,但写法、限制和默认行为有实质差异,不能直接复用同一段 SQL。
MySQL 8 的 WITH RECURSIVE 必须显式加 RECURSIVE 关键字
MySQL 严格要求语法中必须出现 RECURSIVE,漏掉就报错:ERROR 1248 (42000): Every derived table must have its own alias 或更隐晦的解析失败。PostgreSQL 则允许省略(WITH 自动推断递归意图),但加上更清晰。
- ✅ 正确(MySQL):
WITH RECURSIVE cte AS (...) - ❌ 错误(MySQL):
WITH cte AS (...)—— 即使内容含UNION ALL也不行 - ✅ 正确(PostgreSQL):
WITH RECURSIVE cte AS (...)或WITH cte AS (...)
实际迁移时,别图省事删掉 RECURSIVE;加了在两边都兼容,是最小改动方案。
锚点查询(Anchor Member)里不能用 ORDER BY、LIMIT、GROUP BY
这是两个数据库共有的硬性限制:初始查询部分(UNION ALL 前那块)若带 ORDER BY,MySQL 直接报错 ERROR 3577 (HY000): Recursive common table expression anchor member cannot have ORDER BY;PostgreSQL 虽不报错,但会忽略 ORDER BY,导致递归顺序不可控(比如想先展开某子树却没效果)。
- ❌ 锚点里写
SELECT ... FROM t WHERE id = 1 ORDER BY sort_order—— MySQL 拒绝执行,PostgreSQL 丢弃排序 - ✅ 把排序移到最终
SELECT:例如SELECT * FROM cte ORDER BY level, name - ⚠️ 注意:递归层级深度由数据本身决定,不是靠锚点排序控制的
递归终止靠“无新行产生”,不是靠 WHERE 条件主动截断
很多人误以为在递归分支里加 WHERE level 就能安全限深——其实它只过滤当轮输出,不影响下一轮是否继续执行。一旦某轮结果为空,递归才真正停止。如果连接条件写错(比如 <code>ON d.parent_id = r.id 写成 ON d.id = r.parent_id),可能无限循环或卡死。
- ⛔ 危险写法:
SELECT ... FROM dept d JOIN cte r ON d.parent_id = r.id WHERE r.level —— <code>r.level是上一轮的,不阻止本轮生成 level=5 的行 - ✅ 安全写法:在递归分支中加入层级控制字段,并在
SELECT中用CASE或NULLIF截断,例如:SELECT ..., r.level + 1 AS level FROM ... WHERE r.level - ? PostgreSQL 还可设
max_recursion_depth(需 superuser 权限),MySQL 无等效配置,只能靠逻辑控制
字段对齐和别名必须严格一致
UNION ALL 左右两边的列数、类型、顺序必须完全匹配,否则报错。常见坑是锚点查了 id, name, parent_id,递归查了 id, name, parent_id, level —— 多了一列就失败。
- ❌ 锚点:
SELECT id, name, parent_id FROM t WHERE id = 1
递归:SELECT id, name, parent_id, level+1 FROM ... - ✅ 统一写法(锚点补
0 AS level):SELECT id, name, parent_id, 0 AS level FROM t WHERE id = 1
递归:SELECT d.id, d.name, d.parent_id, r.level + 1 FROM t d JOIN cte r ON d.parent_id = r.id - ? MySQL 对隐式类型转换更敏感(如
VARCHAR和CHAR混用),PostgreSQL 更宽松但仍有精度截断风险
最易被忽略的是递归方向与连接条件的对应关系:向下查子节点用 child.parent_id = parent.id,向上查父节点必须反过来写成 parent.id = child.parent_id —— 看似对称,但写反会导致结果为空或逻辑错乱,且错误不报异常,只返回空集。











