应使用 UNION ALL 拼接两个时间段聚合结果并显式添加 period 字段标识,避免用 UNION 去重导致数据错乱,且不可遗漏时间字段标记。

用 UNION ALL 拼接两个时间段的聚合结果
直接用 WHERE 分别查两个时间段再对比,最直观的办法就是把它们拼成一张表——但不能用 UNION(会去重),必须用 UNION ALL,否则相同分组值可能被合并掉,导致数据错乱。
常见错误是漏写时间字段标识,导致无法区分哪行属于哪个时间段。建议显式加一列标记,比如 period:
SELECT '2024-01' AS period, product_id, COUNT(*) AS cnt FROM orders WHERE order_time >= '2024-01-01' AND order_time < '2024-02-01' GROUP BY product_id UNION ALL SELECT '2024-02' AS period, product_id, COUNT(*) AS cnt FROM orders WHERE order_time >= '2024-02-01' AND order_time < '2024-03-01' GROUP BY product_id;
- 时间范围推荐用左闭右开(
>=+<),避免跨月边界遗漏或重复 - 所有 SELECT 的列数、类型、顺序必须严格一致,否则
UNION ALL报错 - 如果某个月份没有某
product_id的记录,它就不会出现在结果里——这是正常行为,不是 bug
用 LEFT JOIN 对齐两个时间段的分组结果
想看“同一个 product_id 在两期分别多少单、差多少”,就得把两期结果按分组键对齐。这时 LEFT JOIN(或 FULL OUTER JOIN)比 UNION ALL 更合适。
注意:MySQL 不支持 FULL OUTER JOIN,得用 LEFT JOIN + RIGHT JOIN 模拟,或者用 UNION 合并两方向的 LEFT JOIN。
WITH t1 AS ( SELECT product_id, COUNT(*) AS cnt_01 FROM orders WHERE order_time >= '2024-01-01' AND order_time < '2024-02-01' GROUP BY product_id ), t2 AS ( SELECT product_id, COUNT(*) AS cnt_02 FROM orders WHERE order_time >= '2024-02-01' AND order_time < '2024-03-01' GROUP BY product_id ) SELECT COALESCE(t1.product_id, t2.product_id) AS product_id, COALESCE(t1.cnt_01, 0) AS cnt_01, COALESCE(t2.cnt_02, 0) AS cnt_02, COALESCE(t2.cnt_02, 0) - COALESCE(t1.cnt_01, 0) AS diff FROM t1 FULL OUTER JOIN t2 ON t1.product_id = t2.product_id;
-
COALESCE是关键,用来把NULL补成 0,否则差值会变成NULL - PostgreSQL / SQL Server 支持
FULL OUTER JOIN;MySQL 用户得改写成两个LEFT JOIN并UNION - 如果分组字段不止一个(比如
(product_id, region)),ON条件和COALESCE都要覆盖全部字段
WHERE 时间条件写错导致结果为空或重复
最常踩的坑不是语法,而是时间表达式逻辑出错。比如用 BETWEEN 包含秒级时间,或没考虑时区、日期函数返回值类型。
-
BETWEEN '2024-01-01' AND '2024-01-31'实际等价于>= '2024-01-01 00:00:00' AND <= '2024-01-31 00:00:00',会漏掉 1 月 31 日全天数据 -
DATE(order_time) = '2024-01-01'会强制索引失效,尽量用范围查询替代 - PostgreSQL 中
'2024-01'::date会被解析为'2024-01-01',不是整月,别这么写 - 确认
order_time是TIMESTAMP还是DATE类型,类型不匹配时隐式转换可能出人意料
聚合后数据量大时性能卡在哪
如果两个时间段各自聚合后有几万行,FULL OUTER JOIN 或 UNION ALL 本身不慢,慢在前面的扫描和分组。优化重点不在“怎么对比”,而在“怎么快算出两期聚合”。
- 确保
order_time和分组字段(如product_id)上有联合索引,例如INDEX (order_time, product_id) - 避免在
WHERE里对时间字段用函数,比如YEAR(order_time) = 2024会让索引失效 - 如果只是看同比/环比,且表很大,考虑提前物化聚合结果到汇总表,而不是每次实时算
- 某些场景下,用窗口函数配合
GROUPING SETS能一次扫表完成多周期聚合,但可读性下降,需权衡
真正麻烦的不是写法,是分组键在两个时间段分布差异太大——比如某产品上月卖了 10 万单,本月只卖了 3 单,但你要对每个 product_id 都做差值计算,这种稀疏对比容易被忽略数据倾斜问题。

















