SQLServer 默认递归上限问题及三种可靠解决方案
我在 SQL Server 里用递归 CTE 生成时间序列时,触发了默认递归上限:
The maximum recursion 100 has been exhausted...
这正是 SQL Server 的典型报错(默认 MAXRECURSION = 100)。
下面给你三种可靠解决方案,按推荐顺序排列:
✅ 方案 A:加大递归上限或无限制(最快改法)
在你的查询最后加上 OPTION (MAXRECURSION N):
- 指定一个足够大的 N(如 100000),或
- 用
0表示不限制(要确保查询本身不会死循环)。
-- 你的原始递归CTE查询
WITH ts AS (
SELECT CAST('2025-10-01T00:00:00' AS datetime2) AS bucket
UNION ALL
SELECT DATEADD(minute, 10, bucket)
FROM ts
WHERE bucket < CAST('2025-10-02T00:00:00' AS datetime2)
)
SELECT *
FROM ts
OPTION (MAXRECURSION 0); -- 0 = 不限制(生产慎用,需确保 WHERE 正确)适合临时脚本或时间跨度不算特别大的场景。
如果你的时间间隔很小、跨度很长(例如 1 分钟跨数月),递归会很深,不建议长期依赖。
✅ 方案 B:改用Tally/Numbers 表(生产推荐,稳定高效)
不用递归,直接用连续整数生成时间点,性能最佳。
1) 一次性建一个 Numbers 表(建议持久化)
-- 建表:包含从 0 开始的连续整数 CREATE TABLE dbo.Numbers (n int NOT NULL PRIMARY KEY); -- 生成足够多的行(示例:生成 1,000,000 行) ;WITH E1(N) AS (SELECT 1 UNION ALL SELECT 1), E2 AS (SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 4 E4 AS (SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 16 E8 AS (SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 256 E16 AS (SELECT 1 FROM E8 a CROSS JOIN E8 b), -- 65,536 E32 AS (SELECT 1 FROM E16 a CROSS JOIN E16 b) -- ~4B(谨慎) INSERT INTO dbo.Numbers(n) SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 FROM E32;
只需建一次,后续所有“时间补齐”都复用它。
2) 用 Numbers 表生成时间序列并补齐
DECLARE @start datetime2 = '2025-10-01T00:00:00';
DECLARE @end datetime2 = '2025-10-02T00:00:00';
DECLARE @step_min int = 5; -- 间隔:5分钟
WITH ts AS (
SELECT DATEADD(minute, n * @step_min, @start) AS bucket
FROM dbo.Numbers
WHERE DATEADD(minute, n * @step_min, @start) < @end -- 通常用 [start, end)
),
agg AS (
-- 将事实表事件对齐到 5 分钟桶
SELECT DATEADD(minute, DATEDIFF(minute, 0, event_time) / @step_min * @step_min, 0) AS bucket_5m,
COUNT(*) AS c
FROM dbo.EventLog
WHERE event_time >= @start AND event_time < @end
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, event_time) / @step_min * @step_min, 0)
)
SELECT ts.bucket,
ISNULL(agg.c, 0) AS event_count
FROM ts
LEFT JOIN agg ON ts.bucket = agg.bucket_5m
ORDER BY ts.bucket;优点
- 没有递归深度限制
- 可预估生成行数:
行数 ≈ CEILING(DATEDIFF(minute, @start, @end) / @step_min) - 最适合生产环境与大跨度时间序列
✅ 方案 C:不建持久表,临时生成 Tally(适合脚本/一次性查询)
用系统表 + ROW_NUMBER() 临时造数:
DECLARE @start datetime2 = '2025-10-01T00:00:00';
DECLARE @end datetime2 = '2025-10-02T00:00:00';
DECLARE @step_min int = 15;
;WITH N AS (
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS n
FROM sys.all_objects a CROSS JOIN sys.all_objects b
),
ts AS (
SELECT DATEADD(minute, n * @step_min, @start) AS bucket
FROM N
WHERE DATEADD(minute, n * @step_min, @start) < @end
),
agg AS (
SELECT DATEADD(minute, DATEDIFF(minute, 0, event_time) / @step_min * @step_min, 0) AS bucket_15m,
COUNT(*) AS c
FROM dbo.EventLog
WHERE event_time >= @start AND event_time < @end
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, event_time) / @step_min * @step_min, 0)
)
SELECT ts.bucket,
ISNULL(agg.c, 0) AS event_count
FROM ts
LEFT JOIN agg ON ts.bucket = agg.bucket_15m
ORDER BY ts.bucket;把 TOP (1000000) 调整到能覆盖你的区间即可。
常见细节与坑
- 区间边界:推荐用
[start, end)(即< @end),避免终点重复。 - 分箱对齐:
DATEADD(minute, DATEDIFF(minute, 0, event_time) / step * step, 0)是 SQL Server 经典对齐写法。 - 时区:全部用同一时区(UTC 或本地)进行聚合;需要展示时再做转换。
- 性能:给
event_time建索引/分区;先裁剪再聚合;Numbers 表持久化优于临时递表或递归。 - 极大跨度:优先用“按天序列 + 维表/应用层展开”,不要用深递归。
到此这篇关于SQLServer 默认递归上限问题的文章就介绍到这了,更多相关sqlserver递归上限内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
MS SQL Server STUFF实现统计记录行转为列显示
SQL语句行转列函数是一种用于将多个行数据转换为一列的函数,本文主要介绍了MS SQL Server STUFF实现统计记录行转为列显示,具有一定的参考价值,感兴趣的可以了解一下2024-04-04
SqlServer Mysql数据库修改自增列的值及相应问题的解决方案
这篇文章主要介绍了SqlServer Mysql数据库修改自增列的值及相应问题的解决方案的相关资料,需要的朋友可以参考下2016-01-01
Spark临时表tempView的注册/使用/注销/注意事项(推荐)
transformation是根据原有RDD创建一个新的RDD,而action则把RDD操作后的结果返回给driver,这篇文章主要介绍了Spark临时表tempView的注册/使用/注销/注意事项的相关资料,需要的朋友可以参考下2022-10-10
SQL Server2000在win10上安装的方法图文教程
Win10本身是一个兼容性较好的操作系统,有很多人在咨询如何在Windows 10上安装SQL Server 2000数据库,都没有成功过,这篇文章主要给大家介绍了关于SQL Server2000在win10上安装的方法,需要的朋友可以参考下2024-05-05


最新评论