这不是 bug,是 sql 标准要求 select 中非聚合列必须出现在 group by 或被聚合函数包裹;mysql 5.7+ 启用 only_full_group_by 强制该规则,避免隐式取值导致结果不可靠。

直接结论:这不是 bug,是 SQL 标准在拦你——必须让 SELECT 中每个非聚合列要么出现在 GROUP BY 里,要么被聚合函数包裹。
为什么 MySQL 5.7+ 突然报 Expression #1 of SELECT list is not in GROUP BY clause
MySQL 5.7 默认启用了 ONLY_FULL_GROUP_BY 模式,它强制执行 SQL92 标准语义:只要你在 SELECT 里写了没套 MIN()、MAX()、COUNT() 等函数的字段,这个字段就必须明明白白出现在 GROUP BY 子句中。
常见错误写法:
SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id
这里 name 既没进 GROUP BY,也没被聚合,MySQL 就会拒绝执行。
- 这不是“MySQL 变严格了”,而是之前宽松模式(允许隐式取值)本就不符合标准,容易导致结果不可靠
- PostgreSQL / SQL Server / Oracle 从不妥协——它们根本没
ANY_VALUE(),也不接受“随便选一个”,一写就报错 - 云数据库(如阿里云 RDS)通常禁用
SET GLOBAL,改配置只能走控制台参数模板
把非聚合列加进 GROUP BY 是最安全的做法
如果业务上确实需要 name 和 user_id 同时出现,且二者是 1:1 关系(比如 name 来自用户主表),那就补全:
SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id, name
但要注意几个坑:
- 如果
name实际存在重复user_id对应多个name(比如历史改名未清理),加进去会导致分组数暴增,统计口径歪掉 - 字符串字段参与分组时,注意前后空格、大小写、NULL 值——
GROUP BY name会把所有NULL归为一组,可能漏掉预期外的数据 - 时间类字段如
TIMESTAMP涉及时区,GROUP BY DATE(created_at)在跨时区场景下可能切错日期
用 ANY_VALUE() 或聚合函数包裹字段要非常谨慎
ANY_VALUE(name) 是 MySQL 特有函数,意思是“从这组里随便拿一个”,但它不保证稳定性:
- 同一查询在不同 MySQL 版本、不同优化器路径、甚至并行执行时,可能返回不同
name - 它不被其他数据库支持,写死
ANY_VALUE()的 SQL 很难迁移到 PostgreSQL 或 Flink SQL - 如果业务真正要的是“最新订单对应的用户名”,而你用了
MAX(name),那按字典序取最大值,和业务逻辑完全无关
更靠谱的替代是显式聚合:
- 取最新时间:
MAX(created_at) - 拼接多值:
GROUP_CONCAT(status SEPARATOR ',')(MySQL)、STRING_AGG(status, ',')(PostgreSQL) - 取首条记录 ID:
MIN(id)或MAX(id)(仅当 ID 递增且语义明确时)
真正需要“每组一条 + 原始明细”时,别硬套 GROUP BY
当你想查“每个部门薪资最高的人的完整信息(姓名、工号、入职时间)”,GROUP BY dept 加 ANY_VALUE(name) + MAX(salary) 是错的——这两个值大概率来自不同行。
正确解法是窗口函数:
SELECT name, dept, salary, hire_date
FROM (
SELECT name, dept, salary, hire_date,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn = 1;
关键点:
-
ROW_NUMBER()确保每组内排序唯一,不会漏人也不会重复 - 所有原始字段(
name、hire_date)都能保留,不丢失上下文 - 这个写法在 PostgreSQL、SQL Server、Flink SQL、MySQL 8.0+ 都能跑,兼容性远高于
ANY_VALUE()
最容易被忽略的是:GROUP BY 本质是“压缩行”,而你要的是“带聚合信息的明细行”——这是两类问题,强行混用只会让逻辑越来越脆。











