在GROUP BY中使用LIMIT来获得每组N个结果?

Wel*_*lls 361 mysql sql ranking greatest-n-per-group

以下查询:

SELECT
year, id, rate
FROM h
WHERE year BETWEEN 2000 AND 2009
AND id IN (SELECT rid FROM table2)
GROUP BY id, year
ORDER BY id, rate DESC
Run Code Online (Sandbox Code Playgroud)

收益率:

year    id  rate
2006    p01 8
2003    p01 7.4
2008    p01 6.8
2001    p01 5.9
2007    p01 5.3
2009    p01 4.4
2002    p01 3.9
2004    p01 3.5
2005    p01 2.1
2000    p01 0.8
2001    p02 12.5
2004    p02 12.4
2002    p02 12.2
2003    p02 10.3
2000    p02 8.7
2006    p02 4.6
2007    p02 3.3
Run Code Online (Sandbox Code Playgroud)

我想要的只是每个id的前5个结果:

2006    p01 8
2003    p01 7.4
2008    p01 6.8
2001    p01 5.9
2007    p01 5.3
2001    p02 12.5
2004    p02 12.4
2002    p02 12.2
2003    p02 10.3
2000    p02 8.7
Run Code Online (Sandbox Code Playgroud)

有没有办法使用在GROUP BY中工作的某种LIMIT修饰符来做到这一点?

fth*_*lla 106

您可以使用GROUP_CONCAT聚合函数将所有年份转换为单个列,按以下id顺序排列并按以下顺序排序rate:

SELECT   id, GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
FROM     yourtable
GROUP BY id
Run Code Online (Sandbox Code Playgroud)

结果:

-----------------------------------------------------------
|  ID | GROUPED_YEAR                                      |
-----------------------------------------------------------
| p01 | 2006,2003,2008,2001,2007,2009,2002,2004,2005,2000 |
| p02 | 2001,2004,2002,2003,2000,2006,2007                |
-----------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

然后你可以使用FIND_IN_SET,它返回第二个参数中第一个参数的位置,例如.

SELECT FIND_IN_SET('2006', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
1

SELECT FIND_IN_SET('2009', '2006,2003,2008,2001,2007,2009,2002,2004,2005,2000');
6
Run Code Online (Sandbox Code Playgroud)

使用GROUP_CONCATFIND_IN_SET,并通过find_in_set返回的位置进行过滤,您可以使用此查询仅返回每个id的前5年:

SELECT
  yourtable.*
FROM
  yourtable INNER JOIN (
    SELECT
      id,
      GROUP_CONCAT(year ORDER BY rate DESC) grouped_year
    FROM
      yourtable
    GROUP BY id) group_max
  ON yourtable.id = group_max.id
     AND FIND_IN_SET(year, grouped_year) BETWEEN 1 AND 5
ORDER BY
  yourtable.id, yourtable.year DESC;
Run Code Online (Sandbox Code Playgroud)

请看这里的小提琴.

请注意,如果多行可以具有相同的速率,则应考虑在rate列而不是year列上使用GROUP_CONCAT(DISTINCT rate ORDER BY rate).

GROUP_CONCAT返回的字符串的最大长度是有限的,因此如果您需要为每个组选择一些记录,这将很有效.

  • 那是*美丽*的表现,相对简单,而且很好的解释;非常感谢。到最后一点,在可以计算出合理的最大长度的情况下,可以使用SET SESSION group_concat_max_len = <maximum length>;`在OP的情况下,非问题(因为默认值为1024),但是作为示例,group_concat_max_len至少应为25:4(一年字符串的最大长度)+1(分隔符)乘以5(前5年)。字符串将被截断而不是抛出错误,因此请注意警告,例如“已设置1054行,789警告(0.31秒)”。 (3认同)

Sal*_*n A 93

所述原始查询使用的用户变量和ORDER BY上派生表; 两个怪癖的行为都无法保证.修改后的答案如下.

你可以使用穷人的级别而不是分区来达到预期的效果.只是外连接的表本身的每一行,计算行数较少比它:

SELECT t.id, t.rate, t.year, COUNT(l.rate) AS rank
FROM t
LEFT JOIN t AS l ON t.id = l.id AND t.rate < l.rate
GROUP BY t.id, t.rate, t.year
HAVING COUNT(l.rate) < 5
ORDER BY t.id, t.rate DESC, t.year
Run Code Online (Sandbox Code Playgroud)

注意:

  1. COUNT从零开始
  2. 对于排序降序,较小的行是具有较高速率的行
  3. 返回最后一个位置的所有行

结果:

| id  | rate | year | rank |
|-----|------|------|------|
| p01 |  8.0 | 2006 | 0    |
| p01 |  7.4 | 2003 | 1    |
| p01 |  6.8 | 2008 | 2    |
| p01 |  5.9 | 2001 | 3    |
| p01 |  5.3 | 2007 | 4    |
| p02 | 12.5 | 2001 | 0    |
| p02 | 12.4 | 2004 | 1    |
| p02 | 12.2 | 2002 | 2    |
| p02 | 10.3 | 2003 | 3    |
| p02 |  8.7 | 2000 | 4    |
Run Code Online (Sandbox Code Playgroud)

  • 我认为值得一提的是关键部分是ORDER BY id,因为任何id值的更改都会重新计入排名. (7认同)
  • 在较新的版本中,派生表中的"ORDER BY"可以并且通常会被忽略.这打败了目标.找到了有效的分组[_here_](http://mysql.rjweb.org/doc.php/groupwise_max). (3认同)

Vis*_*mar 20

对我来说就像

SUBSTRING_INDEX(group_concat(col_name order by desired_col_order_name), ',', N) 
Run Code Online (Sandbox Code Playgroud)

工作得很好.没有复杂的查询.


例如:每组获得前1名

SELECT 
    *
FROM
    yourtable
WHERE
    id IN (SELECT 
            SUBSTRING_INDEX(GROUP_CONCAT(id
                            ORDER BY rate DESC),
                        ',',
                        1) id
        FROM
            yourtable
        GROUP BY year)
ORDER BY rate DESC;
Run Code Online (Sandbox Code Playgroud)


bob*_*nce 9

不,你不能任意限制子查询(你可以在较新的MySQL中有限地执行,但不能为每组5个结果).

这是一个groupwise-maximum类型查询,在SQL中这不是一件容易的事.有一些方法可以解决某些问题,对某些情况来说可能更有效,但对于top-n一般来说,你会想看看Bill对之前类似问题的回答.

与此问题的大多数解决方案一样,如果存在多个具有相同rate值的行,则它可以返回超过五行,因此您可能仍需要一定量的后处理来检查该行.


Bri*_*ght 9

这需要一系列子查询来对值进行排名,限制它们,然后在分组时执行求和

@Rnk:=0;
@N:=2;
select
  c.id,
  sum(c.val)
from (
select
  b.id,
  b.bal
from (
select   
  if(@last_id=id,@Rnk+1,1) as Rnk,
  a.id,
  a.val,
  @last_id=id,
from (   
select 
  id,
  val 
from list
order by id,val desc) as a) as b
where b.rnk < @N) as c
group by c.id;
Run Code Online (Sandbox Code Playgroud)


小智 9

SELECT year, id, rate
FROM (SELECT
  year, id, rate, row_number() over (partition by id order by rate DESC)
  FROM h
  WHERE year BETWEEN 2000 AND 2009
  AND id IN (SELECT rid FROM table2)
  GROUP BY id, year
  ORDER BY id, rate DESC) as subquery
WHERE row_number <= 5
Run Code Online (Sandbox Code Playgroud)

子查询与您的查询几乎相同。唯一的变化是添加

row_number() over (partition by id order by rate DESC)
Run Code Online (Sandbox Code Playgroud)

  • 这很好,但 MySQL 没有窗口函数(如 `ROW_NUMBER()`)。 (8认同)
  • 从 MySQL 8.0 开始,`row_number()` 是 [available](https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_row-number)。 (4认同)

Sah*_*hah 9

试试这个:

SELECT h.year, h.id, h.rate 
FROM (SELECT h.year, h.id, h.rate, IF(@lastid = (@lastid:=h.id), @index:=@index+1, @index:=0) indx 
      FROM (SELECT h.year, h.id, h.rate 
            FROM h
            WHERE h.year BETWEEN 2000 AND 2009 AND id IN (SELECT rid FROM table2)
            GROUP BY id, h.year
            ORDER BY id, rate DESC
            ) h, (SELECT @lastid:='', @index:=0) AS a
    ) h 
WHERE h.indx <= 5;
Run Code Online (Sandbox Code Playgroud)


Wan*_*'an 5

像 Oracle 中的 RowID 一样构建虚拟列?

桌子:

CREATE TABLE `stack` 
(`year` int(11) DEFAULT NULL,
`id` varchar(10) DEFAULT NULL,
`rate` float DEFAULT NULL) 
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Run Code Online (Sandbox Code Playgroud)

数据:

insert into stack values(2006,'p01',8);
insert into stack values(2001,'p01',5.9);
insert into stack values(2007,'p01',5.3);
insert into stack values(2009,'p01',4.4);
insert into stack values(2001,'p02',12.5);
insert into stack values(2004,'p02',12.4);
insert into stack values(2005,'p01',2.1);
insert into stack values(2000,'p01',0.8);
insert into stack values(2002,'p02',12.2);
insert into stack values(2002,'p01',3.9);
insert into stack values(2004,'p01',3.5);
insert into stack values(2003,'p02',10.3);
insert into stack values(2000,'p02',8.7);
insert into stack values(2006,'p02',4.6);
insert into stack values(2007,'p02',3.3);
insert into stack values(2003,'p01',7.4);
insert into stack values(2008,'p01',6.8);
Run Code Online (Sandbox Code Playgroud)

像这样的 SQL:

select t3.year,t3.id,t3.rate 
from (select t1.*, (select count(*) from stack t2 where t1.rate<=t2.rate and t1.id=t2.id) as rownum from stack t1) t3 
where rownum <=3 order by id,rate DESC;
Run Code Online (Sandbox Code Playgroud)

如果删除t3中的where子句,显示如下:

在此处输入图片说明

GET "TOP N Record" --> 添加rownum <=3inwhere子句(t3的where子句);

CHOOSE "the year" --> 添加BETWEEN 2000 AND 2009inwhere子句(t3的where子句);