GROUP BY 后日期“断档”是因为SQL只返回有数据的分组;需用递归CTE生成完整日期序列再LEFT JOIN补全,起止日期须为确定值且递归深度足够。

为什么 GROUP BY 后的日期会“断档”?
直接 GROUP BY DATE(created_at) 或 GROUP BY YEARWEEK(created_at) 只会返回有数据的日期,缺失日期根本不会出现在结果里——这不是 SQL 的 bug,而是它严格按实际行聚合的本意。想补全,得主动构造完整日期序列再 LEFT JOIN。
用递归 CTE 构造连续日期(MySQL 8.0+ / PostgreSQL)
别硬写几十个 UNION ALL,用递归 CTE 动态生成区间。注意起止日期必须是确定值(不能是子查询结果),且递归深度要够:
WITH RECURSIVE date_series AS ( SELECT '2024-01-01'::DATE AS dt UNION ALL SELECT dt + INTERVAL '1 day' FROM date_series WHERE dt < '2024-01-31'::DATE ) SELECT d.dt, COALESCE(t.cnt, 0) AS cnt FROM date_series d LEFT JOIN ( SELECT DATE(created_at) AS dt, COUNT(*) AS cnt FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31' GROUP BY DATE(created_at) ) t ON d.dt = t.dt;
-
INTERVAL '1 day'在 PostgreSQL 中生效;MySQL 用DATE_ADD(dt, INTERVAL 1 DAY) - 递归默认上限 1000 层,若跨度大需调
cte_max_recursion_depth(MySQL)或max_recursive_iterations(PostgreSQL) - WHERE 条件必须放在子查询里过滤原始表,否则 LEFT JOIN 会把无数据日期也带进聚合范围
用数字表 + DATE_ADD 补周/月粒度(兼容老版本 MySQL)
没有递归 CTE?建一个只存 0~99 的 numbers 表,靠 DATE_ADD 算偏移量。关键在步长和起始点对齐:
SELECT
DATE_ADD('2024-01-01', INTERVAL n.n DAY) AS dt,
COALESCE(t.cnt, 0) AS cnt
FROM numbers n
LEFT JOIN (
SELECT DATE(created_at) AS dt, COUNT(*) AS cnt
FROM orders
WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01'
GROUP BY DATE(created_at)
) t ON DATE_ADD('2024-01-01', INTERVAL n.n DAY) = t.dt
WHERE n.n BETWEEN 0 AND 30;
-
numbers.n必须从 0 开始,否则第一天就错位 - 日期范围用
BETWEEN容易漏掉边界时间,推荐用>= start AND 避免时区或秒级截断问题 - 补“周统计”时,先用
YEARWEEK(dt, 1)对齐周一为周首,再用WEEKDAY(dt)调整起始日
NULL 值和时区陷阱必须手动处理
补全后 COALESCE(t.cnt, 0) 只解决计数为 0 的情况,但原始数据里的 created_at IS NULL 会被 DATE(created_at) 变成 NULL,进而让 LEFT JOIN 失效——这些 NULL 不会匹配到任何构造的日期,直接消失。
- 补全前先用
WHERE created_at IS NOT NULL过滤,或用COALESCE(DATE(created_at), '1970-01-01')统一兜底(需同步在 date_series 里加该日期) - 数据库时区和应用时区不一致时,
DATE(created_at)可能跨天。务必确认created_at存的是 UTC 还是本地时间,必要时用CONVERT_TZ或AT TIME ZONE标准化 - 如果统计的是“用户注册日期”,而用户可能来自不同时区,单纯按服务器时间分组会失真——这时得依赖前端传入的时区标识,或用
TIMESTAMP WITH TIME ZONE类型存数据
补日期不是拼函数的事,核心是搞清你的“日期”到底指什么:是数据入库时间?业务发生时间?还是用户本地时间?选错基准,补得再全也是错的。

















