MySQL查找每个类别的用户排名

时间:2014-09-07 21:45:07

标签: mysql

假设我有下表:

 user_id | category_id | points
 -------------------------------
       1 |           1 | 4
       2 |           1 | 2
       2 |           1 | 5
       1 |           2 | 3
       2 |           2 | 2
       1 |           3 | 1
       2 |           3 | 4
       1 |           3 | 8

有人可以帮我构建一个查询来返回每个类别的用户排名 - 如下所示:

user_id | category_id | total_points | rank
-------------------------------------------
      1 |           1 |            4 |    2
      1 |           2 |            3 |    1
      1 |           3 |            9 |    1
      2 |           1 |            7 |    1
      2 |           2 |            2 |    2
      2 |           3 |            4 |    2

3 个答案:

答案 0 :(得分:3)

首先,您需要获得每个类别的总积分。然后你需要枚举它们。在MySQL中,这很容易用变量完成:

SELECT user_id, category_id, points,
       (@rn := if(@cat = category_id, @rn + 1,
                  if(@cat := category_id, 1, 1)
                 )
       ) as rank
FROM (SELECT u.user_id, u.category_id, SUM(u.points) as points
      FROM users u
      GROUP BY u.user_id, u.category_id
     ) g cross join
     (SELEct @user := -1, @cat := -1, @rn := 0) vars
ORDER BY category_id, points desc;

答案 1 :(得分:1)

您希望获得每个唯一SUM的{​​{1}}分数:

category_id

答案 2 :(得分:0)

MySQL不像其他数据库(Oracle,SQL Server)那样具有分析功能,这对于返回这样的结果非常方便。

前三列很简单,只有GROUP BY user_id, category_idSUM(points)

返回rank列是一个问题。除了在客户端上执行此操作外,如果需要在SQL语句中执行此操作,则可以使用MySQL用户定义的变量。

SELECT @rank := IF(@prev_category = r.category_id, @rank+1, 1) AS rank 
     , @prev_category := r.category_id AS category_id
     , r.user_id
     , r.total_points
  FROM (SELECT @prev_category := NULL, @rank := 1) i 
 CROSS
  JOIN ( SELECT s.category_id, s.user_id, SUM(s.points) AS total_points
           FROM users s
         GROUP BY s.category_id, s.user_id
         ORDER BY s.category_id, total_points DESC
       ) r
ORDER BY r.category_id, r.total_points DESC, r.user_id DESC

内联视图别名为i的目的是初始化用户定义的变量。别名为r的内联视图返回每个(user_id,category_id)的total_points。

"技巧"是将前一行的category_id值与当前行的值进行比较;如果它们匹配,我们将等级增加1.如果它是"新的"类别,我们将等级重置为1.注意,这仅适用于行按类别排序,然后按total_points降序,因此我们需要ORDER BY子句。另请注意,SELECT列表中表达式的顺序很重要;我们需要在用当前值覆盖之前对之前的值进行比较,因此对@prev_category的赋值必须遵循条件测试。

另请注意,如果两个用户在一个类别中具有相同的total_points,则他们将获得排名的不同值...上面的查询不会为平局提供相同的排名。 (可以修改查询以执行此操作,但我们还需要保留前一行的total_points,因此我们可以与当前行进行比较。

另请注意,此语法特定于MySQL,并且这种行为保证。


如果您需要特定顺序中的列和/或特定顺序的行(为了获得指定的确切结果集),我们需要将上面的查询包装为内联视图。

SELECT t.user_id
     , t.category_id
     , t.total_points
     , t.rank 
  FROM (
         SELECT @rank := IF(@prev_category = r.category_id, @rank+1, 1) AS rank 
              , @prev_category := r.category_id AS category_id
              , r.user_id
              , r.total_points
           FROM (SELECT @prev_categor := NULL, @rank := 1) i 
          CROSS
           JOIN ( SELECT s.category_id, s.user_id, SUM(s.points) AS total_points
                    FROM users s
                  GROUP BY s.category_id, s.user_id
                  ORDER BY s.category_id, total_points DESC
                ) r
         ORDER BY r.category_id, r.total_points DESC, r.user_id DESC
      ) t
  ORDER BY t.user_id, t.category_id

注意:我没有设置SQL Fiddle演示。我给出了一个只经过桌面检查的示例查询。