8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

检索每组中的最后一条记录 - MySQL

Ben Trewern 3月前

232 0

有一个表 messages 包含如下所示的数据:Id Name Other_Columns-------------------------1 A A_data_12 A A_data_23 A A_data_34 B B_da...

有一个表 messages ,包含如下所示的数据:

Id   Name   Other_Columns
-------------------------
1    A       A_data_1
2    A       A_data_2
3    A       A_data_3
4    B       B_data_1
5    B       B_data_2
6    C       C_data_1

如果我运行查询 select * from messages group by name ,我将获得以下结果:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

什么查询将返回以下结果?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

也就是说,应该返回每组中的最后一条记录。

目前,这是我使用的查询:

SELECT
  *
FROM (SELECT
  *
FROM messages
ORDER BY id DESC) AS x
GROUP BY name

但这看起来效率很低。还有其他方法可以达到同样的效果吗?

帖子版权声明 1、本帖标题:检索每组中的最后一条记录 - MySQL
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Ben Trewern在本站《database》版块原创发布, 转载请注明出处!
最新回复 (0)
  • @KimPrince 看来你建议的答案并没有达到预期的效果!我刚刚尝试了你的方法,它取了每组的第一行并按降序排列。它没有取每组的最后一行

  • dany 3月前 0 只看Ta
    引用 3

    MySQL 8.0 现在支持 窗口函数 ,就像几乎所有流行的 SQL 实现一样。使用此标准语法,我们可以编写 greatest-n-per-group 查询:

    WITH ranked_messages AS (
      SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
      FROM messages AS m
    )
    SELECT * FROM ranked_messages WHERE rn = 1;
    

    MySQL 手册中说明了 这种方法以及其他查找 分组最大行的

    以下是我在 2009 年针对这个问题写的原始答案:


    我这样写解决方案:

    SELECT m1.*
    FROM messages m1 LEFT JOIN messages m2
     ON (m1.name = m2.name AND m1.id < m2.id)
    WHERE m2.id IS NULL;
    

    至于性能,根据数据的性质,两种解决方案可能都更好。因此,您应该测试这两种查询,并使用性能更佳的查询。

    例如,我有一份 StackOverflow 8 月数据转储 。我将使用它进行基准测试。表中有 1,114,357 行 Posts 。这是 在我的 Macbook Pro 2.40GHz 上的 MySQL

    我将编写一个查询来查找给定用户 ID(我的)的最新帖子。

    首先使用@Eric 展示的技术以及子查询中的 GROUP BY:

    SELECT p1.postid
    FROM Posts p1
    INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
                FROM Posts pi GROUP BY pi.owneruserid) p2
      ON (p1.postid = p2.maxpostid)
    WHERE p1.owneruserid = 20860;
    
    1 row in set (1 min 17.89 sec)
    

    即使 EXPLAIN analysis 需要超过 16 秒:

    +----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
    | id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
    +----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
    |  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
    |  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
    |  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
    +----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
    3 rows in set (16.09 sec)
    

    现在使用我的技术和 LEFT JOIN 产生相同的查询结果:

    SELECT p1.postid
    FROM Posts p1 LEFT JOIN posts p2
      ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
    WHERE p2.postid IS NULL AND p1.owneruserid = 20860;
    
    1 row in set (0.28 sec)
    

    分析 EXPLAIN 表明两个表都可以使用其索引:

    +----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
    | id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
    +----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
    |  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
    |  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
    +----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
    2 rows in set (0.00 sec)
    

    这是我的表的 DDL Posts

    CREATE TABLE `posts` (
      `PostId` bigint(20) unsigned NOT NULL auto_increment,
      `PostTypeId` bigint(20) unsigned NOT NULL,
      `AcceptedAnswerId` bigint(20) unsigned default NULL,
      `ParentId` bigint(20) unsigned default NULL,
      `CreationDate` datetime NOT NULL,
      `Score` int(11) NOT NULL default '0',
      `ViewCount` int(11) NOT NULL default '0',
      `Body` text NOT NULL,
      `OwnerUserId` bigint(20) unsigned NOT NULL,
      `OwnerDisplayName` varchar(40) default NULL,
      `LastEditorUserId` bigint(20) unsigned default NULL,
      `LastEditDate` datetime default NULL,
      `LastActivityDate` datetime default NULL,
      `Title` varchar(250) NOT NULL default '',
      `Tags` varchar(150) NOT NULL default '',
      `AnswerCount` int(11) NOT NULL default '0',
      `CommentCount` int(11) NOT NULL default '0',
      `FavoriteCount` int(11) NOT NULL default '0',
      `ClosedDate` datetime default NULL,
      PRIMARY KEY  (`PostId`),
      UNIQUE KEY `PostId` (`PostId`),
      KEY `PostTypeId` (`PostTypeId`),
      KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
      KEY `OwnerUserId` (`OwnerUserId`),
      KEY `LastEditorUserId` (`LastEditorUserId`),
      KEY `ParentId` (`ParentId`),
      CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
    ) ENGINE=InnoDB;
    

    评论者请注意:如果您想要使用不同版本的 MySQL、不同的数据集或不同的表设计进行另一个基准测试,请随意自己动手​​。我已经展示了上面的技术。Stack Overflow 是向您展示如何进行软件开发工作的,而不是为您完成所有工作。

  • 真的吗?如果您有大量条目会发生什么?例如,如果您使用内部版本控制,并且每个文件有大量版本,则连接结果将非常庞大。您是否曾用这种方法对子查询方法进行过基准测试?我很好奇想知道哪个会获胜,但好奇心还没有强到不先问你。

  • 您能否详细说明一下条件“WHERE p2.postid IS NULL”的目的?它不会与另一个条件“p1.postid < p2.postid”相矛盾吗?

  • @KatherineChen,这与 LEFT [OUTER] JOIN 的工作方式有关。如果该连接未找到 m1 中给定行的匹配项,则它仍将返回该行 m1,但 m2 的所有列都将为 NULL。

  • 谢谢你的解释!因此,这将导致它匹配独立记录(m1.name = m2.name 且没有与相同名称关联的其他 id)或给定相同名称的最大 id(m1.name = m2.name 且 m1.id > m2.id)。

  • @ysth 我希望 Stack Overflow 的目的是向读者展示技术,这样他们就可以自己做更多的工作。目标不是为他们做所有的工作。

  • 更新:2017-03-31,MySQL 5.7.5 版本默认启用了 ONLY_FULL_GROUP_BY 开关(因此,非确定性 GROUP BY 查询被禁用)。此外,他们更新了 GROUP BY 实现,即使禁用开关,解决方案也可能无法再按预期工作。需要检查一下。

    当组内项目数较少时,上述 Bill Karwin 的解决方案效果很好,但当组很大时,查询的性能就会变差,因为该解决方案 n*n/2 + n/2 只需要大约的 IS NULL 比较。

    包含 18684446 的行的 InnoDB 表上进行了测试 1182 。该表包含功能测试的测试结果,并以 (test_id, request_id) 作为主键。因此, test_id 每个组的 request_id 最后一个 test_id .

    Bill 的解决方案已经在我的 dell e4310 上运行了几个小时,我不知道它什么时候才能完成,尽管它是在覆盖索引上运行(因此 using index 在 EXPLAIN 中)。

    我还有一些基于相同想法的其他解决方案:

    • 如果基础索引是 BTREE 索引(通常是这种情况),则最大 (group_id, item_value) 对是每个对中的最后一个值 group_id 如果我们按降序遍历索引, group_id 则最大对是每个对中的第一个值
    • 如果我们读取索引涵盖的值,则按照索引的顺序读取值;
    • 每个索引都隐式地包含附加到其上的主键列(即主键在覆盖索引中)。在下面的解决方案中,我直接对主键进行操作,对于您来说,您只需要在结果中添加主键列即可。
    • 在许多情况下,在子查询中按所需顺序收集所需的行 ID 并将子查询的结果连接到 ID 上要便宜得多。由于对于子查询结果中的每一行,MySQL 都需要基于主键进行一次提取,因此子查询将放在连接的首位,并且行将按照子查询中的 ID 顺序输出(如果我们省略连接的显式 ORDER BY)

    《MySQL 使用索引的 3 种方式》 是一篇了解一些细节的好文章。

    解决方案 1

    这个速度非常快,在我的18M+行上大约需要0.8秒:

    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC;
    

    如果要将顺序更改为 ASC,请将其放在子查询中,仅返回 id,然后使用它作为子查询来加入其余列:

    SELECT test_id, request_id
    FROM (
        SELECT test_id, MAX(request_id) AS request_id
        FROM testresults
        GROUP BY test_id DESC) as ids
    ORDER BY test_id;
    

    这对我的数据大约需要1.2秒。

    解决方案 2

    这是另一个解决方案,对于我的表来说大约需要 19 秒:

    SELECT test_id, request_id
    FROM testresults, (SELECT @group:=NULL) as init
    WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
    ORDER BY test_id DESC, request_id DESC
    

    它也以降序返回测试。由于它执行的是全索引扫描,所以速度要慢得多,但它可以让你了解如何为每个组输出 N 最大行。

    该查询的缺点是其结果不能被查询缓存所缓存。

  • 使用 子查询 返回正确的分组,因为您已经完成一半了。

    尝试这个:

    select
        a.*
    from
        messages a
        inner join 
            (select name, max(id) as maxid from messages group by name) as b on
            a.id = b.maxid
    

    如果不是, id 您想要的最大值是:

    select
        a.*
    from
        messages a
        inner join 
            (select name, max(other_col) as other_col 
             from messages group by name) as b on
            a.name = b.name
            and a.other_col = b.other_col
    

    这样,您可以避免相关子查询和/或子查询中的排序,这往往非常缓慢/低效。

  • 引用 11

    只要 max(id) 在组之间是唯一的,这种方法就有效。否则,您可以通过组标识符添加额外的连接条件,如下所示。com/a/7745635/3163618 tests sqlfiddle.com/#!9/f13270/2/0

  • 引用 12

    我得出了一个不同的解决方案,即获取每个组内最后一篇文章的 ID,然后使用第一个查询的结果作为构造的参数从消息表中进行选择 WHERE x IN

    SELECT id, name, other_columns
    FROM messages
    WHERE id IN (
        SELECT MAX(id)
        FROM messages
        GROUP BY name
    );
    

    我不知道与其他一些解决方案相比它的表现如何,但它对我的有 300 多万行的表来说效果非常好。(执行 4 秒,结果超过 1200 条)

    这应该在 MySQL 和 SQL Server 上都有效。

  • 此解决方案会导致 mysql 服务器/服务崩溃。我已经用 1000 万条记录进行了检查,不推荐此解决方案。在这种情况下使用 IN 是最糟糕的。

  • @Kamlesh 也许您缺少一些索引?此外,此解决方案已有近 10 年历史,也许某些更新已经改变了此查询的行为或性能。

  • 漂亮而优雅的解决方案。只需稍加改进,即使使用非univoque排序属性也可以工作。SELECT not_univoque_id, name, other_columns FROM messages WHERE (name, not_univoque_id) IN (SELECT name, MAX(not_univoque_id) FROM messages GROUP BY name );

  • G M 3月前 0 只看Ta
    引用 16

    好的答案,这个链接就像你的答案thispointer.com/retrieving-the-last-record-in-each-group-mysql

  • 通过子查询 小提琴解决 Link

    select * from messages where id in
    (select max(id) from messages group by Name)
    

    解决方案通过连接条件 小提琴链接

    select m1.* from messages m1 
    left outer join messages m2 
    on ( m1.id<m2.id and m1.name=m2.name )
    where m2.id is null
    

    这篇文章的原因只是提供小提琴链接。其他答案中已经提供了相同的 SQL。

  • 我们将了解如何使用 MySQL 获取记录组中的最后一条记录。例如,如果您有此帖子结果集。

    ID 类别ID 帖子标题
    1 1 标题 1
    2 1 标题 2
    3 1 标题 3
    4 2 标题 4
    5 2 标题 5
    6 3 标题 6

    我希望能够获取每个类别的最后一篇文章,即标题 3、标题 5 和标题 6。要按类别获取文章,您将使用 MySQL Group By 键盘。

    select * from posts group by category_id
    

    但我们从这个查询得到的结果是。

    ID 类别ID 帖子标题
    1 1 标题 1
    4 2 标题 4
    6 3 标题 6

    分组依据将始终返回结果集上组中的第一个记录。

    SELECT id, category_id, post_title
    FROM posts
    WHERE id IN (
        SELECT MAX(id)
        FROM posts
        GROUP BY category_id );
    

    这将返回每个组中 ID 最高的帖子。

    ID 类别ID 帖子标题
    3 1 标题 3
    5 2 标题 5
    6 3 标题 6

    参考点此

  • 一种速度相当快的方法如下。

    SELECT * 
    FROM messages a
    WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)
    

    结果

    Id  Name    Other_Columns
    3   A   A_data_3
    5   B   B_data_2
    6   C   C_data_1
    
  • 这甚至可以与 Firebird 1.0.3 一起使用!...并且似乎比 .com/a/9368897/2932052 更快

返回
作者最近主题: