GROUP BY不保证顺序,“每组第一条”需显式排序并取极值;推荐用ROW_NUMBER()窗口函数,次选JOIN聚合表,避免相关子查询和旧版MySQL隐式行为。

GROUP BY本身不保证顺序,不能直接拿“第一条”
SQL标准里GROUP BY只负责分组聚合,不定义组内行的顺序。所谓“每组第一条”,本质是“每组中按某字段排序后取第一条”。直接在GROUP BY后用SELECT *或SELECT col会报错(除非所有非聚合列都在GROUP BY里),更别说指定“第一条”了。
常见错误写法:SELECT *, MIN(created_at) FROM orders GROUP BY user_id —— 这在MySQL 5.7+严格模式下直接报错ERROR 1055,因为*包含未聚合也未分组的列。
- 别指望
GROUP BY自动给你挑出某条记录——它只配合聚合函数工作 - “第一条”必须显式定义排序依据,比如
ORDER BY id或ORDER BY created_at DESC - 不同数据库对
GROUP BY宽松程度不同(如旧版MySQL允许“隐式任意值”),但这是不可靠的行为,应避免
用窗口函数ROW_NUMBER()最通用可靠
主流数据库(PostgreSQL、SQL Server、Oracle、MySQL 8.0+、SQLite 3.25+)都支持窗口函数。ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)能给每组内行编号,再过滤rn = 1即可。
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;-
PARTITION BY user_id对应你要的分组维度 -
ORDER BY created_at DESC决定哪条算“第一”——要最新记录就DESC,最早就ASC - 如果存在并列(比如同个
user_id有多条相同created_at),ROW_NUMBER()仍会强制编号(1,2,3…),而RANK()或DENSE_RANK()会并列编号,这时可能返回多条,需按业务判断是否接受
老版本MySQL或不支持窗口函数时用关联子查询
MySQL 5.7或更早版本没ROW_NUMBER(),得用相关子查询模拟:对每组找满足“该行排序字段值等于该组极值”的记录。
SELECT o1.* FROM orders o1 WHERE o1.created_at = ( SELECT MAX(o2.created_at) FROM orders o2 WHERE o2.user_id = o1.user_id );
- 这个写法依赖
created_at唯一性;如果存在重复时间,可能返回多行 - 性能较差——外层每行都触发一次子查询,数据量大时明显变慢
- 若要取“最小ID”而非“最新时间”,把
MAX()换成MIN(id),子查询条件保持o2.user_id = o1.user_id - 注意:如果排序字段允许NULL,
MAX()结果为NULL时整行会被过滤掉,需额外处理
用JOIN + 聚合表性能更好但写法稍长
先聚合出每组的极值(如最大id或最新created_at),再和原表JOIN回查完整记录。比相关子查询更容易利用索引。
SELECT o.* FROM orders o INNER JOIN ( SELECT user_id, MAX(id) AS max_id FROM orders GROUP BY user_id ) t ON o.user_id = t.user_id AND o.id = t.max_id;
- 必须确保
MAX(id)能唯一标识一行(即id是主键或至少组内唯一) - 如果按时间取最新,且
created_at不唯一,建议组合(created_at, id)做联合极值,否则可能漏数据或取错行 - 这个方案在
user_id和id上有复合索引时效率很高
实际选哪种方式,取决于你用的数据库版本、数据量大小、以及“第一条”的定义是否允许并列。窗口函数是首选,但别忘了检查ORDER BY字段的可排序性和唯一性——这才是最容易被跳过的细节。

















