这不是bug,而是sql标准强制要求非聚合列必须出现在group by或被聚合函数包裹;mysql 5.7+启用only_full_group_by后报错,postgresql等数据库则一律拒绝执行,因语义上无法确定返回哪一行的值。

非聚合列不进GROUP BY会直接报错
这不是数据库“较真”,而是语义上根本无法确定返回哪一行的值。比如 SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id,user_id = 100 对应 5 条订单,name 却可能是 'Alice'、'alice '、NULL、'Alice123' —— 数据库没法猜你要哪个。PostgreSQL、SQL Server、Oracle 一律拒绝执行;MySQL 5.7+ 启用 ONLY_FULL_GROUP_BY 后也报错:Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column。
为什么不能靠主键推导自动放行
即使 name 在 users 表里由 user_id 主键决定(1:1),SQL 标准也不允许数据库“脑补”这种依赖关系。原因很实际:
-
JOIN后字段来源可能模糊:比如LEFT JOIN orders o ON u.id = o.user_id,o.status可能为NULL,此时按u.id分组却选o.status,就会把所有无订单用户归成一组 - 别名或表达式不被识别:
SELECT UPPER(name) AS n FROM users GROUP BY id是非法的——GROUP BY必须写UPPER(name),不能只写name,也不能用别名n - 多表关联时字段归属易错:用
orders.customer_id分组,但SELECT customers.name,如果customer_id存在脏数据(如重复、空值),结果就不可控
常见绕过方式及其风险
有人用 ANY_VALUE(name) 或 MAX(name) 想绕开 GROUP BY 扩展,但要注意:
-
ANY_VALUE()是 MySQL 特有,且不保证稳定性:同一查询多次执行,可能返回不同name值,尤其在并发更新或优化器重排时 -
MAX(name)看似安全,但字符串比较依赖排序规则(collation):大小写、空格、Unicode 归类都影响结果,MAX('alice') vs MAX('Alice')可能出人意料 - 用子查询或
JOIN拆开逻辑更可靠,比如先GROUP BY user_id算出统计值,再和原表JOIN补全name,但要注意关联字段是否唯一、是否有 NULL
最容易被忽略的细节
真正踩坑的往往不是语法,而是数据本身:
-
GROUP BY字段含NULL:所有NULL被视为同一组,如果业务上name IS NULL代表“未填写”,这一组就混了多个用户 - 时间字段没截断:写
GROUP BY created_at会按秒分组,想按天统计得用DATE(created_at),且必须在SELECT和GROUP BY中保持完全一致 - 隐式类型转换:比如
user_id是字符串型'001',但GROUP BY CAST(user_id AS SIGNED)和SELECT user_id不匹配,照样报错











