跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

为什么SQL中使用了GROUP BY却仍然出现重复行_检查SELECT中的隐藏列

MySQL严格模式下SELECT混入未分组字段会报错,非分组字段须用聚合函数或加入GROUP BY;空格、NULL影响分组精度;JOIN后需预聚合防重复;取最新记录应优先用窗口函数而非GROUP BY。 SELECT里混入未分组字段,MySQL严格模式直接报错 MySQL 5.7+ 默认开启严格模式,
SELECT a, b, COUNT(*) FROM t GROUP BY a
会触发
SQLSTATE[42000]: Syntax error or access violation
。这不是bug,是规则强制:所有非分组字段必须被聚合函数包裹,或出现在
GROUP BY
列表中。 常见翻车现场: 写成
SELECT user_id, order_no, created_at FROM orders GROUP BY user_id
——
order_no
和
created_at
既没聚合也没分组,MySQL 拒绝执行 依赖旧版 MySQL 5.6 的“宽松模式”开发,上线后换环境立刻崩 解决方式只有两种:要么把
order_no
包进
MAX()
、
MIN()
,要么把它们加进
GROUP BY
——但后者可能让本该合并的行被拆开。 看似“重复”的分组键,其实是NULL或空格惹的祸
GROUP BY
确实会合并相同值,但“相同”是数据库层面的判定,不是肉眼判断。比如:
'abc '
(带尾部空格)和
'abc'
在多数数据库里是不同值;
NULL
虽然被归为一组,但业务上常要求它不参与去重。 排查建议: 用
SELECT LENGTH(TRIM(name)), name FROM t GROUP BY TRIM(name)
看是否因空格导致分组过细 对敏感字段统一处理:
GROUP BY LOWER(TRIM(email))
若想让
NULL
不参与分组逻辑,改用
COALESCE(email, '')
占位 别跳过这步——很多“重复分组”问题查到最后,都是
TRIM()
没加。 JOIN之后再GROUP BY,重复是必然结果 一对多关联(如用户表 × 订单明细表)不做预聚合,直接
GROUP BY user_id
,会导致一个用户对应N条明细,
COUNT(*)
变成N倍,
SUM(amount)
也重复累加。 正确做法不是在外层硬套
DISTINCT
,而是提前收敛: 子查询先聚合:
(SELECT user_id, SUM(amount) total FROM order_items GROUP BY user_id)
,再跟用户表
JOIN
或用
LEFT JOIN
+ 子查询聚合替代直接多对多
JOIN
检查
EXPLAIN
输出,确认子查询是否命中
user_id
索引;没走索引时,
GROUP BY
可能因临时表限制截断结果 外层
SELECT *
连一堆字段进来,再加
GROUP BY
,基本等于在给自己埋雷。 想取每组最新一条完整记录?别碰GROUP BY 用
MAX(created_at)
配合
JOIN
取最新订单,大概率漏数据:同一秒下多单就只取到其中一条;更糟的是,如果
created_at
不是唯一约束,还可能匹配到其他用户的同时间记录。 真正可靠的做法: 支持窗口函数的数据库(MySQL 8.0+、PostgreSQL、Oracle):用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)
,外层筛
rn = 1
不支持窗口函数的老版本:相关子查询
WHERE created_at = (SELECT MAX(created_at) FROM orders o2 WHERE o2.user_id = o1.user_id)
,但得接受并列时间带来的不确定性 绝对不要写
SELECT *, MAX(created_at) FROM orders GROUP BY user_id
——
MAX(created_at)
和其他字段根本不在同一行 GROUP BY 的设计目标从来不是“挑一行”,而是“归一类”。拿它当去重工具,迟早踩坑。

相关文章