SQL选择组的第n个成员

时间:2009-01-20 20:52:29

标签: mysql group-by

如果我有一个像

这样的USER表
class | age
--------------
1       20    
3       56
2       11
1       12
2       20

然后我可以通过

轻松获得每个班级中最年轻的用户
select class, min(age)
from   user
group by class;

同样,通过用max替换min,我可以得到最老的。但是,我怎样才能在每个班级中获得第10个最年轻(或最老)的 ?顺便说一下,我正在使用MySql v.5.0。

干杯,

7 个答案:

答案 0 :(得分:19)

SELECT a.class,
(
    SELECT b.age 
    FROM users b 
    WHERE b.class = a.class
    ORDER BY age 
    LIMIT 1,1
) as age
FROM users a
GROUP BY a.class

每个班级将获得第二名最年轻的学生。如果您想要第10个最年轻的,那么您需要LIMIT 9,1,如果您想要第10个最老的,那么您需要ORDER BY age DESC

答案 1 :(得分:9)

此处N显示Nth条记录oldest

SELECT *
FROM users k
WHERE N = (SELECT
             COUNT( DISTINCT age)
           FROM users u
           WHERE k.age >= u.age
               AND k.class = u.class
           GROUP BY u.class)

并提供Nth条记录youngest

SELECT *
FROM users k
WHERE N = (SELECT
             COUNT(DISTINCT age)
           FROM users u
           WHERE k.age <= u.age
               AND k.class = u.class
           GROUP BY u.class)

答案 2 :(得分:2)

唯一独立于sql的方式(即使你没有子查询mysql&lt; 5)

 select  u1.class, u1.age, count(*)  from      user u1 join user u2 
 on u1.class = u2.class and u1.age >= u2.age
 group by u1.class, u1.age
 having count(*) = [number]

获得每个班级[数字]最老的

 select  u1.class, u1.age, count(*)  from      user u1 join user u2 
 on u1.class = u2.class and u1.age <= u2.age
 group by u1.class, u1.age
 having count(*) = [number]

获得每个班级最年轻的[数字]

如果两个人的年龄相同,则可能无法恢复。如果您只想返回其中一个,则需要一个唯一的密钥,查询会更复杂。

答案 3 :(得分:1)

另一种方法是将SQL查询放置在for循环中,每次将新的最小值添加到不断增长的元组中,然后使用WHERE ... NOT IN子句将其过滤掉。我不确定这样做的速度,但是对我来说,它对于非常大的数据集(> 180,000条记录)非常有效。抱歉将SQL和Python混合使用,我是两者的新手。我知道有可能在SQL中执行循环,但我不知道。

        idx = tuple()
        for i in range(n):

            qry = '''SELECT class, min(age)
                     FROM user
                     WHERE age NOT IN {}
                     GROUP BY class'''.format(idx)

            cur.execute(qry)
            data = cur.fetchall()
            d = pd.DataFrame(data)

            idx = idx + tuple(d['min(age)'])

答案 4 :(得分:0)

在SQL Server中非常简单:

select 
  *
from(
   select 
      *,
      row_number() over(order by age asc) as eldest
   from class order by age asc) a
where a.eldest = 10

遵循这种模式,对于MySQL,我想你想看看这个:http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/

答案 5 :(得分:0)

 SELECT 
     userid,  
     class,  
     age,  
     (    SELECT COUNT(1) FROM user  
          WHERE class = c.class  AND age > u.age  
     ) AS oldercount  
FROM user AS u  
WHERE oldercount = 9
GROUP BY class

 SELECT userid,  
         class,  
         age  
  FROM user AS u    
  WHERE (SELECT COUNT(1) FROM class WHERE class = c.class AND age > u.age) = 9  
GROUP BY class

答案 6 :(得分:0)

任何自己加入表格的答案都会产生一个平方法......

- a JOIN b ON a.class = b.class AND a.age >= b.age  
- on average the >= condition will be true for half the class  

- 6 people in a class  
->6*6/2 = 18

- 10 people in a class
->10*10/2 = 50

-> very rapid growth

随着桌子尺寸的增长,性能会迅速下降。如果你保持小而且不会增长太多,这是一个问题吗?你在那里打电话......

替代方案涉及更多代码,但线性增长......

  • 首先,将所有记录插入到新表中,其中IDENTITY字段按Class排序然后按年龄
  • 现在,对于每个班级,找到MIN(id)
  • 现在,对于每个班级,rinf记录在哪里= MIN(id)+ 8(对于第9个老人)

有很多方法可以完成最后两个步骤。我个人会用......

SELECT
    [USER_WITH_IDS].id,
    [USER_WITH_IDS].class,
    [USER_WITH_IDS].age
FROM
    [USER_WITH_IDS]
WHERE
    [USER_WITH_IDS].id = (
                          SELECT
                              MIN([min].ID) + 8
                          FROM
                              [USER_WITH_IDS] AS [min]
                          WHERE
                              [min].class = [USER_WITH_IDS].class
                         )

这给出的是......

  • 创建新ID的一次传递
  • 获得每个班级的MIN(id)的一次通过
  • 获得所需记录的一遍

  • 根据优化器的优劣程度,使用索引(类然后是id)将允许它将最后2次传递合并为1次传递。

2或3次传球,无论桌子或班级规模有多大。线性,而不是平方律...