在mysql中基于多列确定排名

q3aa0525  于 2021-06-17  发布在  Mysql
关注(0)|答案(4)|浏览(417)

我有一个表,其中有3个领域,我想排名列的基础上,用户id和游戏id。
下面是sql fiddle:http://sqlfiddle.com/#!9月883e9d/1
我的table已经有了:

user_id | game_id |   game_detial_sum  |
 --------|---------|--------------------|
 6       | 10      |  1000              |   
 6       | 11      |  260               |
 7       | 10      |  1200              |
 7       | 11      |  500               |
 7       | 12      |  360               |
 7       | 13      |  50                |

预期产量:

user_id  | game_id |   game_detial_sum  |  user_game_rank  |
 --------|---------|--------------------|------------------|
 6       | 10      |  1000              |   1              |
 6       | 11      |  260               |   2              |
 7       | 10      |  1200              |   1              |
 7       | 11      |  500               |   2              |
 7       | 12      |  360               |   3              |
 7       | 13      |  50                |   4              |

我目前的努力:

SET @s := 0; 
SELECT user_id,game_id,game_detail, 
       CASE WHEN user_id = user_id THEN (@s:=@s+1) 
            ELSE @s = 0 
       END As user_game_rank 
FROM game_logs

编辑:(从操作注解):排序是基于 game_detail 游戏顺序详情

lmyy7pcs

lmyy7pcs1#

在派生表中(在 FROM 子句),我们对数据进行排序,使所有行具有相同的 user_id 值组合在一起,并根据 game_detail 按降序排列。
现在,我们使用这个结果集并使用条件 CASE..WHEN 用于计算行编号的表达式。它就像一种循环技术(我们在应用程序代码中使用,例如:php)。我们将前一行的值存储在用户定义的变量中,然后对照前一行检查当前行的值。最后,我们将相应地分配行号。
编辑:根据mysql文档和@gordon linoff的观察:
涉及用户变量的表达式的求值顺序未定义。例如,不能保证选择@a@a:=@a+1 首先计算@a,然后执行赋值。
我们需要计算行号并分配 user_id 价值 @u 同一表达式中的变量。

SET @r := 0, @u := 0; 
SELECT
  @r := CASE WHEN @u = dt.user_id 
                  THEN @r + 1
             WHEN @u := dt.user_id /* Notice := instead of = */
                  THEN 1 
        END AS user_game_rank, 
  dt.user_id, 
  dt.game_detail, 
  dt.game_id 

FROM 
( SELECT user_id, game_id, game_detail
  FROM game_logs 
  ORDER BY user_id, game_detail DESC 
) AS dt

结果

| user_game_rank | user_id | game_detail | game_id |
| -------------- | ------- | ----------- | ------- |
| 1              | 6       | 260         | 11      |
| 2              | 6       | 100         | 10      |
| 1              | 7       | 1200        | 10      |
| 2              | 7       | 500         | 11      |
| 3              | 7       | 260         | 12      |
| 4              | 7       | 50          | 13      |

db fiddle视图
mysql文档中的一个有趣的注解,我最近发现:
mysql的早期版本允许在set以外的语句中为用户变量赋值。为了向后兼容,MySQL8.0支持此功能,但在将来的mysql版本中可能会删除此功能。
另外,感谢so的一位成员,mysql团队发现了以下博客:https://mysqlserverteam.com/row-numbering-ranking-how-to-use-less-user-variables-in-mysql-queries/
一般的观察结果是 ORDER BY 对同一查询块中的用户变量求值时,不能确保值始终正确。因此,mysql优化器可能会出现并更改我们假定的评估顺序。
解决这个问题的最佳方法是升级到mysql 8+并利用 Row_Number() 功能:
架构(mysql v8.0)

SELECT user_id, 
       game_id, 
       game_detail, 
       ROW_NUMBER() OVER (PARTITION BY user_id 
                          ORDER BY game_detail DESC) AS user_game_rank 
FROM game_logs 
ORDER BY user_id, user_game_rank;

结果

| user_id | game_id | game_detail | user_game_rank |
| ------- | ------- | ----------- | -------------- |
| 6       | 11      | 260         | 1              |
| 6       | 10      | 100         | 2              |
| 7       | 10      | 1200        | 1              |
| 7       | 11      | 500         | 2              |
| 7       | 12      | 260         | 3              |
| 7       | 13      | 50          | 4              |

db fiddle视图

2w3rbyxf

2w3rbyxf2#

您可以使用非常简单的相关子查询:

SELECT *, (
    SELECT COUNT(DISTINCT game_detail) + 1
    FROM game_logs AS x
    WHERE user_id = t.user_id AND game_detail > t.game_detail
) AS user_game_rank
FROM game_logs AS t
ORDER BY user_id, user_game_rank

小提琴
它比用户变量更慢,但更可靠。只需要一个连接就可以打破它们。

slsn1g29

slsn1g293#

SELECT user_id, game_id, game_detail, 
       CASE WHEN user_id = @lastUserId 
            THEN @rank := @rank + 1 
            ELSE @rank := 1 
       END As user_game_rank,
       @lastUserId := user_id
FROM game_logs
cross join (select @rank := 0, @lastUserId := 0) r
order by user_id, game_detail desc

sqlfiddle演示

55ooxyrt

55ooxyrt4#

mysql版本8.0之前的最佳解决方案如下:

select gl.*, 
       (@rn := if(@lastUserId = user_id, @rn + 1,
                  if(@lastUserId := user_id, 1, 1)
                 )
        ) as user_game_rank
from (select gl.*
      from game_logs gl
      order by gl.user_id, gl.game_detail desc
     ) gl cross join
     (select @rn := 0, @lastUserId := 0) params;

排序是在子查询中完成的。从MySQL5.7开始,这是必需的。变量赋值都在一个表达式中,所以表达式求值的不同顺序无关紧要(mysql也不保证表达式求值的顺序)。

相关问题