SQL 如何实现“去重后取最新 N 条”且保持插入顺序

发布时间 - 2026-01-30 00:00:00    点击率:
用ROW_NUMBER()按时间倒序分组去重的核心是:先按user_id分组、created_at降序排序并编号,再筛选rn=1的最新记录;若取最新N条需两层嵌套,外层必须显式ORDER BY created_at DESC;MySQL 5.7以下需用NOT EXISTS子查询并建联合索引;无可靠时间或ID时应改造表结构。

ROW_NUMBER() 按时间倒序分组去重

核心思路是:先按业务主键(如 user_id)分组,再按时间字段(如 created_at)降序排序,给每组最新记录标上 1,最后筛出所有 rn = 1 的行。这能保证“每个用户只留一条,且是最新那条”。

常见错误是直接 GROUP BY + MAX(created_at),但这样拿不到整行数据(比如用户名、状态等其他字段会丢失或随机)。

  • 必须用窗口函数,不能用聚合函数直接取整行
  • ORDER BY created_at DESC 是关键,升序就会取到最老的记录
  • 如果时间字段有重复,建议追加 id DESC 做二级排序,避免结果不稳定
SELECT user_id, name, stat

us, created_at FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY created_at DESC, id DESC ) AS rn FROM logs ) t WHERE rn = 1;

取“去重后最新 N 条”要两层嵌套

很多人以为加个 LIMIT N 就完事,但那是先 LIMIT 再去重,逻辑完全反了。正确做法是:第一层去重得最新记录 → 第二层对这些去重结果再按时间倒序取前 N 条。

注意:第二层的 ORDER BY 必须显式写,否则数据库不保证返回顺序(即使第一层排过序,外层也不继承)。

  • 内层负责“每个 key 留最新一条”
  • 外层负责“从所有最新记录里取时间最近的 N 条”
  • 外层 ORDER BY created_at DESC 不可省,尤其在 PostgreSQL / MySQL 8.0+ 中
SELECT user_id, name, status, created_at
FROM (
  SELECT user_id, name, status, created_at,
         ROW_NUMBER() OVER (
           PARTITION BY user_id 
           ORDER BY created_at DESC, id DESC
         ) AS rn
  FROM logs
) t
WHERE rn = 1
ORDER BY created_at DESC
LIMIT 5;

MySQL 5.7 或更老版本不能用窗口函数怎么办

只能靠相关子查询或自连接模拟,性能差、写法绕,而且容易漏数据或误删。典型写法是:对每条记录,查同 user_id 下有没有更新的记录;如果没有,说明它就是最新的。

这种写法在数据量稍大(比如 >10 万行)时就明显变慢,且 created_at 字段必须有索引,否则全表扫描。

  • 子查询中 WHERE l2.created_at > l1.created_at 要注意 NULL 安全,建议 created_at 设为 NOT NULL
  • 务必给 (user_id, created_at) 建联合索引,否则性能雪崩
  • 如果存在多条同时间记录,此方法可能保留多条,不如窗口函数精确
SELECT l1.user_id, l1.name, l1.status, l1.created_at
FROM logs l1
WHERE NOT EXISTS (
  SELECT 1 FROM logs l2
  WHERE l2.user_id = l1.user_id
    AND l2.created_at > l1.created_at
)
ORDER BY l1.created_at DESC
LIMIT 5;

“保持插入顺序”本质是依赖 idauto_increment 字段

很多场景说的“最新”,其实不是指 created_at 时间,而是指最后插入的那条(比如日志没打时间戳,或时间不准)。这时应优先用主键 id 判断新旧,它天然有序、无重复、不为空。

但要注意:如果用的是 UUID 或分布式 ID(如雪花 ID),就不能简单比大小——雪花 ID 虽含时间成分,但高位是机器号,直接 ORDER BY id DESC 可能错乱;此时仍应回归真实时间字段。

  • 本地单库 + 自增主键 → 直接 ORDER BY id DESC 最稳
  • 用了 created_at DEFAULT CURRENT_TIMESTAMP → 通常够用,但需确认应用没手动覆盖该字段
  • 批量导入或时钟不同步时,id 比时间更可靠

真正难处理的是“既没可靠时间字段,又没单调递增 ID”的表——这时候得改表结构,否则任何“取最新”的逻辑都是赌概率。


# mysql  # 聚合函数  # sql  # 分布式  # NULL  # 继承  # default  # postgresql  # 数据库  # 的是  # 是指  # 主键  # 标上  # 不能用  # 那条  # 两层  # 多条  # 再按  # 第二层 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?  如何在橙子建站上传落地页?操作指南详解  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制  如何在新浪SAE免费搭建个人博客?  Android仿QQ列表左滑删除操作  如何利用DOS批处理实现定时关机操作详解  如何在IIS中新建站点并配置端口与物理路径?  青岛网站建设如何选择本地服务器?  企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?  如何为不同团队 ID 动态生成多个非值班状态按钮  如何在Tomcat中配置并部署网站项目?  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤  Laravel如何处理文件下载请求?(Response示例)  laravel怎么为应用开启和关闭维护模式_laravel应用维护模式开启与关闭方法  微信h5制作网站有哪些,免费微信H5页面制作工具?  Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】  Python文件异常处理策略_健壮性说明【指导】  高防服务器:AI智能防御DDoS攻击与数据安全保障  如何在IIS中新建站点并配置端口与IP地址?  Laravel如何使用Gate和Policy进行授权?(权限控制)  千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】  JS中对数组元素进行增删改移的方法总结  Laravel如何实现模型的全局作用域?(Global Scope示例)  Bootstrap CSS布局之列表  Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程  中国移动官方网站首页入口 中国移动官网网页登录  郑州企业网站制作公司,郑州招聘网站有哪些?  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  如何生成腾讯云建站专用兑换码?  Microsoft Edge如何解决网页加载问题 Edge浏览器加载问题修复  laravel怎么使用数据库工厂(Factory)生成带有关联模型的数据_laravel Factory生成关联数据方法  微信小程序 配置文件详细介绍  大连 网站制作,大连天途有线官网?  网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?  标准网站视频模板制作软件,现在有哪个网站的视频编辑素材最齐全的,背景音乐、音效等?  JavaScript模板引擎Template.js使用详解  如何为不同团队 ID 动态生成多个独立按钮  HTML 中如何正确使用模板变量为元素的 name 属性赋值  Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?  高配服务器限时抢购:企业级配置与回收服务一站式优惠方案  如何破解联通资金短缺导致的基站建设难题?  怎样使用JSON进行数据交换_它有什么限制  Laravel如何生成API文档?(Swagger/OpenAPI教程)  Laravel怎么判断请求类型_Laravel Request isMethod用法  Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用  JavaScript如何实现音频处理_Web Audio API如何工作?