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

Yar*_*rin 132 mysql sql greatest-n-per-group mysql-variables

以下是最简单的可能示例,但任何解决方案都应该能够扩展到需要的n个顶级结果:

根据下面的表格,使用人员,组和年龄列,您将如何获得每组中最老的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`
Run Code Online (Sandbox Code Playgroud)

我希望能够建立起来,但我不知道如何.

Tar*_*ryn 84

这是一种方法,使用UNION ALL(参见SQL Fiddle with Demo).这适用于两个组,如果您有两个以上的组,那么您需要指定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
)
Run Code Online (Sandbox Code Playgroud)

有多种方法可以执行此操作,请参阅此文章以确定适合您情况的最佳路径:

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;
Run Code Online (Sandbox Code Playgroud)

演示

  • 如果他有1 000多个团体,那会不会让这有点吓人? (47认同)
  • 任何阅读本文的人请注意:该版本的变量接近正确。然而,MySQL 不保证“SELECT”中表达式求值的顺序(事实上,有时会乱序求值)。解决方案的关键是将所有变量赋值放在一个表达式中;这是一个示例:http://stackoverflow.com/questions/38535020/getting-the-latest-n-records-for-each-group/38535249#38535249。 (2认同)

Mar*_*ers 62

在其他数据库中,您可以使用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
Run Code Online (Sandbox Code Playgroud)

看到它在线工作:sqlfiddle


编辑我刚注意到bluefeet发布了一个非常相似的答案:给他+1.然而,这个答案有两个小优点:

  1. 这是一个单一的查询.变量在SELECT语句中初始化.
  2. 它处理问题中描述的关系(按名称的字母顺序).

所以我会留在这里,以防它可以帮助某人.

  • 很好的解决方案,但它似乎不适用于我的环境(MySQL 5.6),因为order by子句在select之后应用,所以它不返回最高结果,请参阅我的替代解决方案来解决此问题 (3认同)
  • 马克 - 这对我们来说效果很好。感谢您提供另一个很好的替代品来赞美@bluefeet-非常感谢。 (2认同)

snu*_*ffn 36

试试这个:

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
Run Code Online (Sandbox Code Playgroud)

DEMO

  • 使用最简单的解决方案,snuffin无处不在!这比Ludo/[Bill Karwin的](http://stackoverflow.com/a/1442867/165673)更优雅吗?我能得到一些评论吗? (5认同)
  • 这有问题.如果组内第二位有并列,则只返回一个最高结果.见修改[demo](http://sqlfiddle.com/#!2/b6ce1/1) (2认同)
  • 如果需要的话,这不是问题.您可以设置`a.person`的顺序. (2认同)

小智 31

如何使用自我加入:

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;
Run Code Online (Sandbox Code Playgroud)

给我:

a.person    a.groupname  a.age     
----------  -----------  ----------
Shawn       1            42        
Jill        1            34        
Laura       2            39        
Paul        2            36      
Run Code Online (Sandbox Code Playgroud)

Bill Karwin给每个类别选择前10条记录的答案给了我很大的启发

另外,我正在使用SQLite,但这应该适用于MySQL.

另一件事:在上面,为方便起见,我用group列替换了列groupname.

编辑:

关于OP关于缺失领带结果的评​​论的后续跟进,我在snuffin的回答中增加了显示所有关系.这意味着如果最后一个是tie,则可以返回超过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;
Run Code Online (Sandbox Code Playgroud)

给我:

person      groupname   age       
----------  ----------  ----------
Shawn       1           42        
Jill        1           34        
Laura       2           39        
Paul        2           36        
Joe         2           36        
Chuck       3           112      
Run Code Online (Sandbox Code Playgroud)

  • 这有问题.如果组内第二位并列,则只返回一个最高结果 - 参见[demo](http://sqlfiddle.com/#!2/b6ce1/3) (2认同)

Tra*_*ty3 10

看一下这个:

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;
Run Code Online (Sandbox Code Playgroud)

SQL小提琴:http://sqlfiddle.com/#!2/cdbb6/15

  • 男人,其他人找到了更简单的解决方案......我只花了15分钟就完成了这项工作,并为自己提出如此复杂的解决方案感到非常自豪.太糟糕了. (5认同)
  • @Keep你的头Travesty3-赞成你的努力,谢谢 (2认同)

Lau*_*ELE 9

当你有足够的行时,Snuffin解决方案似乎执行起来很慢而且Mark Byers/Rick James和Bluefeet解决方案在我的环境(MySQL 5.6)上不起作用,因为在执行select之后应用order by,所以这里是一个变体Marc Byers/Rick James解决方案解决了这个问题(带有额外的叠加选择):

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;
Run Code Online (Sandbox Code Playgroud)

我在具有5百万行的表上尝试了类似的查询,并在不到3秒的时间内返回结果

  • 这是我环境中唯一的查询.谢谢! (3认同)
  • 使用`ORDER BY`将`LIMIT 9999999`添加到任何派生表中.这个_may_可以防止`ORDER BY`被忽略. (3认同)

Hir*_*ren 9

WITH cte_window AS (
SELECT movie_name,director_id,release_date,
ROW_NUMBER() OVER( PARTITION BY director_id ORDER BY release_date DESC) r
FROM movies
)   
SELECT * FROM cte_window WHERE r <= <n>;
Run Code Online (Sandbox Code Playgroud)

上面的查询将返回每个导演的最新 n 部电影。


Ric*_*mes 6

如果其他答案不够快,请尝试使用此代码:

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;
Run Code Online (Sandbox Code Playgroud)

输出:

+---------------------------+------+------------------+------------+
| 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 | ...
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

143392 次

最近记录:

7 年,7 月 前