有一个表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
但这看起来效率很低。还有其他方法可以达到相同的结果吗?
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 秒:
如果您想将顺序更改为 ASC,请将其放入子查询中,仅返回 ids 并将其用作子查询来连接其余列:
这对我的数据来说大约需要 1.2 秒。
解决方案2
这是另一个解决方案,对于我的表来说大约需要 19 秒:
它也按降序返回测试。它要慢得多,因为它执行完整索引扫描,但它可以让您了解如何为每个组输出 N 个最大行。
该查询的缺点是查询缓存无法缓存其结果。
MySQL 8.0 现在支持窗口函数,例如几乎所有流行的 SQL 实现。使用这个标准语法,我们可以编写每组最大n个查询:
此方法和其他查找的方法分组最大行数在 MySQL 手册中进行了说明。
以下是我在2009年针对这个问题写的原始答案:
我这样写解决方案: