必须显式添加period字段标识时间段,用UNION ALL拼接两期分组聚合结果;若需并排对比同一分组的增减,则应使用LEFT JOIN或CTE+FULL OUTER JOIN对齐。

用 UNION ALL 拼接两期聚合结果时必须显式加 period 字段
直接把两个时间段的 COUNT(*) 结果拼成一张表看对比,最常用也最容易出错。关键不是“能不能拼”,而是“拼完能不能分清哪行属于哪期”。UNION 会去重,一旦两期某 product_id 的 cnt 相同,就可能被合并掉——这不是你想要的;必须用 UNION ALL。
更隐蔽的坑是漏写时间标识列。比如只写 SELECT product_id, COUNT(*) ...,拼完根本不知道哪行是 1 月、哪行是 2 月。必须每段 SELECT 都带一个常量字段,例如:
SELECT '2024-01' AS period, product_id, COUNT(*) AS cnt FROM orders WHERE order_time >= '2024-01-01' AND order_time = '2024-02-01' AND order_time
- 所有
SELECT的列数、顺序、类型必须严格一致,否则UNION ALL报错 - 时间范围推荐左闭右开(
>=+),避免跨月重复或遗漏 - 如果某
product_id在某个月没订单,它就不会出现在该期结果里——这是正常行为,不是数据丢失
想算同比/环比差值就得用 LEFT JOIN 对齐分组键
拼成一列看不出来“同一个 product_id 在两期各多少单、差多少”,这时候 UNION ALL 就不够用了。必须把两期结果分别聚合成临时表,再按 product_id 对齐——也就是用 LEFT JOIN 或 FULL OUTER JOIN。
MySQL 不支持 FULL OUTER JOIN,所以常见做法是用 CTE 配合 LEFT JOIN + RIGHT JOIN 模拟,或者更稳妥地:以其中一期为主表做 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' AND order_time
- 如果用 MySQL,把最后的
FULL OUTER JOIN换成LEFT JOIN t2,再UNION ALL一个反向LEFT JOIN,并用WHERE t1.product_id IS NULL补全 t2 独有项 -
COALESCE(..., 0)是为了把NULL转成 0,方便算差值;不加的话,NULL - 5还是NULL - 别在
JOIN后再GROUP BY——聚合必须在 JOIN 前完成,否则性能爆炸且逻辑混乱
JOIN 类型选错会导致部分分组数据消失
用 INNER JOIN 关联两期聚合结果,只会保留两期都存在的 product_id;用 LEFT JOIN(以 t1 为左表)能保留 t1 全部,但 t2 独有的会被丢掉;只有 FULL OUTER JOIN 或等效写法才能看到所有变化。
- 如果你关心“新增品类”,就得让 t2 独有项也出现,不能只依赖
LEFT JOIN - 有些同学试图用
OR条件把两期塞进一个WHERE,再GROUP BY product_id, period,这看似简洁,但无法区分“同一product_id在两期都存在”和“只存在一期”,更没法算差值 - 别在
ON条件里写过滤逻辑(比如ON t1.product_id = t2.product_id AND t2.cnt_02 > 10),这会让LEFT JOIN变成类INNER JOIN效果,悄悄过滤掉本该保留的左表行
聚合字段不一致会导致 JOIN 后结果错乱
两期聚合的分组键必须完全一致,否则对齐就失去意义。比如一期按 product_id 分组,另一期按 category, product_id 分组,JOIN 时用 product_id 匹配,结果里会出现一对多,cnt 值被放大或重复。
- 检查每个 CTE 或子查询的
GROUP BY列,确保维度颗粒度相同 - 如果需要多维对比(如按
region和product_id),两期的GROUP BY必须都包含这两个字段,且顺序无关,但字段名最好统一 - 别依赖
SELECT *做聚合后 JOIN —— 显式列出所有分组字段和聚合字段,避免隐式列导致后续维护困难
实际写的时候,先确认你要回答的问题是“有哪些产品变动”还是“每个产品的具体增减”,前者用 UNION ALL 加 period 就够了;后者必须走 CTE + FULL OUTER JOIN(或等效)。最容易被忽略的是:聚合前没过滤好时间范围,或者聚合后没处理 NULL 就直接参与计算。










