SQLServer递归CTE默认递归上限为100层,导致时间序列生成受限。解决方案包括:加大递归上限(OPTIONMAXRECURSION0),但生产需谨慎;推荐使用持久化Numbers表,无递归限制且性能稳定;亦可临时生成Tally表用于一次性查询。
在 SQL Server 中使用递归 CTE 生成时间序列时,最常见的障碍就是默认递归上限——逻辑明明没有问题,运行时却直接报错:

长期稳定更新的攒劲资源: >>>点此立即查看<<<
The maximum recursion 100 has been exhausted...
没错,SQL Server 默认只允许递归 100 层(MAXRECURSION = 100)。一旦时间跨度稍大或颗粒度较细,这一限制就会阻止查询执行。
下面提供三种可靠方案,按推荐优先级排列,覆盖从应急场景到生产环境的使用需求。
如果场景是临时脚本,或者时间跨度不算特别大,最简单的做法是在查询末尾添加 OPTION (MAXRECURSION N):
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 分钟跨数月),递归深度会很大,不建议长期依赖此方法。
放弃递归,改用连续整数生成时间点——这是性能最佳的做法,也是生产环境的首选方案。
-- 建表:包含从 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;
只需建一次,后续所有“时间补齐”场景都可以复用该表。
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)如果不想持久化 Numbers 表,借助系统表 + 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 中经典的对齐写法。event_time 建立索引/分区;先裁剪再聚合;Numbers 表持久化优于递归或临时递表。侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述