直接用 group by + max(date) 只能获取日期值,无法返回对应完整记录;必须通过子查询 join 或 row_number() 窗口函数实现,后者支持排序去重、语义更明确且兼容主流数据库。

用 GROUP BY + 子查询取每组最新记录
直接在 GROUP BY 后用 MAX(date) 只能拿到日期值,拿不到对应那条完整记录(比如用户ID、订单状态等)。真要查“每个分组里 date 最大的那条完整数据”,得靠子查询或窗口函数。
典型写法是先在子查询里算出每组的 MAX(date),再和原表 JOIN 回去匹配:
SELECT t1.* FROM orders t1 INNER JOIN ( SELECT user_id, MAX(order_date) AS max_date FROM orders GROUP BY user_id ) t2 ON t1.user_id = t2.user_id AND t1.order_date = t2.max_date;
注意:如果同一 user_id 下有多个记录日期相同且都是最大值,这条语句会返回全部——这是合理行为,不是 bug。
用窗口函数 ROW_NUMBER() 更稳更灵活
ROW_NUMBER() 是更推荐的方式,尤其当需要控制“并列时取哪一条”(比如按 ID 升序优先)或者字段有重复日期时。
关键点在于 ORDER BY 的排序逻辑必须明确:
- 想取时间最晚 + ID 最大那条?写
ORDER BY order_date DESC, id DESC - 只按日期排,日期相同时任意取一条?写
ORDER BY order_date DESC(不同数据库行为略有差异) - MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 3.25+ 也支持
示例:
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC, id DESC) AS rn
FROM orders
) ranked
WHERE rn = 1;
WHERE + 相关子查询也能做,但性能通常较差
这种写法直观,适合小表或临时查数,但大数据量时容易慢,因为对每行都执行一次子查询:
SELECT * FROM orders t1 WHERE order_date = ( SELECT MAX(order_date) FROM orders t2 WHERE t2.user_id = t1.user_id );
常见坑:
- 如果
order_date是NULL,MAX()返回NULL,导致整行被过滤掉——加WHERE order_date IS NOT NULL更安全 - 没加索引的话,
(user_id, order_date)联合索引能显著提速 - Oracle 早期版本对这种写法优化不佳,优先选窗口函数
GROUP BY + 其他聚合字段容易误用
有人试图这样写:SELECT user_id, MAX(order_date), status FROM orders GROUP BY user_id——这是错的。
错误原因:
- MySQL 5.7 默认开启
sql_mode=ONLY_FULL_GROUP_BY,会直接报错:Expression #3 of SELECT list is not in GROUP BY clause - 即使关了这个模式,
status值是随机取的,不保证来自MAX(order_date)对应的那条记录 - PostgreSQL、SQL Server 等严格标准下,这种写法语法就不通过
别图省事拼一行 SQL,该拆就拆,该套子查询就套。
真正麻烦的地方不在语法,而在业务含义是否清晰:所谓“最新”,是指最后创建时间?最后更新时间?还是某个状态变更时间?字段语义模糊时,光靠 MAX() 很容易查出错误结果。











