postgresql和sqlite允许在create view中直接使用with recursive,但必须显式写出最外层select的所有列名,且锚点与递归部分列数、类型、顺序须严格一致,锚点禁止引用自身。

递归视图必须显式声明列名
PostgreSQL 和 SQLite 允许在 CREATE VIEW 中直接使用 WITH RECURSIVE,但二者都强制要求最终 SELECT 必须显式写出所有列名,不能依赖推导。省略列别名会导致创建失败或后续查询报错 column "level" does not exist。
-
WITH RECURSIVE tree AS (...)内部的锚点和递归部分可以省略别名,但最外层SELECT必须写全:SELECT id, name, parent_id, level FROM tree - 如果锚点中写了
0 AS level,递归部分也必须是t.level + 1 AS level,否则 SQLite 会因列类型不一致拒绝建视图 - MySQL(截至 8.0.33)完全不支持该语法,执行
CREATE VIEW ... WITH RECURSIVE会直接报错ERROR 1349 (HY000): View's SELECT contains a recursive WITH clause
锚点查询不能引用递归视图自身
这是启动失败的高频原因。数据库解析时会先验证锚点是否可独立执行,若里面出现 FROM tree 或 WHERE id IN (SELECT ... FROM tree),就会报错 recursive reference in anchor part。
- 锚点只能查原始表,比如
SELECT ... FROM employees WHERE parent_id IS NULL - 递归部分才允许
JOIN tree,且仅限一次,不能写成JOIN tree t1 JOIN tree t2,否则 PostgreSQL 报relation "tree" does not exist - 锚点与递归部分的列数、顺序、类型必须严格一致:比如锚点返回
INTEGER,递归部分就不能用CAST(... AS TEXT)拼接路径,否则类型不匹配
必须控制递归深度并防环
没有终止条件的递归可能跑满数据库默认限制(PostgreSQL 是 100 层,SQLite 是 1000),甚至卡死连接。真实数据里父子 ID 错配形成环很常见,不能只靠默认值兜底。
- 在递归部分加
WHERE t.level 这类硬限制,比依赖 <code>max_recursion_depth更可控 - 对可能含环的数据,建议加路径防重字段,例如用
ARRAY[t.id] AS path初始,递归时ARRAY_APPEND(t.path, e.id),再加WHERE NOT e.id = ANY(t.path) - 基础表上
parent_id字段必须有索引,否则每次迭代都是全表扫描,10 层递归可能耗时秒级变分钟级
MySQL 用户得绕开视图直接写内联 CTE
MySQL 不支持递归视图,但支持在普通查询中用 WITH RECURSIVE。所谓“伪视图”,就是建一个普通视图,内容是带完整递归逻辑的 SELECT,但它不是真正的视图——每次调用都会重执行整个递归,无法被优化器复用。
- 可行写法:
CREATE VIEW employee_tree AS SELECT * FROM (WITH RECURSIVE ... ) AS t,注意括号和别名缺一不可 - 更稳的方案是预生成扁平表,比如用定时任务每天跑一次递归结果存到
employee_paths,视图只查这张表 - 若业务要求实时性,又必须用 MySQL,只能把递归逻辑提到应用层,用循环 + 多次查询代替,虽然代码啰嗦但可控
实际写递归视图时,最容易被忽略的是列类型一致性——比如 manager_id 是 VARCHAR 而 id 是 INT,JOIN 条件会静默失败,结果为空却无报错。这类问题往往要到上线后查不出数据才暴露。











