必须先对多端表按关联键预聚合再JOIN,否则一对多JOIN会导致主表行复制、SUM/COUNT在膨胀后的组合行上计算而翻倍。

JOIN前不预聚合会导致SUM/COUNT翻倍
一对多关联时,直接JOIN再GROUP BY会让主表行被复制,SUM和COUNT统计的是组合行而非业务本意。比如orders和payments一对多,SELECT YEAR(o.order_date), SUM(o.amount) 会把同一订单的多笔支付重复计入订单金额。
必须先各自按关键字段聚合,再JOIN:
SELECT o.y, o.m, COALESCE(o.total_order, 0), COALESCE(p.total_paid, 0) FROM ( SELECT YEAR(order_date) AS y, MONTH(order_date) AS m, SUM(amount) AS total_order FROM orders GROUP BY YEAR(order_date), MONTH(order_date) ) o FULL JOIN ( SELECT YEAR(paid_at) AS y, MONTH(paid_at) AS m, SUM(amount) AS total_paid FROM payments GROUP BY YEAR(paid_at), MONTH(paid_at) ) p ON o.y = p.y AND o.m = p.m;
- 用子查询或
WITH提前隔离聚合层级,避免中间结果膨胀 -
FULL JOIN保留单边存在的情况(如某月有订单无回款),比LEFT JOIN更贴合财务对账逻辑 - 别在JOIN后才开始
GROUP BY——执行计划里rows可能暴涨十倍以上
GROUP BY字段漏写或错写引发非预期分组
SELECT列表中每个非聚合字段都必须显式出现在GROUP BY子句里,否则MySQL 5.7+或PostgreSQL会报错:ERROR 1055: Expression not in GROUP BY clause。即使关掉ONLY_FULL_GROUP_BY,返回值也是不确定的。
常见错误写法:SELECT d.name, u.email, COUNT(*) FROM dept d JOIN user u ON d.id = u.dept_id GROUP BY d.id——u.email既没聚合也没分组。
- 补全
GROUP BY d.id, u.email,但需确认每个d.id是否真对应唯一u.email - 改用
MAX(u.email)或GROUP_CONCAT(u.email),语义更清晰 - 字段必须带表前缀,
GROUP BY name是危险的,应写成GROUP BY d.name - SELECT中用了函数(如
DATE(created_at)),GROUP BY里也得一模一样,不能简写为created_at
LEFT JOIN后COUNT(*)和COUNT(字段)行为完全不同
COUNT(*)统计的是结果集行数,而COUNT(orders.id)只统计非NULL值。LEFT JOIN中,右表无匹配时orders.id为NULL,COUNT(*)仍返回1,COUNT(orders.id)才返回0——这是报表数据偏差最隐蔽的来源。
例如统计每月订单数,左表是月份维度表,右表是订单表:
- 错:
COUNT(*)→ 每月都返回1,无法识别空缺月份 - 对:
COUNT(orders.id)→ 空缺月份显示为0 - 若要强制补全所有月份,左表不能依赖业务表现有记录,得用
SELECT '2026-01' UNION SELECT '2026-02'这类方式构造完整维度 -
COALESCE(SUM(orders.amount), 0)比SUM(orders.amount)更安全,避免NULL干扰下游计算
字符集或排序规则不一致导致GROUP BY静默失效
当JOIN的字段来自不同表且COLLATION不一致(如utf8mb4_unicode_ci vs utf8mb4_0900_as_cs),GROUP BY可能无法正确归并相同值,甚至触发Illegal mix of collations错误。
- 不要在
GROUP BY里硬加COLLATE,它大概率无法命中索引,还会让优化器放弃使用Using index - 统一源头表字段的
COLLATION,或在ETL层做标准化处理 - 检查执行计划:
EXPLAIN FORMAT=TREE中若出现type: ALL或Using temporary; Using filesort,大概率是排序规则冲突导致分组前无法走索引 - 临时绕过方案:在JOIN条件里显式转换,如
ON t1.name = CONVERT(t2.name USING utf8mb4),但仅限调试,不可上线
GROUP BY本身不会修复JOIN阶段已发生的膨胀——它只是对错误的数据做数学运算。排查时优先看JOIN后的中间行数,而不是盯着聚合结果调公式。

















