sql server需显式指定option (maxrecursion n),postgresql默认无递归深度限制但受work_mem约束;两者均须用union all连接锚点与递归成员,禁用union以防逻辑中断。

SQL Server 和 PostgreSQL 中用 Recursive CTE 生成数字序列的写法差异
Recursive CTE 是最通用的纯 SQL 数字序列生成方式,但不同数据库对 WITH RECURSIVE 的语法支持和默认限制差别很大。SQL Server 要求必须显式声明 OPTION (MAXRECURSION n),否则默认只跑 100 层;PostgreSQL 则默认无限制,但可能因 work_mem 不足而报错 out of memory for query result。
基础结构都包含两部分:锚点(anchor)和递归成员(recursive member),中间用 UNION ALL 连接。注意不能用 UNION —— 去重操作会中断递归逻辑,导致无限循环或报错。
示例(生成 1~10):
WITH numbers AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM numbers WHERE n <h3>MySQL 8.0+ 必须加递归终止条件,否则直接报错</h3><p>MySQL 对递归 CTE 更严格:如果递归分支没有明确的终止谓词(比如 <code>WHERE n ),执行时会立刻抛出错误 <code>ERROR 3636 (HY000): Recursive query aborted after 1001 iterations</code> —— 即使你本意只想要 10 行,它也会先尝试跑满默认上限再截断并报错。</code></p><p>安全写法必须同时满足三点:</p>
-
WHERE子句中使用「严格递增且可比较」的字段(如n ),不能是 <code>n != 100或n BETWEEN 1 AND 100 - 递归列(如
n)在锚点和递归部分类型一致,避免隐式转换导致终止失效 - 若需大范围序列(如 1~10000),建议显式设置
cte_max_recursion_depth系统变量,否则可能被默认的 1000 拦截
性能陷阱:别在 JOIN 中直接嵌套 Recursive CTE 生成序列
常见误操作是把数字序列 CTE 当作“函数”反复调用,比如写成 SELECT * FROM orders o JOIN (WITH ... SELECT ...) nums ON nums.n = o.id。这会导致 CTE 每次 JOIN 时重新执行,N 行数据就触发 N 次递归,复杂度爆炸。
正确做法是提前物化序列,或改用更高效替代方案:
- 小范围(VALUES 行构造器(PostgreSQL/SQL Server 支持):
VALUES (1),(2),(3),...,(100) - 中等范围(numbers(n INT PRIMARY KEY)),首次初始化后永久复用
- 超大范围或动态长度:考虑应用层生成,或用窗口函数配合现有有序主键模拟(如
ROW_NUMBER() OVER(ORDER BY id))
Oracle 用户注意:11g 及以后才支持 Recursive CTE,且语法略有不同
Oracle 不支持标准的 WITH RECURSIVE 写法,而是用 CONNECT BY。虽然功能类似,但语义差异明显:它基于树形遍历模型,必须指定 START WITH 和 CONNECT BY,且默认从根向下展开,不支持“反向递归”。
生成 1~5 的等效写法是:
SELECT LEVEL AS n FROM DUAL CONNECT BY LEVEL <p>这里 <code>LEVEL</code> 是伪列,不是变量;<code>DUAL</code> 是必需的单行表;若漏掉 <code>CONNECT BY</code> 条件,会返回无限多行(直到内存耗尽)。另外,<code>LEVEL</code> 从 1 开始,无法直接设为从 0 起始——得靠 <code>LEVEL - 1</code> 计算,且要注意负数场景下是否仍满足终止条件。</p><p>真正容易被忽略的是递归深度控制:Oracle 默认 <code>CONNECT_BY_MAX_LEVEL</code> 是 100,超过会报 <code>ORA-01436: CONNECT BY loop in user data</code>,哪怕你没环 —— 这其实是深度超限的误导性报错。</p>










