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, status, 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;
“保持插入顺序”本质是依赖 id 或 auto_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如何工作?


