MsSql

关注公众号 jb51net

关闭
首页 > 数据库 > MsSql > sqlserver递归上限

SQLServer 默认递归上限问题及三种可靠解决方案

作者:衡水世耀科技有限公司

本文给大家分享SQLServer 默认递归上限问题及三种可靠解决方案,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

我在 SQL Server 里用递归 CTE 生成时间序列时,触发了默认递归上限:

The maximum recursion 100 has been exhausted...

这正是 SQL Server 的典型报错(默认 MAXRECURSION = 100)。

下面给你三种可靠解决方案,按推荐顺序排列:

✅ 方案 A:加大递归上限或无限制(最快改法)

在你的查询最后加上 OPTION (MAXRECURSION N)

-- 你的原始递归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;

优点

✅ 方案 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) 调整到能覆盖你的区间即可。

常见细节与坑

到此这篇关于SQLServer 默认递归上限问题的文章就介绍到这了,更多相关sqlserver递归上限内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文