2012-01-05 51 views
67

如何做到这一点?获取最高/最低的记录​​每组

这个问题的前题是“使用等级(@rank:= @rank + 1)与子查询的复杂查询 - 它会工作?”因为我用的行列寻求解决方案,但现在我看到, Bill发布的解决方案要好得多。

原题:

我想编写一个查询,将采取最后的记录从给定的一些定义的顺序各组:

SET @Rank=0; 

select s.* 
from (select GroupId, max(Rank) AS MaxRank 
     from (select GroupId, @Rank := @Rank + 1 AS Rank 
      from Table 
      order by OrderField 
      ) as t 
     group by GroupId) as t 
    join (
     select *, @Rank := @Rank + 1 AS Rank 
     from Table 
     order by OrderField 
    ) as s 
    on t.GroupId = s.GroupId and t.MaxRank = s.Rank 
order by OrderField 

表达@Rank := @Rank + 1通常用于军衔,但对我来说它在2个子查询中使用时看起来很可疑,但只初始化一次。它会以这种方式工作吗?

第二,它将与一个多次评估的子查询一起工作吗?像子查询中的where(或having)子句(另一种方式如何写上述):

SET @Rank=0; 

select Table.*, @Rank := @Rank + 1 AS Rank 
from Table 
having Rank = (select max(Rank) AS MaxRank 
       from (select GroupId, @Rank := @Rank + 1 AS Rank 
        from Table as t0 
        order by OrderField 
        ) as t 
       where t.GroupId = table.GroupId 
      ) 
order by OrderField 

在此先感谢!

+1

更高级的问题在这里http://stackoverflow.com/questions/9841093/how-to-writegreatest-n-per-group-type-query-but-with-additional-conditions/9845109#9845109 – TMS 2012-03-25 10:56:05

回答

129

所以你想获得每组最高的OrderField?我会做这种方式:

SELECT t1.* 
FROM `Table` AS t1 
LEFT OUTER JOIN `Table` AS t2 
    ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField 
WHERE t2.GroupId IS NULL 
ORDER BY t1.OrderField; // not needed! (note by Tomas) 

编辑由Tomas:如果有更多的记录与同组内的相同OrderField,你需要确切地说是其中之一,你可能要扩展的条件编辑的

SELECT t1.* 
FROM `Table` AS t1 
LEFT OUTER JOIN `Table` AS t2 
    ON t1.GroupId = t2.GroupId 
     AND (t1.OrderField < t2.OrderField 
     OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id)) 
WHERE t2.GroupId IS NULL 

端)

换句话说,返回没有其他行t2具有相同GroupId存在行t1和更大。。当t2.*为NULL时,表示左外部联接未找到此匹配项,因此t1在该组中的值最大为OrderField

没有排名,没有子查询。如果你有一个(GroupId, OrderField)的复合索引,这应该运行得很快并且通过“使用索引”来优化对t2的访问。


关于性能,请参阅我的回答Retrieving the last record in each group。我尝试了使用Stack Overflow数据转储的子查询方法和联接方法。差异是显着的:在我的测试中,加入方法的运行速度提高了278倍。

重要的是你有正确的索引以获得最佳结果!

关于使用@Rank变量的方法,它不会像你写的那样工作,因为@Rank的值在查询处理完第一个表后不会重置为零。我会告诉你一个例子。

我插入一些虚拟的数据,一个额外的字段为空,除了在我们所知道的是每组的最大行:

select * from `Table`; 

+---------+------------+------+ 
| GroupId | OrderField | foo | 
+---------+------------+------+ 
|  10 |   10 | NULL | 
|  10 |   20 | NULL | 
|  10 |   30 | foo | 
|  20 |   40 | NULL | 
|  20 |   50 | NULL | 
|  20 |   60 | foo | 
+---------+------------+------+ 

我们可以证明,排名上升至三层为第一组和六为第二组,和内查询这些正确返回:

select GroupId, max(Rank) AS MaxRank 
from (
    select GroupId, @Rank := @Rank + 1 AS Rank 
    from `Table` 
    order by OrderField) as t 
group by GroupId 

+---------+---------+ 
| GroupId | MaxRank | 
+---------+---------+ 
|  10 |  3 | 
|  20 |  6 | 
+---------+---------+ 

现在运行查询没有连接条件,迫使所有行的笛卡尔积,我们也获取所有列:

select s.*, t.* 
from (select GroupId, max(Rank) AS MaxRank 
     from (select GroupId, @Rank := @Rank + 1 AS Rank 
      from `Table` 
      order by OrderField 
      ) as t 
     group by GroupId) as t 
    join (
     select *, @Rank := @Rank + 1 AS Rank 
     from `Table` 
     order by OrderField 
    ) as s 
    -- on t.GroupId = s.GroupId and t.MaxRank = s.Rank 
order by OrderField; 

+---------+---------+---------+------------+------+------+ 
| GroupId | MaxRank | GroupId | OrderField | foo | Rank | 
+---------+---------+---------+------------+------+------+ 
|  10 |  3 |  10 |   10 | NULL | 7 | 
|  20 |  6 |  10 |   10 | NULL | 7 | 
|  10 |  3 |  10 |   20 | NULL | 8 | 
|  20 |  6 |  10 |   20 | NULL | 8 | 
|  20 |  6 |  10 |   30 | foo | 9 | 
|  10 |  3 |  10 |   30 | foo | 9 | 
|  10 |  3 |  20 |   40 | NULL | 10 | 
|  20 |  6 |  20 |   40 | NULL | 10 | 
|  10 |  3 |  20 |   50 | NULL | 11 | 
|  20 |  6 |  20 |   50 | NULL | 11 | 
|  20 |  6 |  20 |   60 | foo | 12 | 
|  10 |  3 |  20 |   60 | foo | 12 | 
+---------+---------+---------+------------+------+------+ 

从上面我们可以看出,每组的最大等级是正确的,但是@Rank继续增加,因为它将第二个派生表处理为7和更高。所以第二个派生表中的等级将永远不会与第一个派生表中的等级重叠。

您必须添加另一个派生表来强制@Rank在处理两个表之间重置为零(并希望优化器不会更改它评估表的顺序,否则使用STRAIGHT_JOIN来防止那):

select s.* 
from (select GroupId, max(Rank) AS MaxRank 
     from (select GroupId, @Rank := @Rank + 1 AS Rank 
      from `Table` 
      order by OrderField 
      ) as t 
     group by GroupId) as t 
    join (select @Rank := 0) r -- RESET @Rank TO ZERO HERE 
    join (
     select *, @Rank := @Rank + 1 AS Rank 
     from `Table` 
     order by OrderField 
    ) as s 
    on t.GroupId = s.GroupId and t.MaxRank = s.Rank 
order by OrderField; 

+---------+------------+------+------+ 
| GroupId | OrderField | foo | Rank | 
+---------+------------+------+------+ 
|  10 |   30 | foo | 3 | 
|  20 |   60 | foo | 6 | 
+---------+------------+------+------+ 

但是这个查询的优化是可怕的。它不能使用任何索引,它会创建两个临时表,以困难的方式对它们进行排序,甚至使用连接缓冲区,因为它在连接临时表时也不能使用索引。这是EXPLAIN输出例如:

+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra       | 
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+ 
| 1 | PRIMARY  | <derived4> | system | NULL   | NULL | NULL | NULL | 1 | Using temporary; Using filesort | 
| 1 | PRIMARY  | <derived2> | ALL | NULL   | NULL | NULL | NULL | 2 |         | 
| 1 | PRIMARY  | <derived5> | ALL | NULL   | NULL | NULL | NULL | 6 | Using where; Using join buffer | 
| 5 | DERIVED  | Table  | ALL | NULL   | NULL | NULL | NULL | 6 | Using filesort     | 
| 4 | DERIVED  | NULL  | NULL | NULL   | NULL | NULL | NULL | NULL | No tables used     | 
| 2 | DERIVED  | <derived3> | ALL | NULL   | NULL | NULL | NULL | 6 | Using temporary; Using filesort | 
| 3 | DERIVED  | Table  | ALL | NULL   | NULL | NULL | NULL | 6 | Using filesort     | 
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+ 

而使用左外连接我的解决方案优化了好多了。它不使用临时表,甚至报告"Using index",这意味着它可以仅使用索引解析连接,而不会触及数据。

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref    | rows | Extra     | 
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+ 
| 1 | SIMPLE  | t1 | ALL | NULL   | NULL | NULL | NULL   | 6 | Using filesort   | 
| 1 | SIMPLE  | t2 | ref | GroupId  | GroupId | 5  | test.t1.GroupId | 1 | Using where; Using index | 
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+ 

您可能会阅读在他们的博客上声称“加入SQL变慢”的人,但这是无稽之谈。糟糕的优化会导致SQL变慢。

+0

这可能证明相当有用(对于OP也是如此),但可悲的是,这两个问题都没有被回答。 – 2012-01-05 21:20:42

+0

谢谢比尔,这是一个好主意,如何避免队伍,但...会不会加入缓慢?连接(没有where子句的限制)将比我的查询大得多。无论如何,感谢这个主意!但是,在原始问题中,我也会很感兴趣,即队伍是否会以这种方式工作。 – TMS 2012-01-05 23:53:21

+0

谢谢你的出色答案,比尔。但是,如果我使用'@ Rank1'和'@ Rank2',每个子查询都有一个呢?这能解决问题吗?这会比你的解决方案更快吗? – TMS 2012-01-06 06:37:33