正确解法是用窗口函数ROW_NUMBER()按分类和更新时间倒序编号后取rn=1的行,或用MySQL的JOIN子查询、PostgreSQL的DISTINCT ON;过滤条件须放在子查询内以确保逻辑正确。
ROW_NUMBER() 按分类和时间排序取首行直接用 GROUP BY 加 MAX(updated_at) 只能拿到时间戳,拿不到对应那条完整记录——这是最常踩的坑。正确解法是用窗口函数给每组内的记录按更新时间倒序编号,再过滤出编号为 1 的行。
假设表叫 articles,字段有 category、title、content、updated_at:
SELECT category, title, content, updated_atFROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY updated_at DESC, id DESC ) AS rn FROM articles) rankedWHERE rn = 1;
注意两点:一是 ORDER BY updated_at DESC 确保最新在前;二是追加 id DESC 防止同秒更新时结果不稳定(避免无序导致每次查询结果不一致)。
得用关联子查询或自连接,但性能差、写法绕。核心思路是:对每个 category,找出它的最大 updated_at,再连回原表匹配该时间和分类。
JOIN + 子查询(推荐,相对易读):SELECT a1.*FROM articles a1INNER JOIN ( SELECT category, MAX(updated_at) AS max_updated FROM articles GROUP BY category) a2 ON a1.category = a2.category AND a1.updated_at = a2.max_updated;
updated_at 完全相同,这个写法会返回多条——不是“最新一条”,而是“最新时间的所有条”。要严格保一条,得在子查询里加 id 辅助去重,或改用 LEFT JOIN 自连接找“不存在更新时间更大的同分类记录”。DISTINCT ON 是更简洁的替代方案这是 PostgreSQL 特有语法,比窗口函数少嵌套一层,语义也更直白:
SELECT DISTINCT ON (category) category, title, content, updated_atFROM articlesORDER BY category, updated_at DESC, id DESC;
关键点:DISTINCT ON 必须配合 ORDER BY 使用,且 ORDER BY 的前导字段必须和 DISTINCT ON 一致;后续字段决定“哪一条被留下”——这里用 updated_at DESC, id DESC 确保取到最新且 ID 最大的那条。
不支持跨数据库迁移,但如果你用 PG,它比 ROW_NUMBER() 更轻量、可读性更好。
比如想只查 status = 'published' 的最新记录,错误写法是把 WHERE status = 'published' 放在最外层——这会导致先取最新再过滤,可能漏掉某些分类(因为最新那条被过滤掉了)。
正确做法是:把过滤条件放在子查询或 CTE 内部,确保排序和编号基于已筛选的数据集:
SELECT category, title, content, updated_atFROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY updated_at DESC, id DESC ) AS rn FROM articles WHERE status = 'published' -- ✅ 这里过滤) rankedWHERE rn = 1;
漏掉这一步,结果就不可靠——尤其是当某些分类最新记录恰好被 WHERE 排除时,整个分类会消失,而不是退而求其次选次新记录。
真正麻烦的不是语法,而是想清楚“最新”是针对全量数据,还是针对某个子集;这个逻辑偏差,比写错函数名还容易引发线上问题。