Heim > Fragen und Antworten > Hauptteil
Es gibt eine Tabellemessages
mit Daten wie diesen:
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
Wenn ich die Abfrage select * from messages group by name
ausführe, erhalte ich folgende Ergebnisse:
1 A A_data_1 4 B B_data_1 6 C C_data_1
Welche Abfrage liefert die folgenden Ergebnisse?
3 A A_data_3 5 B B_data_2 6 C C_data_1
Das heißt, der letzte Datensatz in jeder Gruppe sollte zurückgegeben werden.
Derzeit verwende ich folgende Abfrage:
SELECT * FROM (SELECT * FROM messages ORDER BY id DESC) AS x GROUP BY name
Aber das scheint sehr ineffizient zu sein. Gibt es andere Möglichkeiten, das gleiche Ergebnis zu erzielen?
P粉1565327062023-10-10 11:51:46
UPD:2017-03-31,版本5.7.5 MySQL 默认启用 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 中使用索引),但我不知道它什么时候会完成。
我有几个基于相同想法的其他解决方案:
(group_id, item_value)
对是每个 group_id
中的最后一个值,即如果我们按降序遍历索引,则为每个 group_id
的第一个;MySQL 使用索引的 3 种方式 是一篇很棒的文章,可以帮助您了解一些细节。
解决方案1
这个速度快得令人难以置信,在我的 18M+ 行上大约需要 0.8 秒:
SELECT test_id, MAX(request_id) AS request_id FROM testresults GROUP BY test_id DESC;
如果您想将顺序更改为 ASC,请将其放入子查询中,仅返回 ids 并将其用作子查询来连接其余列:
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 个最大行。
该查询的缺点是查询缓存无法缓存其结果。
P粉4632912482023-10-10 00:12:34
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 月数据转储 的副本。我将用它来进行基准测试。 Posts
表中有 1,114,357 行。它在我的 Macbook Pro 2.40GHz 上的 MySQL 5.0.75 上运行。
我将编写一个查询来查找给定用户 ID(我的)的最新帖子。
首先使用所示的技术< /a> by @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)
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+ | 1 | PRIMARY || 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)
这是我的 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 在这里向您展示如何进行软件开发工作,而不是为您完成所有工作。