MySQL 查询每组前几名的数据(2021-04-28)

  1. 假设我们要查询 group_order_test 表的每个分组的前 4 名的数据。先看看表的结构和数据:

    mysql> select * from group_order_test;
    +------------+-------+
    | group_name | value |
    +------------+-------+
    | a          | 1     |
    | a          | 2     |
    | a          | 3     |
    | a          | 4     |
    | a          | 5     |
    | b          | 11    |
    | b          | 22    |
    | b          | 33    |
    | b          | 44    |
    | b          | 55    |
    | b          | 66    |
    | c          | 111   |
    | c          | 222   |
    | c          | 333   |
    +------------+-------+
    14 rows in set
  2. 我们知道怎么查询某个组的第几名数据,比如查 a 组的第 4 名数据如下,注意 limit 后的数字比所需要的数字小 1:

    mysql> select * from group_order_test where group_name = 'a' order by value asc limit 3, 1;
    +------------+-------+
    | group_name | value |
    +------------+-------+
    | a          | 4     |
    +------------+-------+
    1 row in set
  3. 我们把上述查询结合到表数据中,这样我们增加了一列第 4 名的数值:

    mysql> select
        group_name,
        value,
        (select value from group_order_test as sub_table where sub_table.group_name = main_table.group_name order by value asc limit 3,1) as order_value
    from group_order_test as main_table;
    +------------+-------+-------------+
    | group_name | value | order_value |
    +------------+-------+-------------+
    | a          | 1     | 4           |
    | a          | 2     | 4           |
    | a          | 3     | 4           |
    | a          | 4     | 4           |
    | a          | 5     | 4           |
    | b          | 11    | 44          |
    | b          | 22    | 44          |
    | b          | 33    | 44          |
    | b          | 44    | 44          |
    | b          | 55    | 44          |
    | b          | 66    | 44          |
    | c          | 111   | NULL        |
    | c          | 222   | NULL        |
    | c          | 333   | NULL        |
    +------------+-------+-------------+
    14 rows in set
  4. 这时候要查询前 4 名的数据就好说了,直接比较大小:

    mysql> select group_name, value
    from
    (
        select
            group_name,
            value,
            (select value from group_order_test as sub_table where sub_table.group_name = main_table.group_name order by value asc limit 3,1) as order_value
        from group_order_test as main_table
    ) as t
    where value <= order_value;
    +------------+-------+
    | group_name | value |
    +------------+-------+
    | a          | 1     |
    | a          | 2     |
    | a          | 3     |
    | a          | 4     |
    | b          | 11    |
    | b          | 22    |
    | b          | 33    |
    | b          | 44    |
    +------------+-------+
    8 rows in set
  5. 等等,上面的 c 组呢?原来 NULL 值影响了,这样的话我们还得对少于所要排名的数据做一个处理,我们用相应组的最大值来做补充,如果没有相应排名的数值的话就用最大值来做比较:

    mysql> select *
    from
    (
        select
            group_name,
            value,
            (select value from group_order_test as sub_table where sub_table.group_name = main_table.group_name order by value asc limit 3,1) as order_value,
            (select max(value) from group_order_test as sub_table where sub_table.group_name = main_table.group_name group by group_name) as max_value
        from group_order_test as main_table
    ) as t
    where value <= ifnull(order_value, max_value);
    +------------+-------+-------------+-----------+
    | group_name | value | order_value | max_value |
    +------------+-------+-------------+-----------+
    | a          | 1     | 4           | 5         |
    | a          | 2     | 4           | 5         |
    | a          | 3     | 4           | 5         |
    | a          | 4     | 4           | 5         |
    | b          | 11    | 44          | 66        |
    | b          | 22    | 44          | 66        |
    | b          | 33    | 44          | 66        |
    | b          | 44    | 44          | 66        |
    | c          | 111   | NULL        | 333       |
    | c          | 222   | NULL        | 333       |
    | c          | 333   | NULL        | 333       |
    +------------+-------+-------------+-----------+
    11 rows in set
  6. 这下再做比较就能得到正确的前 4 名的数据了:

    mysql> select group_name, value
    from
    (
        select
            group_name,
            value,
            (select value from group_order_test as sub_table where sub_table.group_name = main_table.group_name order by value asc limit 3,1) as order_value,
            (select max(value) from group_order_test as sub_table where sub_table.group_name = main_table.group_name group by group_name) as max_value
        from group_order_test as main_table
    ) as t
    where value <= ifnull(order_value, max_value);
    +------------+-------+
    | group_name | value |
    +------------+-------+
    | a          | 1     |
    | a          | 2     |
    | a          | 3     |
    | a          | 4     |
    | b          | 11    |
    | b          | 22    |
    | b          | 33    |
    | b          | 44    |
    | c          | 111   |
    | c          | 222   |
    | c          | 333   |
    +------------+-------+
    11 rows in set
  7. 如果语句以后某个时候还要用,并且排名不确定,可以把排名做为一个变量来传入,这时候由于 limit 中不能用变量,可以使用预编译语句来实现:

    mysql>
    set @order := 3;
     
    set @limit_order := @order - 1;
    prepare stmt from '
                select group_name, value
                from
                (
                    select
                        group_name,
                        value,
                        (select value from group_order_test as sub_table where sub_table.group_name = main_table.group_name order by value asc limit ?,1) as order_value,
                        (select max(value) from group_order_test as sub_table where sub_table.group_name = main_table.group_name group by group_name) as max_value
                    from group_order_test as main_table
                ) as t
                where value <= ifnull(order_value, max_value);';
    execute stmt using @limit_order;
    deallocate prepare stmt;
     
    Query OK, 0 rows affected
     
    Query OK, 0 rows affected
     
    Query OK, 0 rows affected
    Statement prepared
     
    +------------+-------+
    | group_name | value |
    +------------+-------+
    | a          | 1     |
    | a          | 2     |
    | a          | 3     |
    | b          | 11    |
    | b          | 22    |
    | b          | 33    |
    | c          | 111   |
    | c          | 222   |
    | c          | 333   |
    +------------+-------+
    9 rows in set
     
    Query OK, 0 rows affected
  8. 如果经常使用,使用存储过程也是个方便的主意,此处略
posted @ 2021-04-28 11:13  听听海看看云  阅读(641)  评论(0编辑  收藏  举报