【问题标题】:Retrieving the last record in each group - MySQL检索每个组中的最后一条记录 - MySQL
【发布时间】:2010-11-21 17:42:00
【问题描述】:

有一个表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

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

【问题讨论】:

标签: sql mysql group-by greatest-n-per-group


【解决方案1】:

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;

以下是我在 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 August data dump 的副本。我将使用它进行基准测试。 Posts 表中有 1,114,357 行。这是在我的 Macbook Pro 2.40GHz 上的 MySQL 5.0.75 上运行的。

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

首先在子查询中使用@Eric 的shown 技术和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)

现在使用my techniqueLEFT 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)

这是我的Posts 表的 DDL:

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
  • @KatherineChen,这与LEFT [OUTER] JOIN 的工作方式有关。如果该连接在m1 中找不到给定行的匹配项,那么它仍将返回该行m1,但m2 的所有列都将为NULL。
  • @KatherineChen,我将其描述为:没有找到具有相同name 和更大id 的其他行,因此m1 必须是具有最大id 的行name 的给定值。
  • @ysth 我希望 Stack Overflow 的目的是向读者展示技术,这样他们就可以自己做更多的工作。目标不是为他们做所有的工作。
【解决方案2】:

UPD: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 行和 1182 组的 InnoDB 表上进行了测试。该表包含功能测试的测试结果,并以(test_id, request_id) 作为主键。因此,test_id 是一个组,我正在为每个 test_id 搜索最后一个 request_id

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

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

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

3 ways MySQL uses indexes 是一篇了解一些细节的好文章。

解决方案 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 max 行。

查询的缺点是它的结果不能被查询缓存缓存。

【讨论】:

    【解决方案3】:

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

    试试这个:

    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
    

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

    【讨论】:

      【解决方案4】:

      我找到了一个不同的解决方案,即获取每个组中最后一个帖子的 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。

      【讨论】:

      • 这个解决方案导致 mysql 服务器/服务崩溃。我已经检查了 1000 万条记录,不推荐此解决方案。在这种情况下使用 IN 是非常糟糕的。
      • @Kamlesh 也许您缺少一些索引?此外,此解决方案已有近 10 年的历史,可能某些更新已更改此查询的行为或性能。
      • 漂亮而优雅的解决方案。即使没有唯一的排序属性,只需稍加改进即可使其工作。 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);
      • 这甚至适用于 Firebird 1.0.3!
      【解决方案5】:

      通过子查询fiddle Link解决

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

      通过连接条件解决fiddle link

      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。

      【讨论】:

        【解决方案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
        

        【讨论】:

        【解决方案7】:

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

        id category_id post_title

        1 1 Title 1

        2 1 Title 2

        3 1 Title 3

        4 2 Title 4

        5 2 Title 5

        6 3 Title 6

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

        select * from posts group by category_id

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

        id category_id post_title

        1 1 Title 1

        4 2 Title 4

        6 3 Title 6

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

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

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

        id category_id post_title

        3 1 Title 3

        5 2 Title 5

        6 3 Title 6

        Reference Click Here

        【讨论】:

          【解决方案8】:

          这里有两个建议。首先,如果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 消息的消息。

          【讨论】:

          • MySQL 不支持 ROW_NUMBER() 或 CTE。
          • MySQL 8.0(和 MariaDB 10.2)现在支持 ROW_NUMBER() 和 CTE。
          • 也许通过使用两个别名(ab)可以提高可读性,例如SELECT * FROM messages a WHERE NOT EXISTS (SELECT * FROM messages as b WHERE a.Name = b.Name AND a.Id &gt; b.Id)
          【解决方案9】:

          显然有很多不同的方法可以获得相同的结果,您的问题似乎是在 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%)。

          由于此数据是随机的,这意味着多行可以具有相同的记录时间戳。我们想要的是按照 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;
          

          这个人要花很长时间了,所以我不得不杀了它。

          【讨论】:

          • 这是一个不同的问题。解决方案是一个巨大的 UNION ALL 查询。
          • @PaulSpiegel 我猜你是在拿庞大的 UNION ALL 开玩笑。除了需要提前知道所有选定的组这一事实之外,如果有 2,000 个选定的组,这将是一个非常庞大的查询,它的性能甚至会比上面最快的示例更差,所以不,这不是一个解决方案。
          • 我是认真的。过去我已经用几百组测试过。当您需要处理大组中的关系时,UNION ALL 是 MySQL 中强制执行最佳执行计划的唯一方法。 SELECT DISTINCT(groupID) 速度很快,会为您提供构建此类查询所需的所有数据。只要查询大小不超过max_allowed_packet(在 MySQL 5.7 中默认为 4MB),您应该可以接受。
          【解决方案10】:

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

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

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

          Fiddle Demo

          【讨论】:

          • 请注意group_concat_max_len 限制了您可以处理的行数。
          【解决方案11】:

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

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

          【讨论】:

          • 返回任意数据。换句话说,返回的列可能不是来自具有 MAX(Id) 的记录。
          • 用于从具有 WHERE 条件的一组记录中选择最大 Id : "SELECT Max(Id) FROM Prod WHERE Pn='" + Pn + "'" 它从一组记录中返回最大 Id具有相同 Pn 的记录。在 c# 中使用 reader.GetString(0) 获取结果
          • 为什么这篇文章一开始就获得了正面投票?在我看来,它完全没有抓住重点。
          【解决方案12】:
          SELECT 
            column1,
            column2 
          FROM
            table_name 
          WHERE id IN 
            (SELECT 
              MAX(id) 
            FROM
              table_name 
            GROUP BY column1) 
          ORDER BY column1 ;
          

          【讨论】:

          • 您能否详细说明您的答案?为什么您的查询比 Vijay 的原始查询更受欢迎?
          【解决方案13】:

          您好@Vijay Dev,如果您的表 messages 包含 Id 这是自动递增的主键,那么要获取基于主键的最新记录,您的查询应该如下所示:

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

          【讨论】:

          • 这是我发现的最快的一个
          • 这也是一个很好的 b/c 限制,并且可以在子查询中使用偏移量(或者在连接中使用查询时调用的任何内容)。 MySQL 不允许在典型的子查询中进行限制/偏移,但它们允许用于这样的连接。
          【解决方案14】:

          您也可以从这里观看。

          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 ;
          

          【讨论】:

            【解决方案15】:

            我在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`)
            

            【讨论】:

            • 你没有在已经发布的答案中看到这个查询吗?
            【解决方案16】:

            有什么方法可以使用这种方法删除表中的重复项?结果集基本上是唯一记录的集合,所以如果我们可以删除所有不在结果集中的记录,我们实际上就没有重复了吗?我试过这个,但 mySQL 给出了 1093 错误。

            DELETE FROM messages WHERE id NOT IN
             (SELECT m1.id  
             FROM messages m1 LEFT JOIN messages m2  
             ON (m1.name = m2.name AND m1.id < m2.id)  
             WHERE m2.id IS NULL)
            

            有没有办法将输出保存到临时变量,然后从 NOT IN(临时变量)中删除? @Bill 感谢您提供非常有用的解决方案。

            编辑:认为我找到了解决方案:

            DROP TABLE IF EXISTS UniqueIDs; 
            CREATE Temporary table UniqueIDs (id Int(11)); 
            
            INSERT INTO UniqueIDs 
                (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
                (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
                AND T1.ID < T2.ID) 
                WHERE T2.ID IS NULL); 
            
            DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);
            

            【讨论】:

            • 因为相关而有趣,但这不值得自己进行问答吗?
            【解决方案17】:

            试试这个:

            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 )  
            

            【讨论】:

              【解决方案18】:

              这是我的解决方案:

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

              【讨论】:

              • 这不会返回每个名称的最新消息。它只是SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME 的一个过于复杂的版本。
              • 此外,这个公式效率极低。
              【解决方案19】:
              SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )
              

              【讨论】:

                【解决方案20】:

                **

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

                **

                SELECT 
                  *
                FROM 
                  message 
                
                WHERE 
                  `Id` IN (
                    SELECT 
                      MAX(`Id`) 
                    FROM 
                      message 
                    GROUP BY 
                      `Name`
                  ) 
                ORDER BY 
                   `Id` DESC
                

                【讨论】:

                  【解决方案21】:

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

                  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;
                  

                  【讨论】:

                    【解决方案22】:

                    如果您想要每个Name 的最后一行,则可以通过Name 为每个行组指定行号,并按Id 降序排列。

                    查询

                    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 Fiddle

                    【讨论】:

                      【解决方案23】:

                      这个怎么样:

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

                      我有类似的问题(在 postgresql 上)和一个 1M 的记录表。这个解决方案需要 1.7 秒,而使用 LEFT JOIN 的解决方案需要 44 秒。 在我的情况下,我必须根据 NULL 值过滤 name 字段的对应项,从而将性能提高 0.2 秒

                      【讨论】:

                        【解决方案24】:

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

                        在最后的列上将其设置为 true,并在每行插入/更新/删除时对其进行维护。写入会更慢,但你会从读取中受益。这取决于您的用例,我建议您仅在以阅读为中心的情况下使用它。

                        所以您的查询将如下所示:

                        SELECT * FROM Messages WHERE IsLastInGroup = 1
                        

                        【讨论】:

                        • Moodle 中的某些表格有这样的标志列。
                        【解决方案25】:

                        怎么样:

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

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

                        【讨论】:

                        • 首先,您的答案通常不能为 MySQL 提供正确的结果,因为最大 ID 和其余列可能来自同一组的不同记录。其次,就像 9 年前一样,相同的答案已经存在 (stackoverflow.com/a/9956875/1089242)。第三,问题具体提到了MySQL。如果您关心的是通用 sql 数据库(或只是 sqlite),请检查其他问题,例如stackoverflow.com/q/3800551/1089242
                        • 我只用 sqlite 测试过它,它产生了正确的结果。我还没有尝试过MySQL。解决方案很简单,所以我把它贴在这里。有时简单是错误的。你能分享一个不工作的 sqlite 例子吗?
                        • @JacekBłocki 这个问题是关于 MySQL 的。您的答案可能在 sqlite 中有效(我不知道,我没有),但在 MySQL 中无效。
                        【解决方案26】:

                        另一种方法:

                        在每个程序中找到最大 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
                        

                        【讨论】:

                          【解决方案27】:

                          MariaDB 10.3 和更新版本使用GROUP_CONCAT

                          想法是使用ORDER BY + LIMIT

                          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 demo

                          【讨论】:

                            【解决方案28】:

                            如果您需要分组查询中文本列的最新或最旧记录,并且您不想使用子查询,您可以这样做...

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

                            id series name
                            1 Star Wars A New hope
                            2 Star Wars The Empire Strikes Back
                            3 Star Wars Return of The Jedi
                            SELECT COUNT(id), series, SUBSTRING(MAX(CONCAT(id, name)), LENGTH(id) + 1), 
                            FROM Movies
                            GROUP BY series
                            

                            这会返回...

                            id series name
                            3 Star Wars Return of The Jedi

                            MAX 将返回具有最高值的行,因此通过将 id 连接到名称,您现在将获得最新的记录,然后去掉 id 以获得最终结果。

                            比使用子查询更有效。

                            所以对于给定的例子:

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

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

                            【讨论】:

                              【解决方案29】:
                              select * from messages group by name desc
                              

                              【讨论】:

                                【解决方案30】:

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

                                SELECT 
                                    user,
                                    COUNT(user) AS count,
                                    MAX(id) as last
                                FROM request 
                                GROUP BY user
                                

                                【讨论】:

                                  猜你喜欢
                                  相关资源
                                  最近更新 更多