Skip to content
返回

SQL获取分组最新记录的四种姿势

嗨,各位奋斗在代码一线的开发者们!👋 你是否也曾遇到过这样的场景:

“老板,我需要从这张订单表里,查出每个用户的最后一笔订单金额。”

“产品经理,我要在用户动态列表里,只显示每个用户最新发布的那一条动态。” 这些需求翻译成 SQL 语言,本质上都是同一个问题:如何在一个分组中,取出按时间排序后的最新一条记录? 很多同学的第一反应可能是这样写:

SELECT sb_id, qyb
FROM yqhs_qyb
WHERE jyz_id = #{jyzId}
ORDER BY create_time DESC;

然后,在前端代码里循环处理,或者用 GROUP BY sb_id 试试?结果发现要么数据不对,要么语法报错。🤯 别慌!今天这篇文章,就带你彻底搞懂这个问题,并为你盘点四种解决方案,从青铜到王者,总有一款适合你!


场景设定

假设我们有一张表 yqhs_qyb,记录了不同设备(sb_id)在不同时间(create_time)的某个业务值(qyb)。我们的目标是:获取每个 sb_id 对应的最新 qyb 数据。 表结构简化如下:

idsb_idqybcreate_timejyz_id
1A1002023-10-01 10:00:00101
2B2002023-10-01 10:01:00101
3A1502023-10-01 10:05:00101
4C3002023-10-01 10:02:00101
5B2502023-10-01 10:06:00101
我们期望得到的结果是:
sb_idqyb
------------
A150
B250
C300

👑 王者方案:窗口函数 ROW_NUMBER()

这是目前最现代、最优雅、性能也通常最好的解决方案!如果你用的是 MySQL 8.0+、PostgreSQL、SQL Server、Oracle 等现代数据库,请把它作为你的首选! 💡 思路: 我们可以想象,先给每个 sb_id 分组内的数据按时间倒序排个名,最新的那条就是第 1 名。最后,我们只需要把所有“第 1 名”的记录挑出来就行了。 ✅ 代码实现:

WITH RankedData AS (
    SELECT
        sb_id,
        qyb,
        -- 核心:为每个 sb_id 分组内的数据按创建时间倒序排名
        ROW_NUMBER() OVER(PARTITION BY sb_id ORDER BY create_time DESC) as rn
    FROM
        yqhs_qyb
    WHERE
        jyz_id = #{jyzId}
)
SELECT
    sb_id,
    qyb
FROM
    RankedData
WHERE
    rn = 1; -- 筛选出每组的第一名

👍 优点:


🥈 经典方案:子查询 JOIN

这个方法非常经典,兼容性极好,尤其适合还在使用 MySQL 5.7 等旧版本数据库的同学。 💡 思路: 分两步走:

  1. 先用 GROUP BY 找到每个 sb_id 对应的最新时间点 (MAX(create_time))。
  2. 然后将原表和这个“最新时间点”的结果集进行关联,找出时间点匹配的完整记录。 ✅ 代码实现:
SELECT
    t1.sb_id,
    t1.qyb
FROM
    yqhs_qyb t1
INNER JOIN (
    -- 子查询:找出每个 sb_id 的最新 create_time
    SELECT
        sb_id,
        MAX(create_time) AS max_create_time
    FROM
        yqhs_qyb
    WHERE
        jyz_id = #{jyzId}
    GROUP BY
        sb_id
) t2 ON t1.sb_id = t2.sb_id AND t1.create_time = t2.max_create_time
WHERE
    t1.jyz_id = #{jyzId};

👍 优点:


🥉 专属福利:PostgreSQL DISTINCT ON

如果你是 PostgreSQL 用户,那么恭喜你!你拥有最简洁的“专属武器”。 💡 思路: DISTINCT ON (sb_id) 语法会为每个 sb_id 只保留一行,具体保留哪一行,由 ORDER BY 子句决定。 ✅ 代码实现:

SELECT DISTINCT ON (sb_id)
    sb_id,
    qyb
FROM
    yqhs_qyb
WHERE
    jyz_id = #{jyzId}
ORDER BY
    sb_id, create_time DESC; -- 注意:DISTINCT ON 的列必须在 ORDER BY 的最前面

👍 优点:


🚫 青铜方案:相关子查询

最后介绍一个能实现但不推荐的方案,了解一下即可,避免在性能敏感的场景中使用。 💡 思路: 对于表中的每一条记录,都去问一个问题:“是否存在另一条同 sb_idcreate_time 比我更新的记录?” 如果答案是否定的,那我就是最新的。 ✅ 代码实现:

SELECT
    t1.sb_id,
    t1.qyb
FROM
    yqhs_qyb t1
WHERE
    t1.jyz_id = #{jyzId}
    AND NOT EXISTS (
        SELECT 1
        FROM yqhs_qyb t2
        WHERE t2.sb_id = t1.sb_id
          AND t2.jyz_id = #{jyzId}
          AND t2.create_time > t1.create_time
    );

👎 缺点:


总结与划重点

方案适用场景推荐度
窗口函数MySQL 8.0+ / PG / SQL Server / Oracle⭐⭐⭐⭐⭐ (首选)
子查询 JOINMySQL 5.7 等旧版数据库⭐⭐⭐⭐ (备选)
PG DISTINCT ONPostgreSQL 用户⭐⭐⭐⭐⭐ (PG首选)
相关子查询了解即可,避免使用

一句话总结:

能用窗口函数就用窗口函数,旧版 MySQL 用 JOIN,PG 用户用 DISTINCT ON,相关子查询快忘掉!

希望这篇文章能帮你解决日常开发中的这个小烦恼,写出更高效、更优雅的 SQL!🚀

觉得有用?那就 点赞 👍、在看 👀、转发 🔄 就是对我们最大的支持! 关注 公众号-秋雨 ,解锁更多硬核技术干货!



Share this post on:

Previous Post
MCP服务器连接MySQL数据库配置指南
Next Post
AviatorScript支持and和or关键字