根据 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 Table(FROM
子句内的子查询)中,我们对数据进行排序,使所有具有相同 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 |
我最近发现的 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 |
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
它比用户变量更慢但更可靠。只需要一个 JOIN 就可以打破它们。
我有一个 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 Table(FROM
子句内的子查询)中,我们对数据进行排序,使所有具有相同 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 |
我最近发现的 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 |
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
它比用户变量更慢但更可靠。只需要一个 JOIN 就可以打破它们。