row_number() 不能直接生成连续日期,因为它必须基于真实表的行源才能编号;需用递归cte先构造数字序列,再转为日期,并设终止条件防止无限递归。

为什么不能直接用 ROW_NUMBER() 生成连续日期?
很多人试过写 SELECT DATE_ADD('2023-01-01', INTERVAL ROW_NUMBER() OVER() - 1 DAY),结果报错或只出一行——因为 ROW_NUMBER() 必须搭配 FROM 子句里的真实表,而你没提供任何基础行源。窗口函数不凭空造行,它只对已有结果集重排序、分组、编号。
用递归 CTE 构造基础数字序列(MySQL 8.0+ / PostgreSQL / SQL Server)
先得有一堆连续整数,才能转成日期。递归 CTE 是最可控的方式,比依赖系统表或笛卡尔积更干净。
关键点:
- 必须定义终止条件(比如
n ),否则无限循环 - 递归部分的
SELECT必须和锚点部分列数、类型一致 - MySQL 要显式设置
cte_max_recursion_depth,默认 1000,不够时会截断
WITH RECURSIVE nums AS ( SELECT 0 AS n UNION ALL SELECT n + 1 FROM nums WHERE n <h3>用 <code>GENERATE_SERIES()</code> 替代递归(PostgreSQL 10+ 推荐)</h3><p>PostgreSQL 用户别硬套递归——<code>GENERATE_SERIES()</code> 更直观、无栈深限制、性能更好。</p><p>注意参数顺序和类型:</p>
-
GENERATE_SERIES(start, stop, step),step 可正可负 - start/stop 必须是同类型,日期要转成
DATE或用INTERVAL - 直接生成日期:用
GENERATE_SERIES('2023-01-01'::DATE, '2023-12-31'::DATE, '1 day')
SELECT GENERATE_SERIES('2023-01-01'::DATE, '2023-12-31'::DATE, '1 day') AS dt;
窗口函数在这里真正起什么作用?
单纯生成连续日期,根本不需要窗口函数。但如果你的需求是「给每个用户补全其最近 30 天的日期记录」,那就要结合 PARTITION BY user_id 和 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 做累积逻辑——这时候窗口函数才不可替代。
常见误用:
- 试图用
LAG()或LEAD()生成日期序列 → 它们只能取已有行的偏移值,不能造新行 - 在递归 CTE 外层加
ROW_NUMBER() OVER (ORDER BY dt)再过滤 → 多此一举,CTE 里已有序号 - 忽略时区:用
NOW()生成范围时,不同 session 时区可能导致边界偏差
真正需要窗口函数介入的场景,往往发生在“已有业务数据 + 补全缺失日期”之后,而不是纯日期生成阶段。










