GROUP BY 日期会漏掉无数据的日期,因它只对实际存在的记录分组;需用 LEFT JOIN 配合生成的日期序列补全,再用 COALESCE 填充空值。

为什么 GROUP BY 日期后会漏掉空天
因为 SQL 的 GROUP BY 只对实际存在的记录分组,数据库不会凭空生成某天没数据的行。比如查 7 天销量,但其中两天没订单,结果就只有 5 行——这不是 bug,是预期行为。
用 LEFT JOIN 搭配生成的日期序列补全
核心思路:先造一个完整日期列表(如用递归 CTE 或日历表),再和你的业务表按日期 LEFT JOIN。只要确保左表是“全量日期”,右表是“业务数据”,空天自然显示 NULL,后续用 COALESCE 填 0 即可。
PostgreSQL 示例(生成最近 7 天):
SELECT d.dt, COALESCE(SUM(t.amount), 0) AS total FROM ( SELECT CURRENT_DATE - INTERVAL '1 day' * n AS dt FROM generate_series(0, 6) AS n ) d LEFT JOIN orders t ON DATE(t.created_at) = d.dt GROUP BY d.dt ORDER BY d.dt;
MySQL 8.0+ 可用 WITH RECURSIVE;老版本建议建一张小日历表(calendar),查起来更快也更稳定。
注意 DATE() 函数和时区/索引的隐性开销
-
DATE(created_at)会让索引失效,如果数据量大,优先考虑把查询条件改写为范围:created_at >= '2024-05-01' AND created_at - 时区不一致会导致“明明有数据却补空”——确认
created_at存的是 UTC 还是本地时间,生成的日期序列必须对齐同一时区 - 用字符串拼日期(如
'2024-05-' || LPAD(n::text, 2, '0'))易出错,不如直接用日期运算
聚合前补全 vs 聚合后补全:选错顺序会丢精度
务必在 GROUP BY 之前完成补全。如果先 GROUP BY 再试图用 UNION ALL 插入空天,可能因分组键不一致(比如还带了用户 ID)导致逻辑混乱。
错误示范(别这么干):
SELECT date, SUM(sales) FROM sales GROUP BY date UNION ALL SELECT '2024-05-03', 0 WHERE NOT EXISTS (SELECT 1 FROM sales WHERE date = '2024-05-03');
这种写法无法覆盖多维分组场景,且 NOT EXISTS 在大数据下性能差。坚持“先扩日期、再关联、最后聚合”的链路最可控。
真正麻烦的不是怎么写,而是补全之后要不要保留原始粒度(比如按小时统计却要补全天)、以及如何让下游系统识别哪些是真实数据、哪些是填充值——这些得靠字段标记或元数据约定,SQL 本身不负责解释语义。

















