应使用窗口函数ROW_NUMBER()配合子查询获取每组最新完整记录,而非直接用MAX()聚合,因MAX()仅返回最大值不绑定整行数据。

直接用 MAX(date) 无法拿到整行数据
很多人写 SELECT user_id, MAX(created_at), content FROM posts GROUP BY user_id,发现 content 值是随机的(MySQL 5.7+ 默认报错,旧版可能返回非对应行)。这是因为 MAX() 只计算聚合值,不绑定其他字段的来源行。你真正要的是「每个分组里 created_at 最大的那条完整记录」,不是「最大日期 + 某个不确定的 content」。
推荐方案:用窗口函数 ROW_NUMBER() 排序后取 Top 1
这是最直观、可读性高且兼容主流数据库(PostgreSQL、SQL Server、MySQL 8.0+、Oracle)的做法。核心思路是给每组按日期倒序编号,再筛出编号为 1 的行:
SELECT user_id, created_at, content
FROM (
SELECT user_id, created_at, content,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM posts
) t
WHERE rn = 1;
注意点:
-
PARTITION BY user_id对应你的分组字段,别写成GROUP BY - 如果同用户有多条记录日期相同,
ROW_NUMBER()会任意选一个;需要稳定结果可追加二级排序,例如ORDER BY created_at DESC, id DESC - 想保留并列第一(比如两个相同最大日期都算),改用
RANK()或DENSE_RANK()
兼容老版本 MySQL(5.7 及更早)的替代写法
没有窗口函数时,常用自连接或子查询。但要注意性能和空值陷阱:
created_at
- MySQL 5.7+ 不支持多列
IN子查询(报错ERROR 1242) - 即使支持(如 PostgreSQL),若存在重复最大日期,会返回多行——而你可能只想要一条
- 索引利用率低,尤其当
created_at未建联合索引时,子查询可能全表扫描
真正麻烦的地方不在语法,而在你是否意识到:日期相等时业务上要“任选一条”还是“全都要”,以及空值、时区、索引缺失这些隐形雷——它们比怎么写 SQL 更容易让你半夜被报警叫醒。











