MySQL严格模式下GROUP BY后字段须在SELECT中出现或套用聚合函数,否则报错;COUNT(*)统计所有行,COUNT(字段)仅统计非NULL值;日期聚合宜用YEAR/MONTH分组而非BETWEEN。

GROUP BY 之后字段必须出现在 SELECT 或聚合函数里
MySQL 严格模式下,SELECT 列表中如果出现没被 GROUP BY 覆盖、又没套聚合函数的字段,会直接报错:Expression #1 of SELECT list is not in GROUP BY clause。这不是 bug,是 SQL 标准行为,但很多人误以为是配置问题去关 strict mode,其实该修的是写法。
- 错误写法:
SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id——name没聚合也没分组,MySQL 不知道该取哪一行的值 - 正确做法:要么去掉
name,要么用MAX(name)/MIN(name)(假设同user_id的name一致),或改用ANY_VALUE(name)(MySQL 5.7+,明确表示“我接受任意值”) - 别依赖
sql_mode临时绕过,生产环境可能因版本/配置差异突然崩掉
COUNT(*) vs COUNT(字段) 区别不止是性能
COUNT(*) 统计行数,COUNT(字段) 只统计该字段非 NULL 的行。报表里漏掉空值常导致数据对不上,尤其在关联查询后。
- 比如统计每个用户下单数:
COUNT(*)对应订单表总行数;COUNT(order_amount)会跳过order_amount IS NULL的订单 - 左连接查用户+订单时,用
COUNT(o.id)才能正确体现“0 单用户”,而COUNT(*)会把用户行也算进去(因为 LEFT JOIN 后用户行仍在) -
COUNT(*)在 InnoDB 中通常最快(引擎可优化),但语义不对就再快也没用
日期维度聚合别硬写 BETWEEN,用 DATE() 或按年月分组更稳
查“2024 年每月销售额”时,用 WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31' 看似简单,但容易因时区、索引失效或 DATETIME 精度问题漏数据。
- 推荐写法:
GROUP BY YEAR(created_at), MONTH(created_at),或GROUP BY DATE_FORMAT(created_at, '%Y-%m') - 如果字段是
DATETIME类型,WHERE DATE(created_at) = '2024-01-01'会导致全表扫描——改用WHERE created_at >= '2024-01-01' AND created_at - 注意
DATE()函数无法走索引,范围查询优先用 >= /
报表结果排序和 LIMIT 要放在最后一步
聚合完再排序,不是先排序再聚合。常见错误是把 ORDER BY created_at DESC LIMIT 10 写在 GROUP BY 前面,结果只取原始表前 10 行再分组,完全失真。
- 正确顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
- 想取“销量 top 10 的商品”,得先
GROUP BY product_id,再ORDER BY SUM(quantity) DESC LIMIT 10 -
HAVING是过滤分组后结果的唯一方式,WHERE过滤不了聚合值(比如不能WHERE SUM(price) > 1000)
聚合报表最麻烦的从来不是语法,而是搞清每一行数据到底代表什么业务含义——GROUP BY 的粒度、NULL 怎么处理、时间范围怎么切,这些地方一错,数字看着再整齐也没用。










