<p>MySQL 8.0+ 使用 WITH RECURSIVE 生成连续数字需包含锚点和递归成员,如 WITH RECURSIVE seq(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM seq WHERE n<100) SELECT * FROM seq,并注意终止条件与 cte_max_recursion_depth 限制。</p>

MySQL 8.0+ 怎么用 WITH RECURSIVE 生成连续数字
只有 MySQL 8.0 及以上版本才支持递归 CTE,低版本(如 5.7)直接报错 ERROR 1064。递归必须包含两个部分:锚点(anchor)和递归成员(recursive member),且递归查询不能有聚合、GROUP BY、ORDER BY(除非在最外层)等限制。
基本结构如下:
WITH RECURSIVE seq(n) AS ( SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 100 ) SELECT * FROM seq;
这会生成 1 到 100 的整数。注意:n 必须有明确的终止条件(WHERE n < 100),否则会无限递归直到达到 cte_max_recursion_depth 限制(默认 1000),然后报错 ERROR 3636。
为什么 SELECT n FROM seq 有时返回空或不完整
常见原因是递归深度不够或初始值/终止条件逻辑反了。比如想生成 10–20,写成 SELECT 10 UNION ALL SELECT n+1 FROM seq WHERE n > 20 就永远进不了递归分支——因为初始 n=10 不满足 n > 20,结果只返回一行 10。
- 终止条件必须让递归能“走起来”,且最终为假:用
<=或<配合递增,或>=/>配合递减 - 检查当前会话的递归上限:
SELECT @@cte_max_recursion_depth;,需要时可临时调高:SET SESSION cte_max_recursion_depth = 5000; - 如果生成大范围(如 1–100000),CTE 会构建完整中间结果集,内存和速度不如临时表或数字辅助表
生成带偏移、步长或字符前缀的序列怎么写
CTE 的递归列可以参与任意标量计算,不只是 n + 1。但所有计算必须是确定性的,不能引用外部表或使用随机函数。
例如生成偶数 2, 4, 6…20:
WITH RECURSIVE seq(n) AS ( SELECT 2 UNION ALL SELECT n + 2 FROM seq WHERE n < 20 ) SELECT * FROM seq;
生成带前缀的编号(如 'ID-001', 'ID-002'):
WITH RECURSIVE seq(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM seq WHERE n < 5
)
SELECT CONCAT('ID-', LPAD(n, 3, '0')) AS id FROM seq;注意:LPAD 和 CONCAT 是安全的,但别在递归分支里用 NOW()、RAND() 或子查询,MySQL 会拒绝执行。
替代方案:没有 CTE 时怎么凑出数字序列
MySQL 5.7 或更老版本无法用 WITH RECURSIVE,常见绕过方式是利用系统表(如 information_schema.columns)做笛卡尔积“榨取”行数,但不可靠——表数量可能不足,且依赖元数据权限。
更稳妥的做法是建一个最小化数字表:
CREATE TABLE nums (n INT PRIMARY KEY); INSERT INTO nums VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
再通过交叉连接生成更大范围:
SELECT a.n * 10 + b.n + 1 AS num FROM nums a CROSS JOIN nums b WHERE a.n * 10 + b.n + 1 <= 100;
这种方案性能稳定、兼容性好,但需要提前建表;而 CTE 方案零依赖,适合一次性、中小规模(
真正要注意的是:别在应用层循环拼 SQL 插入数字,也别用存储过程 while 循环——这两者在大数据量下慢且难维护。CTE 或预置数字表,才是干净解法。


















