获取每组分组结果的前n条记录

wn9m85ua  于 2021-06-24  发布在  Mysql
关注(0)|答案(11)|浏览(383)

下面是一个最简单的例子,尽管任何解决方案都应该能够扩展到需要多少n个top结果:
如果有一个这样的表格,有person、group和age列,那么如何得到每组中2个年龄最大的人(组内的关系不应产生更多结果,但应按字母顺序给出前2个结果)

+--------+-------+-----+
| Person | Group | Age |
+--------+-------+-----+
| Bob    | 1     | 32  |
| Jill   | 1     | 34  |
| Shawn  | 1     | 42  |
| Jake   | 2     | 29  |
| Paul   | 2     | 36  |
| Laura  | 2     | 39  |
+--------+-------+-----+

期望结果集:

+--------+-------+-----+
| Shawn  | 1     | 42  |
| Jill   | 1     | 34  |
| Laura  | 2     | 39  |
| Paul   | 2     | 36  |
+--------+-------+-----+

注意:这个问题建立在前一个问题的基础上——获取每组分组sql结果的最大值记录——从每个组中获取一个顶行,并从@bohemian得到了一个很好的mysql特定答案:

select * 
from (select * from mytable order by `Group`, Age desc, Person) x
group by `Group`

我很想以此为基础,尽管我不知道怎么做。

eqzww0vc

eqzww0vc1#

SELECT
p1.Person,
p1.`GROUP`,
p1.Age  
   FROM
person AS p1 
 WHERE
(
SELECT
    COUNT( DISTINCT ( p2.age ) ) 
FROM
    person AS p2 
WHERE
    p2.`GROUP` = p1.`GROUP` 
    AND p2.Age >= p1.Age 
) < 2 
ORDER BY
p1.`GROUP` ASC,
p1.age DESC

参考leetcode

q9yhzks0

q9yhzks02#

snuffin解决方案在有大量行的情况下执行起来似乎相当慢,而且mark byers/rick james和bluefeet解决方案在我的环境(mysql 5.6)上不起作用,因为order by是在执行select之后应用的,所以下面是marc byers/rick james解决方案的一个变体来解决这个问题(使用一个额外的重叠select):

select person, groupname, age
from
(
    select person, groupname, age,
    (@rn:=if(@prev = groupname, @rn +1, 1)) as rownumb,
    @prev:= groupname 
    from 
    (
        select person, groupname, age
        from persons 
        order by groupname ,  age desc, person
    )   as sortedlist
    JOIN (select @prev:=NULL, @rn :=0) as vars
) as groupedlist 
where rownumb<=2
order by groupname ,  age desc, person;

我在一个有500万行的表上尝试了类似的查询,结果在3秒内返回

z5btuh9x

z5btuh9x3#

看看这个:

SELECT
  p.Person,
  p.`Group`,
  p.Age
FROM
  people p
  INNER JOIN
  (
    SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`
    UNION
    SELECT MAX(p3.Age) AS Age, p3.`Group` FROM people p3 INNER JOIN (SELECT MAX(Age) AS Age, `Group` FROM people GROUP BY `Group`) p4 ON p3.Age < p4.Age AND p3.`Group` = p4.`Group` GROUP BY `Group`
  ) p2 ON p.Age = p2.Age AND p.`Group` = p2.`Group`
ORDER BY
  `Group`,
  Age DESC,
  Person;

sql小提琴:http://sqlfiddle.com/#!2015年6月2日

h6my8fg2

h6my8fg24#

试试这个:

SELECT a.person, a.group, a.age FROM person AS a WHERE 
(SELECT COUNT(*) FROM person AS b 
WHERE b.group = a.group AND b.age >= a.age) <= 2 
ORDER BY a.group ASC, a.age DESC

演示

1l5u6lss

1l5u6lss5#

在其他数据库中,可以使用 ROW_NUMBER . mysql不支持 ROW_NUMBER 但您可以使用变量来模拟它:

SELECT
    person,
    groupname,
    age
FROM
(
    SELECT
        person,
        groupname,
        age,
        @rn := IF(@prev = groupname, @rn + 1, 1) AS rn,
        @prev := groupname
    FROM mytable
    JOIN (SELECT @prev := NULL, @rn := 0) AS vars
    ORDER BY groupname, age DESC, person
) AS T1
WHERE rn <= 2

在线查看:sqlfiddle
编辑我刚刚注意到蓝脚报给他一个非常相似的答案:+1。然而,这个答案有两个小优点:
这是一个单一的查询。变量在select语句中初始化。
它处理问题中描述的关系(按名称的字母顺序)。
所以我把它放在这里,以防它能帮助别人。

fsi0uk1n

fsi0uk1n6#

在sql server中 row_numer() 是一个强大的函数,可以很容易地得到如下结果

select Person,[group],age
from
(
select * ,row_number() over(partition by [group] order by age desc) rn
from mytable
) t
where rn <= 2
lyr7nygr

lyr7nygr7#

如果其他答案不够快,请尝试以下代码:

SELECT
        province, n, city, population
    FROM
      ( SELECT  @prev := '', @n := 0 ) init
    JOIN
      ( SELECT  @n := if(province != @prev, 1, @n + 1) AS n,
                @prev := province,
                province, city, population
            FROM  Canada
            ORDER BY
                province   ASC,
                population DESC
      ) x
    WHERE  n <= 3
    ORDER BY  province, n;

输出:

+---------------------------+------+------------------+------------+
| province                  | n    | city             | population |
+---------------------------+------+------------------+------------+
| Alberta                   |    1 | Calgary          |     968475 |
| Alberta                   |    2 | Edmonton         |     822319 |
| Alberta                   |    3 | Red Deer         |      73595 |
| British Columbia          |    1 | Vancouver        |    1837970 |
| British Columbia          |    2 | Victoria         |     289625 |
| British Columbia          |    3 | Abbotsford       |     151685 |
| Manitoba                  |    1 | ...
q7solyqu

q7solyqu8#

如何使用自连接:

CREATE TABLE mytable (person, groupname, age);
INSERT INTO mytable VALUES('Bob',1,32);
INSERT INTO mytable VALUES('Jill',1,34);
INSERT INTO mytable VALUES('Shawn',1,42);
INSERT INTO mytable VALUES('Jake',2,29);
INSERT INTO mytable VALUES('Paul',2,36);
INSERT INTO mytable VALUES('Laura',2,39);

SELECT a.* FROM mytable AS a
  LEFT JOIN mytable AS a2 
    ON a.groupname = a2.groupname AND a.age <= a2.age
GROUP BY a.person
HAVING COUNT(*) <= 2
ORDER BY a.groupname, a.age DESC;

给了我:

a.person    a.groupname  a.age     
----------  -----------  ----------
Shawn       1            42        
Jill        1            34        
Laura       2            39        
Paul        2            36

比尔·卡尔温的回答给了我很大的启发,他为每个类别选出了十大唱片
另外,我正在使用sqlite,但这应该可以在mysql上使用。
另一件事:在上面,我替换了 group 带有 groupname 为方便起见。
编辑:
根据op关于缺少平局结果的评论,我增加了snuffin的答案来显示所有的平局。这意味着如果最后一行是ties,则可以返回2行以上的数据,如下所示:

.headers on
.mode column

CREATE TABLE foo (person, groupname, age);
INSERT INTO foo VALUES('Paul',2,36);
INSERT INTO foo VALUES('Laura',2,39);
INSERT INTO foo VALUES('Joe',2,36);
INSERT INTO foo VALUES('Bob',1,32);
INSERT INTO foo VALUES('Jill',1,34);
INSERT INTO foo VALUES('Shawn',1,42);
INSERT INTO foo VALUES('Jake',2,29);
INSERT INTO foo VALUES('James',2,15);
INSERT INTO foo VALUES('Fred',1,12);
INSERT INTO foo VALUES('Chuck',3,112);

SELECT a.person, a.groupname, a.age 
FROM foo AS a 
WHERE a.age >= (SELECT MIN(b.age)
                FROM foo AS b 
                WHERE (SELECT COUNT(*)
                       FROM foo AS c
                       WHERE c.groupname = b.groupname AND c.age >= b.age) <= 2
                GROUP BY b.groupname)
ORDER BY a.groupname ASC, a.age DESC;

给了我:

person      groupname   age       
----------  ----------  ----------
Shawn       1           42        
Jill        1           34        
Laura       2           39        
Paul        2           36        
Joe         2           36        
Chuck       3           112
2uluyalo

2uluyalo9#

在mysql中,有一个非常好的答案来解决这个问题——如何获得每组前n行
根据引用链接中的解决方案,您的查询如下:

SELECT Person, Group, Age
   FROM
     (SELECT Person, Group, Age, 
                  @group_rank := IF(@group = Group, @group_rank + 1, 1) AS group_rank,
                  @current_group := Group 
       FROM `your_table`
       ORDER BY Group, Age DESC
     ) ranked
   WHERE group_rank <= `n`
   ORDER BY Group, Age DESC;

哪里 ntop n 以及 your_table 表的名称。
我认为参考文献中的解释非常清楚。为了快速参考,我将复制并粘贴到这里:
目前mysql不支持row_number()函数,该函数可以在一个组中分配一个序列号,但是作为一种解决方法,我们可以使用mysql会话变量。
这些变量不需要声明,可以在查询中用于进行计算和存储中间结果。
@current\u country:=country此代码对每行执行,并将country列的值存储到@current\u country变量。
@country\u rank:=if(@current\u country=country,@country\u rank+1,1)在本代码中,如果@current\u country是相同的我们递增rank,否则设置为1。对于第一行,@current\u country为空,因此rank也设置为1。
为了获得正确的排名,我们需要按国家、人口、人口和性别排序

pprl5pva

pprl5pva10#

这里有一种方法,使用 UNION ALL (参见SQLFiddle演示)。这适用于两个组,如果有两个以上的组,则需要指定 group 为每个查询编号并添加查询 group :

(
  select *
  from mytable 
  where `group` = 1
  order by age desc
  LIMIT 2
)
UNION ALL
(
  select *
  from mytable 
  where `group` = 2
  order by age desc
  LIMIT 2
)

有多种方法可以做到这一点,请参阅本文以确定适合您的情况的最佳路线:
http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/
编辑:
这可能也适用于您,它为每条记录生成一个行号。使用上面链接中的示例,这将仅返回行号小于或等于2的记录:

select person, `group`, age
from 
(
   select person, `group`, age,
      (@num:=if(@group = `group`, @num +1, if(@group := `group`, 1, 1))) row_number 
  from test t
  CROSS JOIN (select @num:=0, @group:=null) c
  order by `Group`, Age desc, person
) as x 
where x.row_number <= 2;

请参见演示

7qhs6swi

7qhs6swi11#

我想和大家分享这一点,因为我花了很长时间寻找一种简单的方法来在我正在开发的java程序中实现这一点。这并不能给出你想要的结果,但已经很接近了。mysql中的函数 GROUP_CONCAT() 在指定每组返回多少个结果方面效果非常好。使用 LIMIT 或者其他任何一种奇特的方法 COUNT 不适合我。因此,如果您愿意接受修改后的输出,这是一个很好的解决方案。假设我有一个名为“学生”的表格,上面有学生ID、性别和平均绩点。假设我希望每个性别的平均绩点达到前5名。然后我可以这样写查询

SELECT sex, SUBSTRING_INDEX(GROUP_CONCAT(cast(gpa AS char ) ORDER BY gpa desc), ',',5) 
AS subcategories FROM student GROUP BY sex;

请注意,参数“5”告诉它每行要连接多少个条目
输出结果看起来像

+--------+----------------+
| Male   | 4,4,4,4,3.9    |
| Female | 4,4,3.9,3.9,3.8|
+--------+----------------+

您还可以更改 ORDER BY 变量并以不同的方式排序。因此,如果我有学生的年龄,我可以取代'平均绩点下降'与'年龄下降',这将工作!还可以向GROUPBY语句中添加变量,以在输出中获得更多的列。所以这是我发现的一种非常灵活的方法,如果你对列出的结果满意的话,它会很好的工作。

相关问题