【问题标题】:SQL select nth member of groupSQL 选择组的第 n 个成员
【发布时间】:2009-01-20 20:52:28
【问题描述】:

如果我有一个像这样的 USER 表

class | age
--------------
1       20    
3       56
2       11
1       12
2       20

然后我可以很容易地通过以下方式获得每个班级中最年轻的用户

select class, min(age)
from   user
group by class;

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

干杯,

【问题讨论】:

    标签: mysql group-by


    【解决方案1】:
    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

    【讨论】:

    • 这将给我整体排名第 10 位,而不是每个班级中第 10 位。
    • 这似乎不适用于 MySQL v5.5:错误 1235 (42000):此版本的 MySQL 尚不支持 'LIMIT & IN/ALL/ANY/SOME 子查询'跨度>
    【解决方案2】:

    这里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)
    

    【讨论】:

      【解决方案3】:

      唯一的sql独立方式(即使你没有子查询mysql

       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]
      

      让你成为每个班级中年龄最大的 [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]
      

      让你成为每个班级最年轻的 [number] 个

      如果两个人的年龄相同,则可能无法正常工作,因为两者都会被退回。如果您只想返回其中一个,则需要一个唯一键,并且查询会更复杂。

      【讨论】:

        【解决方案4】:

        任何在其上加入表格的答案都将创建一个平方律...

        - 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 字段,按类别排序,然后是年龄
        • 现在,对于每个类,找到 MIN(id)
        • 现在,对于每个班级,查找记录,其中 is = 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 次传球,无论桌子或班级人数有多大。线性,而不是平方律...

        【讨论】:

          【解决方案5】:

          另一种方法是将 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)'])
          

          【讨论】:

            【解决方案6】:

            在 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/

            【讨论】:

            • 链接中的方法确实有效,但它有点像 hack。它也非常快。一次性罚款,生产不!
            【解决方案7】:
             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
            

            【讨论】:

            • 如果两个人的年龄相同并且您只想退回其中一个人,则还需要一个唯一的密钥。
            • 第一个查询导致错误:“'where子句'中的未知列'younger'”
            • 两者都为源表中的每条记录重新扫描整个类。应该可以在不触及平方律的情况下实现......
            猜你喜欢
            • 2023-03-25
            • 2018-03-24
            • 2019-01-03
            • 2020-07-27
            • 2015-05-30
            • 1970-01-01
            • 2022-12-12
            • 1970-01-01
            • 2022-01-23
            相关资源
            最近更新 更多