正确做法是用ROW_NUMBER()按user_id分组、order_time降序编号取rn=1,需加二级排序(如id DESC)确保结果稳定;MySQL 5.7等旧版本需用关联子查询替代。

用窗口函数获取每个用户的最新订单
直接用 GROUP BY 加 MAX(order_time) 无法拿到对应那笔订单的金额,因为聚合和非聚合字段混用会报错(如 MySQL 严格模式下 SELECT user_id, MAX(order_time), amount 不合法)。正确做法是先标记出每条订单在其用户内的序号,再筛选序号为 1 的记录。
推荐用 ROW_NUMBER() 按用户分组、按时间倒序编号:
SELECT user_id, order_amount
FROM (
SELECT user_id, order_amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn
FROM orders
) t
WHERE rn = 1;
-
PARTITION BY user_id确保编号在每个用户内独立计算 -
ORDER BY order_time DESC保证最新订单排第一;若时间有重复,需加二级排序(如id DESC)避免结果不稳定 - 不用
RANK()或DENSE_RANK(),它们对相同时间会并列编号,可能导致取多条
处理时间相同但需唯一最新订单的场景
当多个订单 order_time 完全相同时,ROW_NUMBER() 默认按任意顺序分配序号,结果不可预测。必须引入确定性排序依据。
- 优先加主键排序:
ORDER BY order_time DESC, id DESC(假设id自增,越大越新) - 若无可靠递增字段,可用
ORDER BY order_time DESC, order_id DESC(只要order_id能反映创建先后) - 避免只依赖
order_time—— 很多系统插入时未精确到毫秒,批量导入更易撞时间
MySQL 5.7 或旧版本不支持窗口函数怎么办
只能用关联子查询或自连接模拟,性能较差,但兼容性强。
SELECT o1.user_id, o1.order_amount FROM orders o1 WHERE o1.order_time = ( SELECT MAX(o2.order_time) FROM orders o2 WHERE o2.user_id = o1.user_id );
- 这个写法在
order_time唯一时安全;若有重复,会返回多行 —— 和窗口函数行为不同 - 务必给
(user_id, order_time)建联合索引,否则子查询会全表扫描 - 如果业务允许“任取一条”,可加
LIMIT 1配合ORDER BY,但需用外层包裹才能生效,写法更绕
注意 NULL 和空数据的影响
order_time 为 NULL 的订单会被 ORDER BY ... DESC 排到最后(多数数据库默认),导致 ROW_NUMBER() 把它标成非 1 号 —— 这通常符合预期。但要确认业务是否允许 NULL 时间存在。
- 若存在无效订单(
order_time IS NULL),建议前置过滤:WHERE order_time IS NOT NULL - 若用户从未下单,该用户不会出现在结果中;需要保留所有用户(包括 0 订单),得用
LEFT JOIN关联用户表,并处理NULL金额 - 金额字段本身为
NULL时,order_amount直接输出NULL,无需额外处理

















