有一个表消息,其中包含如下所示的数据:

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 *,我将得到如下结果:

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

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


当前回答

你可以通过计数来分组,也可以得到分组的最后一项,比如:

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

其他回答

我还没有测试大DB,但我认为这可能比连接表更快:

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

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

如果您需要分组查询中文本列的最新或最古老的记录,并且不希望使用子查询,您可以这样做…

例如,你有一个电影列表,需要获得系列电影和最新电影的数量

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

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

以下是我的解决方案:

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

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中使用索引)。

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

if the underlying index is BTREE index (which is usually the case), the largest (group_id, item_value) pair is the last value within each group_id, that is the first for each group_id if we walk through the index in descending order; if we read the values which are covered by an index, the values are read in the order of the index; each index implicitly contains primary key columns appended to that (that is the primary key is in the coverage index). In solutions below I operate directly on the primary key, in you case, you will just need to add primary key columns in the result. in many cases it is much cheaper to collect the required row ids in the required order in a subquery and join the result of the subquery on the id. Since for each row in the subquery result MySQL will need a single fetch based on primary key, the subquery will be put first in the join and the rows will be output in the order of the ids in the subquery (if we omit explicit ORDER BY for the join)

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行。

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