直接用 GROUP BY + MAX(id) 无法返回完整行,因 SQL 标准要求 SELECT 列须为分组字段或聚合函数结果,* 不合规;且 MAX(id) 仅返回最大 ID 值,不关联整行数据,属“分组内 Top 1”问题,需子查询或窗口函数解决。

为什么直接用 GROUP BY + MAX(id) 无法返回完整行
很多人写 SELECT *, MAX(id) FROM table GROUP BY column_a,结果报错或数据错乱——因为 SQL 标准要求:GROUP BY 后的 SELECT 列,要么是分组字段,要么必须被聚合函数包裹。而 * 显然不满足这个规则,MySQL 5.7+ 默认严格模式下会直接报错:Expression #1 of SELECT list is not in GROUP BY clause。
更关键的是,MAX(id) 只告诉你“最大 ID 是多少”,但不会自动帮你把整条对应记录捞出来。你真正要的是“每组中 id 最大的那条完整记录”,这属于「分组内 Top 1」问题,不是纯聚合能解决的。
正确做法:用子查询关联原表取完整行
核心思路是先算出每组的 MAX(id),再用这个结果跟原表 JOIN 或 IN 匹配,拿到完整记录:
SELECT t1.* FROM your_table t1 INNER JOIN ( SELECT column_a, MAX(id) AS max_id FROM your_table GROUP BY column_a ) t2 ON t1.column_a = t2.column_a AND t1.id = t2.max_id;
注意点:
-
t1和t2必须用别名,否则外层SELECT t1.*会因歧义报错 - 如果
column_a允许 NULL,GROUP BY column_a会把所有 NULL 归为一组,需确认是否符合业务预期 - 若存在多条记录 ID 相同(极少见),该写法可能返回多行;实际中 ID 通常是主键或唯一,可忽略
替代方案:用窗口函数(MySQL 8.0+/PostgreSQL/SQL Server)
如果数据库支持窗口函数,ROW_NUMBER() 更直观且可控:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY column_a ORDER BY id DESC) AS rn FROM your_table ) ranked WHERE rn = 1;
优势与差异:
- 不用自连接,逻辑更线性,可读性高
-
ROW_NUMBER()保证每组只返回 1 行,即使有相同id也能稳定去重(按ORDER BY中后续字段决胜) - 比
GROUP BY + JOIN多一次全表扫描,大数据量时注意性能;但现代优化器通常能较好处理 - PostgreSQL 还可选
DISTINCT ON (column_a) ORDER BY column_a, id DESC,更简洁
删除重复数据只留最大ID那条(谨慎操作!)
查出来只是第一步,真要删,千万别用 DELETE ... JOIN 直接套上面的 SELECT 逻辑——MySQL 不允许在子查询中直接修改目标表。安全写法是:
DELETE t1 FROM your_table t1 LEFT JOIN ( SELECT column_a, MAX(id) AS max_id FROM your_table GROUP BY column_a ) t2 ON t1.column_a = t2.column_a AND t1.id = t2.max_id WHERE t2.max_id IS NULL;
执行前务必:
- 先备份表:
CREATE TABLE your_table_backup AS SELECT * FROM your_table; - 在测试环境验证 WHERE 条件是否精准命中要删的行
- 避免在大表上无索引运行——确保
(column_a, id)有联合索引,否则GROUP BY和JOIN都会很慢
真正容易被忽略的,是 column_a 的数据类型和空值处理——比如 VARCHAR 字段带前后空格、大小写不敏感比较、或隐式类型转换导致分组异常,这些都会让“重复”的定义和你预期不一致。










