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

Retrieving the last record in each group - MySQL

提问人:Vijay Dev 提问时间:8/22/2009 最后编辑:O. JonesVijay Dev 更新时间:8/15/2023 访问量:1133202

问:

有一个表,其中包含如下所示的数据: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

但这看起来效率非常低下。还有其他方法可以达到相同的结果吗?

mysql sql group-by greatest-n-per-group groupwise-maximum

评论

4赞 o17t H1H' S'k 6/25/2012
请参阅 stackoverflow.com/questions/1379565/ 中公认的答案,以获得更有效的解决方案
2赞 Tomas 6/15/2013
stackoverflow.com/q/121387/684229 的副本
12赞 DatsunBing 12/3/2015
为什么不能只添加 DESC,即从消息组中按名称 DESC 选择 *
0赞 Ciro Santilli OurBigBook.com 6/13/2016
如何在 SQL 中通过另一列选择具有 MAX(Column value)、DISTINCT 的行的可能重复?
5赞 Ayrat 5/22/2017
@KimPrince 您建议的答案似乎没有达到预期!我刚刚尝试了您的方法,它为每组占据了第一行并订购了 DESC。它不占用每个组的最后一行

答:

135赞 Eric 8/22/2009 #1

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

试试这个:

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

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

评论

0赞 qwr 6/10/2023
只要 max(id) 在组之间是唯一的,这就有效。否则,您可以按组标识符添加其他联接条件,如下所示 stackoverflow.com/a/7745635/3163618 测试 sqlfiddle.com/#!9/f13270/2/0
13赞 Steve Kass 8/22/2009 #2

这里有两个建议。首先,如果 mysql 支持 ROW_NUMBER(),则非常简单:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

我假设“最后”是指 Id 顺序中的最后一个。如果没有,请相应地更改 ROW_NUMBER() 窗口的 ORDER BY 子句。如果 ROW_NUMBER() 不可用,这是另一种解决方案:

其次,如果没有,这通常是一个很好的方法:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

换言之,选择没有具有相同名称的 later-Id 消息的邮件。

评论

8赞 Bill Karwin 8/22/2009
MySQL 不支持 ROW_NUMBER() 或 CTE。
3赞 Rick James 1/28/2020
MySQL 8.0(和 MariaDB 10.2)现在支持 CTE。ROW_NUMBER()
1赞 Wolf 12/9/2021
也许通过使用两个别名( 和 ),可以提高可读性,如下所示abSELECT * FROM messages a WHERE NOT EXISTS (SELECT * FROM messages as b WHERE a.Name = b.Name AND a.Id > b.Id)
0赞 akostadinov 4/20/2022
第一个版本如何更好?第二个非常清晰和快速。
1385赞 Bill Karwin 8/22/2009 #3

MySQL 8.0 现在支持窗口函数,就像几乎所有流行的 SQL 实现一样。使用这种标准语法,我们可以编写每组最大的 n 个查询:

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 行。这在我的 Macbook Pro 5.0.75GHz 上的 MySQL 2.40 上运行。Posts

我将编写一个查询来查找给定用户 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 分析也需要 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 在这里向您展示如何进行软件开发工作,而不是为您完成所有工作。

评论

12赞 Eric 8/22/2009
真?如果您有大量条目会怎样?例如,如果你正在使用内部版本控制,并且每个文件有大量的版本,那么联接结果将是巨大的。你有没有用这个对子查询方法进行基准测试?我很好奇哪个会赢,但好奇心不够,不能先问你。
2赞 Katherine Chen 7/25/2021
您能否详细说明条件“WHERE p2.postid IS NULL”的目的?它不会与另一个条件“p1.postid < p2.postid”相矛盾吗?
1赞 Bill Karwin 7/26/2021
@KatherineChen,这与工作方式有关。如果该连接在 中没有找到给定行的匹配项,则它仍将返回该行,但 的所有列都将为 NULL。LEFT [OUTER] JOINm1m1m2
1赞 Katherine Chen 7/27/2021
谢谢你的解释!因此,这将导致它匹配独立记录(m1.name = m2.name,并且没有与相同名称关联的其他 ID)或具有相同名称的最大 ID(m1.name = m2.name 和 m1.id > m2.id)。
3赞 Bill Karwin 7/30/2021
@ysth我希望 Stack Overflow 的重点是为读者演示技术,这样他们就可以自己做更多的工作。目标不是为他们做所有的工作。
4赞 Pro Web Design 7/15/2011 #4

试试这个:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  
3赞 Teja 11/19/2011 #5

根据您的问题,以下查询将正常工作。

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;
179赞 newtover 1/6/2012 #6

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

当组中的项目计数相当小时,Bill Karwin 上面的解决方案工作正常,但是当组相当大时,查询的性能会变差,因为该解决方案只需要比较。n*n/2 + n/2IS NULL

我在带有组的 InnoDB 行表上进行了测试。该表包含功能测试的测试结果,并将 作为主键。因此,是一个组,我正在为每个组寻找最后一个.186844461182(test_id, request_id)test_idrequest_idtest_id

Bill 的解决方案已经在我的戴尔 e4310 上运行了几个小时,我不知道它何时会完成,即使它在覆盖率指数上运行(因此在 EXPLAIN 中)。using index

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

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

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

解决方案 1

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

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 个最大行。

查询的缺点是查询缓存无法缓存其结果。

107赞 JYelton 2/21/2012 #7

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

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

我不知道与其他一些解决方案相比,它的表现如何,但它对我拥有 3+ 百万行的表非常有效。(4秒执行,1200+结果)

这应该适用于 MySQL 和 SQL Server。

评论

0赞 Kamlesh 9/17/2021
此解决方案使mysql服务器/服务崩溃。我已经用 1000 万条记录检查了它,不推荐这个解决方案。在这种情况下使用 IN 是非常糟糕的。
1赞 JYelton 9/17/2021
@Kamlesh 也许您缺少一些索引?此外,此解决方案已有近 10 年的历史,也许某些更新更改了此查询的行为或性能。
0赞 bytepan 11/1/2021
漂亮而优雅的解决方案。只是一点点改进,使其即使在没有单一排序属性的情况下也能工作。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 );
0赞 Wolf 12/9/2021
这甚至适用于 Firebird 1.0.3!
0赞 MrSalesi 4/21/2023
好答案,这个链接就像你的答案 thispointer.com/retrieving-the-last-record-in-each-group-mysql
4赞 user942821 3/31/2012 #8

我还没有用大型数据库进行测试,但我认为这可能比连接表更快:

SELECT *, Max(Id) FROM messages GROUP BY Name

评论

20赞 harm 7/3/2014
这将返回任意数据。换言之,返回的列可能不来自具有 MAX(Id) 的记录。
0赞 Nicola 4/8/2015
从具有 WHERE 条件的一组记录中选择最大 Id 很有用:“SELECT Max(Id) FROM Prod WHERE Pn='” + Pn + “'” 它从一组具有相同 Pn.In c# 使用读取器的记录中返回最大 Id。GetString(0) 获取结果
0赞 Wolf 12/9/2021
为什么这篇文章首先获得了赞成票?在我看来,它完全没有抓住重点。
55赞 Vipin 12/25/2013 #9

子查询 fiddle 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。

7赞 M Khalid Junaid 3/29/2014 #10

这是另一种使用 with order by 获取最后相关记录的方法,并从列表中选择一条记录GROUP_CONCATSUBSTRING_INDEX

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

上面的查询将对同一组中的所有人进行分组,并且在我使用的情况下,使用提供的分隔符按降序加入特定组中的所有人,使用此列表将选择第一个Other_ColumnsNameORDER BY id DESCOther_Columns||SUBSTRING_INDEX

Fiddle 演示

评论

0赞 Rick James 1/28/2020
请注意,这限制了您可以处理的行数。group_concat_max_len
6赞 Jeet Singh Parmar 4/11/2014 #11
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;

评论

1赞 janfoeh 5/4/2014
你能详细说明一下你的答案吗?为什么您的查询比 Vijays 原始查询更可取?
7赞 bikashphp 10/21/2014 #12

嗨,@Vijay开发人员,如果您的表消息包含 Id,这是自动递增主键,那么要获取基于主键的最新记录,您的查询应如下所示:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId

评论

1赞 CORSAIR 4/10/2019
这是我找到的最快的
0赞 Charles L. 3/19/2021
这也是一个不错的 b/c 限制,偏移量可以在子查询中使用(或者在联接中使用查询时调用的任何名称)。MySQL不允许在典型的子查询中使用限制/偏移,但对于这样的连接,它们被允许。
6赞 Shrikant Gupta 9/28/2015 #13

您也可以从这里查看。

http://sqlfiddle.com/#!9/ef42b/9

第一个解决方案

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

第二种解决方案

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;
3赞 Ullas 11/19/2015 #14

如果您想要每个 的最后一行,则可以按降序为每个行组指定行号和顺序。NameNameId

查询

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

SQL 小提琴

2赞 Azathoth 11/30/2016 #15

这个怎么样:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

我遇到了类似的问题(在 postgresql 上很艰难)和 1M 记录表上。该解决方案需要 1.7 秒,而使用 LEFT JOIN 的解决方案需要 44 秒。 就我而言,我必须根据 NULL 值过滤您姓名字段的相关内容,从而在 0.2 秒内获得更好的性能

3赞 Abhishek Yadav 6/9/2017 #16

这是我的解决方案:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;

评论

1赞 Paul Spiegel 12/10/2019
这不会返回每个名称的最新消息。它只是 .SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
0赞 Rick James 1/28/2020
此外,这种配方效率极低。
17赞 Song Zhengyi 3/11/2018 #17

具有相当快速度的方法如下。

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

评论

0赞 Wolf 12/9/2021
这甚至适用于 Firebird 1.0.3!...而且似乎比 stackoverflow.com/a/9368897/2932052“更快
0赞 caram 8/12/2022
这在大型数据集上真的很慢。
9赞 Yoseph 4/18/2018 #18

显然,有很多不同的方法可以获得相同的结果,您的问题似乎是在MySQL中获取每个组中最后结果的有效方法是什么。如果您正在处理大量数据,并假设您正在将 InnoDB 与最新版本的 MySQL(例如 5.7.21 和 8.0.4-rc)一起使用,那么可能没有有效的方法来做到这一点。

我们有时需要对超过 6000 万行的表执行此操作。

对于这些示例,我将使用只有大约 150 万行的数据,其中查询需要查找数据中所有组的结果。在我们的实际案例中,我们经常需要从大约 2,000 个组返回数据(假设不需要检查太多数据)。

我将使用下表:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

温度表中填充了大约 150 万条随机记录和 100 个不同的组。 selected_group中填充了这 100 个组(在我们的例子中,对于所有组来说,这通常不到 20%)。

由于此数据是随机的,这意味着多行可以具有相同的 recordedTimestamps。我们想要的是按照 groupID 的顺序获取所有选定组的列表,每个组的最后记录时间戳,如果同一组有多个这样的匹配行,则这些行的最后一个匹配 id。

如果假设MySQL有一个last()函数,该函数从特殊ORDER BY子句中的最后一行返回值,那么我们可以简单地执行以下操作:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

在这种情况下,它只需要检查几 100 行,因为它不使用任何正常的 GROUP BY 函数。这将在 0 秒内执行,因此效率很高。 请注意,通常在MySQL中,我们会在GROUP BY子句之后看到一个ORDER BY子句,但是这个ORDER BY子句用于确定last()函数的ORDER,如果它在GROUP BY之后,那么它将对GROUPS进行排序。如果不存在 GROUP BY 子句,则所有返回的行中的最后一个值将相同。

但是,MySQL没有这个,所以让我们看看它所具有的不同想法,并证明这些都不是有效的。

示例 1

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 1
);

这检查了 3,009,254 行,在 5.7.21 上花费了 ~0.859 秒,在 8.0.4-rc 上花费了更长的时间

示例 2

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

这检查了 1,505,331 行,在 5.7.21 上花费了 ~1.25 秒,在 8.0.4-rc 上花费了更长的时间

示例 3

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

这检查了 3,009,685 行,在 5.7.21 上花费了 ~1.95 秒,在 8.0.4-rc 上花费了更长的时间

示例 4

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

这检查了 6,137,810 行,在 5.7.21 上花费了 ~2.2 秒,在 8.0.4-rc 上花费了更长的时间

示例 5

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

这检查了 6,017,808 行,在 8.0.4-rc 上花费了 ~4.2 秒

示例 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

这检查了 6,017,908 行,在 8.0.4-rc 上花费了 ~17.5 秒

示例 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

这个需要很长时间,所以我不得不杀死它。

评论

0赞 Paul Spiegel 12/10/2019
这是一个不同的问题。解决方案是一个巨大的 UNION ALL 查询。
0赞 Yoseph 12/12/2019
@PaulSpiegel 我猜你是在开玩笑说巨大的 UNION ALL。除了需要提前知道所有选定的组之外,还有 2,000 个选定的组,这将是一个非常庞大的查询,它的性能甚至比上面最快的示例还要差,所以不,这不是一个解决方案。
0赞 Paul Spiegel 12/13/2019
我绝对是认真的。我过去曾用几百个小组测试过这一点。当您需要处理大组中的联系时,UNION ALL 是 MySQL 中强制执行最佳执行计划的唯一方法。 速度很快,将为您提供构建此类查询所需的所有数据。只要不超过 ,您应该可以接受查询大小,在 MySQL 5.7 中默认为 4MB。SELECT DISTINCT(groupID)max_allowed_packet
3赞 michal.jakubeczy 5/2/2018 #19

如果性能确实是您关心的问题,则可以在表中引入一个名为 BIT 类型的新列。IsLastInGroup

在最后一列上将其设置为 true,并在每行插入/更新/删除时保持它。写入速度会变慢,但读取会让您受益。这取决于您的用例,我建议仅在您以阅读为重点时才使用它。

因此,您的查询将如下所示:

SELECT * FROM Messages WHERE IsLastInGroup = 1

评论

0赞 Lawrence 8/27/2019
Moodle 中的某些表有一个这样的标志列。
4赞 ShriP 2/8/2019 #20
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )
0赞 Amir Fo 4/7/2019 #21

您可以通过计数进行分组,也可以获取分组的最后一项,例如:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user
21赞 Yagnesh bhalala 4/27/2019 #22

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

编号 category_id post_title
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

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

编号 category_id post_title
1 1 标题 1
4 2 标题 4
6 3 标题 6

group by 将始终返回结果集上组中的第一条记录。

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

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

编号 category_id post_title
3 1 标题 3
5 2 标题 5
6 3 标题 6

参考资料 点击这里

5赞 Abhishek Sengupta 10/17/2019 #23

**

嗨,这个查询可能会有所帮助:

**

SELECT 
  *
FROM 
  message 

WHERE 
  `Id` IN (
    SELECT 
      MAX(`Id`) 
    FROM 
      message 
    GROUP BY 
      `Name`
  ) 
ORDER BY 
   `Id` DESC
1赞 Ka. 12/10/2019 #24

另一种方法:

求出每个程序的最大m2_price属性(1 个程序中的 n 个属性):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
1赞 kiruba 1/15/2020 #25

希望以下Oracle查询可以提供帮助:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1
4赞 milad nazari 10/1/2020 #26

我在 https://dzone.com/articles/get-last-record-in-each-mysql-group 找到了最佳解决方案

select * from `data` where `id` in (select max(`id`) from `data` group by `name_id`)

评论

6赞 forpas 10/1/2020
您在已经发布的答案中没有看到此查询吗?
1赞 Jacek Błocki 10/10/2020 #27

怎么样:

select *, max(id) from messages group by name 

我已经在sqlite上测试了它,它返回所有名称的所有列和最大id值。

评论

4赞 Lacek 1/6/2021
首先,您的答案通常不会为 MySQL 提供正确的结果,因为最大 ID 和其余列可能来自同一组的不同记录。其次,同样的答案已经存在(stackoverflow.com/a/9956875/1089242),就像 9 年前一样。第三,问题特别提到了MySQL。如果您担心的是通用 sql 数据库(或只是 sqlite),请查看其他问题,例如 stackoverflow.com/q/3800551/1089242
1赞 Jacek Błocki 1/10/2021
我只用sqlite测试了它,它产生了正确的结果。我没有尝试过MySQL。解决方案很简单,所以我把它贴在这里。有时简单是错误的。你能分享一个不起作用的sqlite例子吗?
2赞 Brilliand 2/6/2021
@JacekB łocki:这个问题是关于MySQL的。您的答案可能在sqlite中有效(我不知道,我没有),但在MySQL中不起作用。
3赞 Lukasz Szozda 4/7/2021 #28

MariaDB 10.3 及更高版本使用 GROUP_CONCAT

这个想法是使用 + :ORDER BYLIMIT

SELECT GROUP_CONCAT(id ORDER BY id DESC LIMIT 1) AS id,
       name,
       GROUP_CONCAT(Other_columns ORDER BY id DESC LIMIT 1) AS Other_columns
FROM t
GROUP BY name;

db<>fiddle 演示

7赞 user13457138 8/23/2021 #29

如果您需要分组查询中文本列的最新或最早的记录,并且您不想使用子查询,则可以执行此操作...

前任。您有一个电影列表,需要获取该系列和最新电影的计数

编号 系列 名字
1 星球大战 新的希望
2 星球大战 帝国反击战
3 星球大战 绝地归来
SELECT COUNT(id), series, SUBSTRING(MAX(CONCAT(id, name)), LENGTH(id) + 1), 
FROM Movies
GROUP BY series

这返回...

编号 系列 名字
3 星球大战 绝地归来

MAX 将返回具有最高值的行,因此通过将 id 与名称连接起来,您现在将获得最新记录,然后只需去除 id 即可获得最终结果。

比使用子查询更有效。

因此,对于给定的示例:

SELECT MAX(Id), Name, SUBSTRING(MAX(CONCAT(Id, Other_Columns)), LENGTH(Id) + 1), 
FROM messages
GROUP BY Name

快乐编码,“愿原力与你同在”:)

评论

0赞 Suraj 4/28/2022
这可能行不通,让我们假设在您的示例中,“新希望”的 id 是 9,“绝地归来”的 id 是 10。这是因为 id 和 name 串联的 MAX 函数将在字符串上执行此操作。因此,《9A新希望》将比《绝地归来》具有更高的顺序
1赞 id'7238 11/11/2021 #30

从 MySQL 8.0.14 开始,这也可以使用横向派生表来实现:

SELECT t.*
FROM messages t
JOIN LATERAL (
  SELECT name, MAX(id) AS id 
  FROM messages t1
  WHERE t.name = t1.name
  GROUP BY name
) trn ON t.name = trn.name AND t.id = trn.id

db<>小提琴

1赞 user17929112 4/14/2022 #31

这是 1 行中更有效的版本,只要表有时间戳列就可以工作。

SELECT Id, Name, SUBSTRING_INDEX(MAX(CONCAT(TimeStamp, ',', Other_Columns)), ',', -1)
FROM Messages
ORDER BY id DESC GROUP BY Name 

这将返回该组在“Other_Columns”上的最新记录

2赞 lemon 5/15/2022 #32

另一个没有子查询的选项。

该方案使用MySQL LAST_VALUE窗口函数,利用了Window Function Frame中可用的MySQL工具。

SELECT DISTINCT 
    LAST_VALUE(Id)            
        OVER(PARTITION BY Name 
             ORDER     BY Id 
             ROWS BETWEEN 0 PRECEDING 
                      AND UNBOUNDED FOLLOWING),
    Name,
    LAST_VALUE(Other_Columns)            
        OVER(PARTITION BY Name 
             ORDER     BY Id 
             ROWS BETWEEN 0 PRECEDING 
                      AND UNBOUNDED FOLLOWING)
FROM   
    tab

在这里试试吧。

评论

1赞 emiljoj 9/23/2022
我正在使用 T-SQL,这对我来说效果很好,谢谢!
1赞 thistleknot 9/19/2022 #33

我有一个类似的问题

子查询并加入救援

SELECT p."Date"
        ,p."Symbol"
        ,p."ratio_roll_qtr_ret"
    FROM PUBLIC."prices_vw" AS p
    JOIN (
        SELECT "Symbol"
            ,max("Date")
        FROM PUBLIC."prices_vw"
        GROUP BY "Symbol"
        ) AS sq ON p."Date" = sq."max"
        AND p."Symbol" = sq."Symbol"
    WHERE p."ratio_roll_qtr_ret" IS NOT NULL
    ORDER BY "ratio_roll_qtr_ret" DESC;
3赞 Timo 8/15/2023 #34

在我寻求通用的 groupwise-max 的过程中,我看到了许多关于这个主题的答案和博客文章。即使是我最喜欢的(实际上是关于该主题的精彩系列的一部分)也未能确定便携式解决方案,而是深入研究了每个 RDMBS 的细节。

幸运的是,确实存在便携式解决方案

您需要的二级索引是 。( 将是相同的,因为主键始终是隐式包含的。namename, id

创建 s 组,并使用依赖子查询获取每个组的最新行。message

SELECT m.*

-- Step 1: Start by obtaining the groupwise maximums
FROM
(
    SELECT (
        -- Step 1b: Find the ID of the group maximum by seeking in the index
        SELECT id
        FROM messages m
        WHERE m.name = groups.name
        ORDER BY m.name DESC, m.id DESC -- Match the index EXACTLY, and indicate direction
        LIMIT 1
    ) AS id

    -- Step 1a: Find the groups by seeking through the index
    FROM messages AS groups
    GROUP BY groups.name
) AS maxes

-- Step 2: For each group, join the max row by ID
-- This neatly separates any potential followup SQL from the groupwise-max tactics
INNER JOIN messages m ON m.id = maxes.id
;

这是可移植的,因为它只需要以下构建基块组合:

  • 索引。GROUP BY
  • 用 和 索引。SELECTORDER BY [ASC/DESC]LIMIT/TOP
  • 从属子查询。

只要确保有正确的索引:.GroupKeyColumn(s), GroupWinnerColumn(s), PrimaryKeyColumn(s)

在 OP 的案例中,组键是 ,组的获胜者由 确定,并且主键已经被 覆盖,因此: 。nameidname, id

许多人提出了涉及子查询的解决方案,但最容易被忽视的方面是一组高度具体的排序子句,它会导致使用正确的索引 - 在正确的遍历方向上,同样如此。

其他优势

  • 可轻松调整最小值 () 与.max ()。ASCDESC
  • 每组的获胜者可以是复合的,例如.(这也允许我们消除非唯一入选者的歧义,例如“最新时间戳”。timestamp, id
  • 组键可以是复合键,例如.company_id, department_name
  • 轻松扩展要选择的组。WHERE
  • 轻松扩展要忽略的项目,包括索引 () 和非索引 ()。WHEREid >= 1000is_deleted = 0

为什么这能[最佳]工作?

想象一下,翻阅实体电话簿,找到每个城镇的最后一个条目,即该城镇按字母顺序排列的最大名称的条目。你会怎么做?

你会从最后开始。书中的最后一个条目是最后一个城镇的组最大值。这是您遇到的第一个结果行。

对于每个后续的所需结果行,您将向后进行二进制搜索,以找到下一个最大的城镇。在当前城镇过渡到其前一行时,有前一行的最后一行(按字母顺序排列的最大名称),即您的下一个结果行。重复直到没有更多的城镇。

粗略地说,电话簿就像 上的二级索引,作为主键。(为了方便起见,我简化了事情,假装电话号码分配给一个人,名字形成一列。{ Town, Name, PhoneNumber }PhoneNumber

您实际上是在对索引进行反向搜索。通过有效地反复跳转到下一个城镇(由于二进制搜索或 B 树结构),工作受到结果行数而不是总行数的限制。这是渐近最优的。由于反向遍历方向,你遇到的每个城镇都会从它最大的一行开始,你的目标。这很重要:想象一下,如果你必须扫描一个城镇的所有行,那么荒谬的无谓工作量是多么荒谬。

将解更改为分组最小值与更改遍历方向(即从 到 )一样微不足道。DESCASC

RDBMS 说明

  • 虽然MySQL 8正确地显示了这一点,但MySQL 5.7显示了一个令人担忧的,但它实际上执行正确。(在涉及非常大的群体的庞大数据集上进行了测试。在~3秒内获得了数以亿计的结果。Using indexUsing where; Using index
  • 对于 SQL Server,语法是 而不是 .SELECT TOP 1SELECT ... LIMIT 1