因为MAX(time)只返回最大时间值,无法保证对应行的其他字段(如status、user_id)同步取出;MySQL在ONLY_FULL_GROUP_BY模式下会报错,关闭后结果随机不可靠。

GROUP BY后取最新记录为什么不能直接用MAX(time)?
因为MAX(time)只能拿到时间最大值,但对应那一行的其他字段(比如status、user_id)无法保证同步取出——MySQL在ONLY_FULL_GROUP_BY模式下会直接报错:Expression #2 of SELECT list is not in GROUP BY clause;即使关掉该模式,结果也是随机的,不可靠。
用窗口函数ROW_NUMBER()最稳(MySQL 8.0+)
这是目前语义最清晰、行为最确定的方式:先按分组和时间排序编号,再筛出每组rn = 1的记录。
示例(取每个category下created_at最新的订单):
SELECT id, category, amount, created_at
FROM (
SELECT id, category, amount, created_at,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;注意点:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
PARTITION BY字段必须和你要的“每组”逻辑一致,别漏或多写 -
ORDER BY里如果有相同时间,建议追加主键(如ORDER BY created_at DESC, id DESC)避免不确定排序 - 如果只要最新一条,用
ROW_NUMBER();如果要并列最新(比如多个同时间记录都算最新),改用RANK()
兼容老版本MySQL(5.7及以下)得绕开窗口函数
常见做法是自连接或子查询关联最大时间,但容易出错:
- 用
(SELECT MAX(created_at) FROM orders o2 WHERE o2.category = o1.category)做子查询,再关联原表——但若存在时间重复,可能返回多行 - 更安全的是用
LEFT JOIN自连接,排除“有更大时间”的记录:
SELECT o1.* FROM orders o1 LEFT JOIN orders o2 ON o1.category = o2.category AND o1.created_at < o2.created_at WHERE o2.id IS NULL;
这个写法性能差(全表自连),数据量大时务必给(category, created_at)建联合索引,否则慢到卡死。
别踩“用GROUP BY + ANY_VALUE()糊弄过去”的坑
有人关掉ONLY_FULL_GROUP_BY,然后写SELECT category, ANY_VALUE(id), MAX(created_at)——这看似能跑,但ANY_VALUE(id)不保证和MAX(created_at)来自同一行,结果是错的,只是你暂时没发现。
真正需要“最新记录”,核心就两点:一是明确“最新”的定义(时间?ID?),二是确保整行数据原子性获取。窗口函数是首选,老版本则宁可多查一次,也别赌ANY_VALUE()碰巧对上。

















