递归视图必须用WITH RECURSIVE且锚点与递归查询用UNION ALL连接;锚点须独立可执行、列定义一致,递归引用仅限FROM/JOIN中出现一次,须设深度限制防死循环。
PostgreSQL、SQL Server(自2017)、SQLite(3.8.3+)和标准 SQL 都要求显式声明 WITH RECURSIVE,MySQL 8.0+ 也支持但不强制写 RECURSIVE 关键字(写上更安全)。漏掉它会直接报错:ERROR: invalid reference to FROM-clause entry 或类似提示。
结构上,递归视图由两部分组成:非递归的“锚点查询”(anchor)和引用自身视图名的“递归查询”(recursive term),二者必须用 UNION ALL 连接——用 UNION 会去重,导致层级断裂;用逗号或 JOIN 则语法非法。
CREATE VIEW org_tree ASWITH RECURSIVE tree AS ( -- 锚点:顶层节点(parent_id IS NULL 或 = 0) SELECT id, name, parent_id, 1 AS level FROM employees WHERE parent_id IS NULL<p>UNION ALL</p><p>-- 递归:关联到上一层的子节点SELECT e.id, e.name, e.parent_id, t.level + 1FROM employees eINNER JOIN tree t ON e.parent_id = t.id)SELECT * FROM tree;
锚点查询不能依赖递归视图本身,否则数据库无法启动迭代。常见错误是把锚点写成 SELECT ... FROM tree WHERE ...,这会导致 ERROR: recursive reference in anchor part。
递归查询中,视图名(如 tree)只能出现在 FROM 或 JOIN 子句里,且仅出现一次;不能在 WHERE 中嵌套子查询引用自身,也不能多次 JOIN tree,否则多数数据库会拒绝(PostgreSQL 报 ERROR: relation "tree" does not exist,实为解析阶段拦截)。
另外,锚点结果集的列名、类型、顺序必须和递归查询完全一致,否则类型不匹配报错,比如锚点返回 TEXT,递归部分返回 VARCHAR(50),某些引擎会隐式转换失败。
没有终止条件的递归(例如父子 ID 写错形成环)会让查询无限跑下去,直到触发数据库默认限制。PostgreSQL 默认 max_recursion_depth = 100,SQL Server 是 100 层(可设 OPTION (MAXRECURSION n)),SQLite 是 1000(通过 PRAGMA recursive_triggers 不影响此值)。
建议主动加防护:
t.level 类似条件,避免意外深链拖垮性能
parent_id 字段建索引(CREATE INDEX idx_emp_parent ON employees(parent_id)),否则每次递归都要全表扫描WITH RECURSIVE + LIMIT 测试深度:SELECT * FROM org_tree ORDER BY level LIMIT 20
根节点怎么标?有些系统用 parent_id = 0,有些用 parent_id IS NULL,还有些用自引用(parent_id = id)。递归视图不会自动识别“根”,必须在锚点里明确写死逻辑。一旦写反,整个树就缺层。
更隐蔽的问题是 NULL 处理:WHERE parent_id = t.id 在 t.id 为 NULL 时永远不成立(因为 NULL = NULL 是 unknown),所以锚点若选 parent_id IS NULL,递归部分就不能漏掉对 NULL 的防御,除非你确认数据里没有 NULL 父节点。
另外,SQL Server 要求递归列不能有聚合、窗口函数或外部引用;PostgreSQL 允许在递归分支中用 ORDER BY,但只影响该分支输出顺序,不影响最终 UNION ALL 结果的行序——这点常被误认为能控制树遍历顺序。
真正控制遍历顺序得靠外层查询加 ORDER BY,比如按路径字符串排序:SELECT * FROM org_tree ORDER BY lpad(level::text, 4, '0') || id,但这个路径字段得在递归过程中自己拼出来,不是白给的。