因为group by+max()仅返回最大时间值,无法获取对应记录的其他字段;mysql 5.7+启用only_full_group_by后会报错“expression #1 of select list is not in group by clause”,强行查询则结果不可靠。

为什么不能直接用 GROUP BY + MAX(time) 拿最近一条?
因为 MAX(time) 只能返回时间值,拿不到对应那条记录的其他字段(比如 status、user_id)。强行 SELECT * 会报错或返回随机行——MySQL 5.7+ 默认开启 sql_mode=ONLY_FULL_GROUP_BY,直接拒绝这种写法。
常见错误现象:Expression #1 of SELECT list is not in GROUP BY clause,或者结果中 status 和 MAX(created_at) 不匹配。
实操建议:
- 别试图靠子查询先查
MAX(created_at)再JOIN回原表——多对一关联时可能命中多条,还得加LIMIT 1或二次去重,逻辑脆弱 - 窗口函数是更干净的解法:先按分组排序标序号,再筛序号为 1 的行
用 ROW_NUMBER() 窗口函数标记每组最新记录
核心思路:对每个分组(如 user_id)按时间倒序排序,给每行打一个序号,取序号 = 1 的那条。
示例(查每个用户的最新订单):
SELECT user_id, order_id, amount, created_at
FROM (
SELECT user_id, order_id, amount, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;
注意点:
-
PARTITION BY是分组依据,必须明确;ORDER BY必须包含时间字段且用DESC - 如果同一用户有多条记录时间相同,
ROW_NUMBER()会任意分配 1/2/3…,此时应补充次级排序字段(如id DESC)保证确定性 - 性能上,该查询依赖
(user_id, created_at)复合索引,否则窗口计算开销大
如何在 JOIN 中复用「最近一条」结果?
典型场景:查用户信息 + 其最新登录记录。不能把窗口函数写在 ON 条件里,得先生成中间结果再 JOIN。
实操建议:
- 用 CTE(
WITH子句)封装窗口查询,提高可读性和复用性 - 避免在
JOIN右侧直接写子查询——部分旧版 MySQL 不支持,且优化器难做下推
示例:
WITH latest_login AS (
SELECT user_id, ip, login_time,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_time DESC) AS rn
FROM login_logs
)
SELECT u.id, u.name, l.ip, l.login_time
FROM users u
LEFT JOIN latest_login l ON u.id = l.user_id AND l.rn = 1;
关键细节:
-
LEFT JOIN保证用户不因无登录记录而丢失;l.rn = 1必须写在ON里,不能放WHERE,否则变INNER JOIN - 若用
RANK()或DENSE_RANK(),时间相同时会并列排名,可能导致多行被选中——除非业务允许「并列最新」,否则坚持用ROW_NUMBER()
MySQL 8.0 之前没窗口函数怎么办?
只能退回到相关子查询或自连接,但性能差、语法绕,且容易出错。
勉强可用的替代写法(仅限小数据量):
SELECT u.id, u.name, l1.ip, l1.login_time FROM users u LEFT JOIN login_logs l1 ON u.id = l1.user_id WHERE l1.login_time = ( SELECT MAX(l2.login_time) FROM login_logs l2 WHERE l2.user_id = u.id ) OR l1.user_id IS NULL;
问题明显:
- 子查询对每个用户执行一次,
O(N×M)复杂度 - 当存在多个相同最大时间的记录时,
l1会返回多行,需额外去重 - 无法利用索引高效执行
MAX()子查询,尤其无user_id + login_time索引时
真正上线项目,优先升级到 MySQL 8.0+ 或换用支持窗口函数的数据库。硬扛旧版本,迟早卡在关联性能上。










