窗口函数无法真正递归,因其不维护状态、不迭代;所谓“替代”仅适用于层级有限、数据已按父子邻接排序的扁平化树结构,依赖lag()或max() over等技巧近似推导深度或路径,但顺序错则结果崩。

为什么不能直接用 WITH RECURSIVE 就想当然换窗口函数?
窗口函数本身不维护状态、不迭代,无法真正“递归”。所谓“代替”,其实是针对特定层级计算场景(比如树形结构的深度、路径、祖先列表),用非递归方式近似或等价实现。前提是:数据已扁平化存储(如每个节点带 parent_id),且层级深度有限、可预估。
常见误判是看到“逐层累加”就以为能用 SUM() OVER (ORDER BY ...) 一招解决——但排序依据若不是拓扑序,结果必然错乱。
LAG() 和 MAX() OVER (ROWS BETWEEN ...) 怎么配合算出节点深度?
当树结构按广度优先或深度优先顺序物理存储(例如导出时已排序),可用 LAG() 捕捉上一行的父级信息,再结合条件判断推导当前行深度。更稳妥的是用 MAX(depth) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) 动态继承父层最大深度 + 1,但要求数据严格按父子邻接顺序排列。
- 必须先用
ORDER BY parent_id, id或类似逻辑保证父子行相邻 -
LAG(parent_id)返回 NULL 时通常为根节点,深度设为 1 - 若某节点父节点出现在后几行(排序没对齐),
LAG()会取错值,深度全崩 -
MAX() OVER方式比LAG()更鲁棒,但依然依赖排序质量
用 STRING_AGG() + 窗口累计拼接完整路径可行吗?
可以,但仅限 PostgreSQL / SQL Server 2017+ / DuckDB 等支持 STRING_AGG() OVER (... ORDER BY ... ROWS UNBOUNDED PRECEDING) 的引擎。MySQL 目前(8.0)不支持在窗口中用 STRING_AGG()(即 GROUP_CONCAT() OVER 无效)。
关键点不是函数有没有,而是“路径拼接”本质依赖确定的遍历顺序。如果表里没有 path_order 或 lft/rgt 字段,强行用 id 排序拼出来的路径大概率跳辈或断裂。
- PostgreSQL 示例:
STRING_AGG(name, ' > ' ORDER BY level) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)—— 但level得先算出来 - 拼接前必须确保每一行的祖先都已出现在 preceding 行中,否则漏段
- 一旦中间有并发插入打乱物理顺序,窗口累计结果不可靠
真正该放弃窗口函数的信号有哪些?
当出现以下任一情况,硬套窗口函数只会增加调试成本,不如老实用 WITH RECURSIVE:
- 需要查任意深度的祖父(不止上一级)、或跨层级聚合(如“所有子节点销售额总和”)
- 树结构动态变更频繁,无法维护全局排序字段
- 数据库不支持
RECURSIVE CTE(如旧版 MySQL),但又没能力加lft/rgt列——这时窗口函数也救不了,得换存储模型 - 层级超过 50 层,窗口函数的
ROWS BETWEEN范围难控制,性能陡降
窗口函数替代递归的本质,是把“算法逻辑”下推到数据顺序里;顺序一旦不确定,整个推导就失去根基。










