当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 窗口函数怎样取每组最新一条

MySQL 窗口函数怎样取每组最新一条

来源:17golang原创 2026-10-14 08:48:35 0浏览 收藏

MySQL 用窗口函数取每组最新一条,常用写法是 ROW_NUMBER() OVER (PARTITION BY 分组字段 ORDER BY 时间 DESC, 唯一键 DESC),再在外层查询筛选 rn=1。时间倒序决定“最新”,唯一键负责在时间相同的时候稳定选一条。窗口函数保留原行,因此可以返回状态、金额等完整字段,不必把 MAX(时间) 和不确定的普通字段拼在一起。

以下以 MySQL 8.4 官方文档为基线,示例未运行,不包含实测执行计划或性能数据。读者需要在支持窗口函数的 MySQL 环境使用,不能直接把该语法套到 MySQL 5.7。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/window-functions-usage.html

把最新定义成可以排序的规则

设想设备事件记录,每行含 id、device_id、occurred_at 和 status。要求是“每台设备取最近发生的事件”,分组字段就是 device_id,时间字段就是 occurred_at;如果时间并列,本例约定取 id 较大的一行。id 必须在候选集中唯一。这个约定只是并列裁决,不能把自增 id 直接解释成事件发生时间,补录数据可能打乱两者关系。

这里有三条独立规则:分组决定哪些行互相比较,排序决定谁排在组内首位,外层条件决定保留哪些编号。若按租户与设备共同识别对象,PARTITION BY 应包含 tenant_id、device_id;否则不同租户的同名设备可能被混在一起。

不要只在最终 SELECT 末尾加 ORDER BY occurred_at DESC。最终排序影响展示顺序,不负责告诉窗口函数如何分配组内编号。窗口内必须有自己的 ORDER BY;排序字段仍然相同的行会获得不同 ROW_NUMBER,但被选中的那一行可能不稳定。

事件记录与分组排序编号的结构关系
静态结构说明图:device_id 绑定分组,occurred_at 和 id 绑定排序,rn 绑定组内编号;不是查询结果截图。

内层编号,外层选出完整记录

下面用 CTE 构造少量示例记录,无需建立真实业务表。设备 31 有两个并列时间,设备 32 的最新事件是失败状态。先保留全部候选行,并在 ranked 中加入编号,再在最外层选首行。

WITH events AS (
    -- 教学数据:同一设备可以有相同时间,id 用于并列裁决。
    SELECT 501 AS id, 31 AS device_id,
           CAST('2026-09-10 09:00:00' AS DATETIME) AS occurred_at,
           'success' AS status
    UNION ALL
    SELECT 502, 31, CAST('2026-09-10 10:00:00' AS DATETIME), 'success'
    UNION ALL
    SELECT 503, 31, CAST('2026-09-10 10:00:00' AS DATETIME), 'failed'
    UNION ALL
    SELECT 601, 32, CAST('2026-09-10 08:00:00' AS DATETIME), 'success'
    UNION ALL
    SELECT 602, 32, CAST('2026-09-10 11:00:00' AS DATETIME), 'failed'
), ranked AS (
    SELECT id, device_id, occurred_at, status,
           ROW_NUMBER() OVER (
               PARTITION BY device_id
               ORDER BY occurred_at DESC, id DESC
           ) AS rn
    FROM events
)
-- rn 是内层结果列,在外层筛选;最后排序只控制展示。
SELECT id, device_id, occurred_at, status
FROM ranked
WHERE rn = 1
ORDER BY device_id;

按规则推导,设备 31 应取 503,设备 32 应取 602。503 与 502 时间相同,但 503 的 id 更大;602 的发生时间更晚,状态失败不影响这里的“最新”定义。这是预期结果说明,而不是已经执行的输出。

实际使用时,把 events 换成业务表即可。窗口函数只能出现在 SELECT 列表和 ORDER BY 等允许的位置,不能在同层 WHERE 中直接写 ROW_NUMBER() OVER (...) = 1,也不能把同层刚定义的 rn 当成可用的普通列。CTE 或派生表提供了清晰的查询层边界。

筛选条件的位置会改变问题

“每组最新成功记录”和“每组最新记录是否成功”不是同一个需求。前者应先限制候选集合为 success,再编号;后者要对所有事件编号,选出最新行后再检查 status。以刚才的样本为例,前者返回 502 和 601,后者不返回任何行,因为两台设备的最新事件都是 failed。

下面假设已有 device_events 表,字段与示例一致。这两段 SQL 不创建表,也不修改数据。

-- 需求一:只在成功候选中比较,取每台设备最近一次成功。
WITH ranked AS (
    SELECT id, device_id, occurred_at, status,
           ROW_NUMBER() OVER (
               PARTITION BY device_id
               ORDER BY occurred_at DESC, id DESC
           ) AS rn
    FROM device_events
    WHERE status = 'success'
)
SELECT id, device_id, occurred_at, status
FROM ranked
WHERE rn = 1;

-- 需求二:先对全部事件排名,再只保留最新事件成功的设备。
WITH ranked AS (
    SELECT id, device_id, occurred_at, status,
           ROW_NUMBER() OVER (
               PARTITION BY device_id
               ORDER BY occurred_at DESC, id DESC
           ) AS rn
    FROM device_events
)
SELECT id, device_id, occurred_at, status
FROM ranked
WHERE rn = 1 AND status = 'success';

时间区间也一样:内层限制最近七天,就是在最近七天的候选中取最新,而不是无条件取全历史最新。若没有七天内事件,该设备不会出现。不要为了减少扫描而把任意条件移动到内层;它会改变参与排名的数据范围。

候选集合过滤和首行结果过滤的边界
静态结构说明图:内层状态条件约束候选集合,外层状态条件约束首行结果;两种语义对应不同问题,不是执行时间轴。

把并列规则和性能边界写清楚

只要每组恰好选一条,用 ROW_NUMBER 并提供唯一的排序组合。如果要保留所有最大时间并列记录,可改用 RANK 或 DENSE_RANK,窗口只按 occurred_at DESC 排序,然后在外层取排名 1。此时不要加入唯一 id 作为排名排序,否则原本的时间并列会被拆开。对于首名并列,RANK 和 DENSE_RANK 都能保留并列行;它们的差别主要体现在后续名次是否留空档。

有复合索引也不能承诺整个窗口查询无需排序。读者可评估 (device_id, occurred_at DESC, id DESC) 这类与分组及排序对应的索引,同时考虑实际内层过滤条件、读取列与写入成本。是否选用、是否减少排序,仍由执行计划和数据分布决定,不应该把索引定义当成性能保证。

MySQL 8.4 文档说明,包含窗口函数的派生子查询不会按普通方式合并,而会物化。大候选集仍可能带来排序及临时结果开销。先用语义正确的条件缩小候选范围,明确选择必要列,再由读者在自己的环境观察计划。优化细节可查 https://dev.mysql.com/doc/refman/8.4/en/window-function-optimization.html。

上线前用边界样本核对结果

建议读者准备这些样本:每组单行、不同时间多行、并列时间、全部失败、旧成功新失败、指定时间范围内没有记录。检查的不只是行数,还应检查返回的 id、状态和时间是否属于同一条完整记录。这份样本清单用于读者自己的回归,本文没有执行数据库验证。

需求关键设置
每组恰好一条最新行ROW_NUMBER + 时间及唯一键排序 + 外层 rn=1
最新的成功行内层过滤成功,再排名
最新行必须成功全部候选排名,外层过滤成功
保留最大时间并列行RANK 或 DENSE_RANK,只按时间排名
结果显示顺序稳定最外层显式 ORDER BY

没有事件的设备会出现吗?

不会。事件表中没有候选行,就没有对应分组。若设备主表也要完整展示,应从设备主表 LEFT JOIN 已经选好的每组首行结果,并把首行条件放在结果子查询或连接条件中,避免在外层 WHERE 排除未匹配行。

时间允许 NULL 怎么处理?

先定义业务含义。若未知时间不能参加“最新事件”比较,可在内层排除 occurred_at IS NULL;这会使全部时间未知的分组消失。不要随意把 NULL 替换成当前时间,那会把未知记录误标成最新。

加 LIMIT 能得到每组一条吗?

单独 LIMIT 1 只限制整个结果,不负责分组。应先筛选 rn=1,再按最终展示顺序分页;并发写入时跨页结果还可能变化,稳定排序不能替代一致的数据快照。

记住分组、排序、候选过滤与结果过滤这四项,ROW_NUMBER 的写法就不难。函数行为说明:https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL JSON_TABLE 展开嵌套数组的列定义与空值处理MySQL JSON_TABLE 展开嵌套数组的列定义与空值处理
上一篇
MySQL JSON_TABLE 展开嵌套数组的列定义与空值处理
MySQL JSON_TABLE 的 EXISTS PATH 怎么区分字段缺失和 JSON null
下一篇
MySQL JSON_TABLE 的 EXISTS PATH 怎么区分字段缺失和 JSON null
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    466次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    528次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    546次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    497次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    316次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码