根据 MySQL 中的多列确定排名

Determine Rank based on Multiple Columns in MySQL

我有一个 table,它有 3 个字段,我想根据 user_id 和 game_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

编辑:(来自OP ):排序是按照game_detail

的降序排列

order of game_detail

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 Demo

Derived TableFROM 子句内的子查询)中,我们对数据进行排序,使所有具有相同 user_id 值的行聚集在一起,并根据它们之间的进一步排序在 game_detail 上按降序排列。

现在,我们使用此结果集并使用条件 CASE..WHEN 表达式来计算行编号。它将类似于循环技术(我们在应用程序代码中使用它,例如:PHP)。我们会将前一行的值存储在用户定义的变量中,然后对照前一行检查当前行的值。最终,我们将相应地分配行号。

编辑: 基于 MySQL docs 和@Gordon Linoff 的观察:

The order of evaluation for expressions involving user variables is undefined. For example, there is no guarantee that SELECT @a, @a:=@a+1 evaluates @a first and then performs the assignment.

我们需要评估行号并将 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      |

View on DB Fiddle


我最近发现的 MySQL Docs 的有趣注释:

Previous releases of MySQL made it possible to assign a value to a user variable in statements other than SET. This functionality is supported in MySQL 8.0 for backward compatibility but is subject to removal in a future release of 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              |

View on DB Fiddle

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;

排序是在子查询中完成的。这是从 MySQL 5.7 开始需要的。变量赋值都在一个表达式中,因此表达式求值顺序不同无关紧要(并且 MySQL 不保证表达式求值顺序)。

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

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

DB Fiddle

它比用户变量更慢但更可靠。只需要一个 JOIN 就可以打破它们。