游乐游手机版
首页/数据库/文章详情

SQL查询每个分类下最新更新的一条完整记录

时间:2026-06-24 17:50
使用窗口函数`ROW_NUMBER()`按分类和更新时间倒序编号并取首行,可获取每个分类的最新完整记录。注意附加`id`排序避免同秒结果不稳定。MySQL老版本可用子查询配合`MAX(updated_at)`自连接,但需警惕同时间多条记录;PostgreSQL的`DISTINCTON`更简洁。过滤条件应置于子查询内,避免逻辑偏差。

先聊一个不少同学踩过的坑:用 GROUP BYMAX(updated_at) 确实能拿到每个分类的最新更新时间戳,但你要的是那条完整记录——时间戳之外的字段全没着落。真正靠谱的解法是窗口函数 ROW_NUMBER() 按分类和更新时间倒序编号,然后只取编号为 1 的行。MySQL 里可以配合 JOIN 子查询,PostgreSQL 则有更简洁的 DISTINCT ON。另外提醒一句:过滤条件放错位置,结果会悄悄出错。

如何在SQL中统计每个分类下最新更新的一条完整记录?

用窗口函数 ROW_NUMBER() 按分类和时间排序取首行

假设表名叫 articles,字段有 categorytitlecontentupdated_at。标准写法长这样:

SELECT category, title, content, updated_at
FROM (
  SELECT *,
         ROW_NUMBER() OVER (
           PARTITION BY category 
           ORDER BY updated_at DESC, id DESC
         ) AS rn
  FROM articles
) ranked
WHERE rn = 1;

这里有两个容易忽略的细节。一是 ORDER BY updated_at DESC 确保最新记录排在最前;二是额外加上 id DESC——如果同一分类下多条记录在同一秒更新,不指定 id 会导致每次查询结果不稳定,加个 id 排序就能避免这种“随机性”。

MySQL 5.7 或更老版本不支持窗口函数怎么办

老版本没有窗口函数,得用关联子查询或自连接,性能差一些,写法也绕。核心思路是:先对每个分类求出最大 updated_at,再连回原表匹配。推荐用 INNER JOIN + 子查询,相对易读:

SELECT a1.*
FROM articles a1
INNER 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 自连接,找“不存在更新时间更大的同分类记录”。哪种都好,关键是要意识到这个场景。

PostgreSQL 中 DISTINCT ON 是更简洁的替代方案

如果你是 PostgreSQL 用户,有更优雅的选项:

SELECT DISTINCT ON (category)
       category, title, content, updated_at
FROM articles
ORDER BY category, updated_at DESC, id DESC;

DISTINCT ON 的用法很直白:对每个 category 只保留一条记录,至于保留哪条,由 ORDER BY 中的后续字段决定。这里 updated_at DESC, id DESC 确保留下最新且 ID 最大的那条。相比窗口函数少一层嵌套,可读性更好。当然,这是 PG 专有语法,跨数据库迁移时要小心。

WHERE 条件不能直接写在窗口函数外层

一个经典错误:比如你想只统计 status = 'published' 的最新记录,结果把 WHERE status = 'published' 放在了最外层。这会导致先取每组最新,再过滤——如果某个分类的最新记录恰好不是 published,这个分类就会消失,而不是退而求其次选次新的 published 记录。逻辑就全歪了。

正确做法是把过滤条件放在子查询或 CTE 内部,确保排序和编号基于已筛选的数据集:

SELECT category, title, content, updated_at
FROM (
  SELECT *,
         ROW_NUMBER() OVER (
           PARTITION BY category 
           ORDER BY updated_at DESC, id DESC
         ) AS rn
  FROM articles
  WHERE status = 'published'  -- ✅ 在这里过滤
) ranked
WHERE rn = 1;

真正麻烦的不是语法本身,而是想清楚“最新”是针对全量数据还是某个子集。这个逻辑偏差,比写错函数名还容易引发线上问题。写 SQL 时多问自己一句:我要的“最新一条”,是在哪些数据里挑?

来源:https://www.php.cn/faq/2676408.html
上一篇SQL窗口函数实现多条件Top-K问题求解的实用方法 下一篇为什么在SQL分组聚合中HAVING子句必须与GROUP BY一起使用
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
如何在PostgreSQL 16中创建带安全限定符的SQL视图详细教程
数据库 · 2026-06-27

如何在PostgreSQL 16中创建带安全限定符的SQL视图详细教程

先说几个核心判断:PostgreSQL 16 的安全视图,不是靠某个内置参数或语法开关就能一劳永逸解决的。它需要一套组合拳来保障——权限、schema 隔离、行级策略,少一个都不行。 PostgreSQL 16 安全视图的“三重卡死”机制 PostgreSQL 16 本身并不支持带参数的视图。

SQL视图定义中为何不建议使用SELECT * 而应明确列名
数据库 · 2026-06-27

SQL视图定义中为何不建议使用SELECT * 而应明确列名

从语法层面来看,在SQL视图定义中使用SELECT *本身并不构成语法错误。然而,从数据库设计与架构优化的角度审视,这种做法几乎等同于主动放弃了对于输出结果集的精确掌控——视图一旦创建,其列名、列顺序以及列数量理应是明确且固定的,而*通配符却让这一切变成了运行时才揭晓的未知数。视图列结构会因底层表变

SQL Server GROUP BY非聚合列报错解决方法
数据库 · 2026-06-27

SQL Server GROUP BY非聚合列报错解决方法

SQL Server 对查询的模糊性零容忍,态度极为明确。一旦 SELECT 列表中包含非聚合列且该列未被 GROUP BY 子句引用,SQL Server 便会立即抛出“列名无效”错误,绝不妥协、猜测或回退。这种严格虽然让新手感到棘手,但也迫使开发者正视查询语义的边界。 然而,许多开发者在遭遇此错

利用SQL嵌套查询检查日期区间重叠有效性
数据库 · 2026-06-27

利用SQL嵌套查询检查日期区间重叠有效性

好的,我将以一位资深数据库专家的视角,对原文进行人性化重写,保留所有核心信息、逻辑结构与图片,同时去除AI腔调,让语言更自然、有节奏,并谨慎控制第一人称的使用。 --- 日期区间重叠检查,这事儿的坑比想象的多。写 SQL 时,很多人总想着先写个函数或者建个临时表来比对,其实没必要——直接上自连接加个

Oracle 12c RAC环境下RMAN恢复共享数据文件
数据库 · 2026-06-27

Oracle 12c RAC环境下RMAN恢复共享数据文件

在RAC环境下使用RMAN恢复共享数据文件,很多DBA第一次遇到时都会感到棘手:备份文件明明完整,执行RESTORE DATABASE却报ORA-01102或ORA-01507。别紧张,这并非命令错误,而是RAC的共享存储与多实例并发机制与RMAN恢复流程存在根本性的不兼容。 RMAN在RAC下无法