首先:您想要的功能称为sliding window
和ranking
。Oracle 使用OVER
-keyword 和rank()
-function 来实现这些。MySQL 不支持这些特性,所以我们必须解决这个问题。
我使用这个答案创建了以下查询。+1
如果这对你有帮助,也给他一个。
SELECT
`player_id`, `event`, `points`,
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) AS `rank`
FROM
`points` `l`
这将输出每个player_id
和event
的等级points
。例如:假设(player_id, event, points)
has(1,A,10), (1,A,5), (1,A,2), (1,A,2), (1,A,1), (2,A,0)
那么输出将是
player_id event points rank
1 A 10 1
1 A 5 2
1 A 2 3
1 A 2 3
1 A 1 5
2 A 0 1
秩并不密集,所以如果你有重复的元组,你将有相同秩的输出元组以及你的秩数中的间隙。
要获取N
每个的 top * 元组player_id
,event
您可以创建一个视图或在条件中使用子查询。视图是首选方式,但您无权在许多服务器上创建视图。
创建一个包含rank
as 列的视图。
CREATE VIEW `points_view`
AS SELECT
`player_id`, `event`, `points`,
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) as `rank`
FROM
`points` `l`
从视图中获取所需的前 N 个结果:
SELECT
`player_id`, `event`, `points`
FROM `points_view`
WHERE
`event` = 'A' AND `rank` <= 5
OR
`event` = 'B' AND `rank` <= 2
OR
`event` = 'C' AND `rank` <= 3
使用条件中的等级
SELECT
`player_id`, `event`, `points`
FROM
`points` `l`
WHERE
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= N
要根据您的事件进一步获得不同数量的元组,您可以这样做
SELECT
`player_id`, `event`, `points`
FROM
`points` `l`
WHERE
`event` = 'A' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 5
OR
`event` = 'B' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 2
OR
`event` = 'C' AND
(SELECT 1 + count(*)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` > `l`.`points`
) <= 3
我将只使用您的最大值N
5 并忽略其他事件类型的其他元组,因为 MySQL 不会优化此查询,这会导致 3 个单独的依赖子查询。如果性能不是问题,或者您没有太多数据,请保持这种状态。
* 正如我所解释的那样,rank
它并不密集,因此获取所有元组rank <= N
通常会导致多于N
元组。额外的元组是重复的。
从示例表中可以看出,简单地删除重复项是一个坏主意。如果您想要 and 的前 5 个结果player_id = 1
,则event = A
需要两个元组(1,A,2)
。他们都有排名 3。但如果你删除其中一个,你最终只会得到前 4 个结果(1,A,10,1)
, (1,A,5,2)
, (1,A,2,3)
, (1,A,1,5)
。
要获得密集排名,您可以使用此子查询
(SELECT count(DISTINCT `points`)
FROM `points`
WHERE `l`.`player_id` = `player_id`
AND `l`.`event` = `event`
AND `points` >= `l`.`points`
) as `dense_rank`
小心,因为这仍然会产生重复的等级。
编辑
要将所有事件的分数加起来为一个分数,请使用GROUP BY
SELECT
`player_id`, SUM(`points`)
FROM `points_view`
WHERE
`event` = 'A' AND `rank` <= 5
OR
`event` = 'B' AND `rank` <= 2
OR
`event` = 'C' AND `rank` <= 3
GROUP BY `player_id`
ORDER BY SUM(`points`) DESC
在分区 ( GROUP BY
) 之前,结果包含正确数量的最高分,因此您可以简单地将所有分数相加。
您在这里面临的一个大问题是,既不rank
也不dense_rank
会给您工具为每个player_id
和获得恰好 5 个元组event
。例如:如果某人在事件 A 中获得 1000 倍 1 分,那么他最终将获得 1000 分,因为所有分数都会得到rank
和dense_rank
1
。
但又是这样ROWNUM
:MySQL 不支持这个,所以我们必须模仿这个。问题ROWNUM
在于它将为所有元组生成一个复合数字。但是我们想要 , 组的合player_id
数event
。不过,我仍在研究此解决方案。
编辑2
使用这个答案,我发现这个解决方案可以工作:
select
player_id, sum( points )
from
(
select
player_id,
event,
points,
/* increment current_pos and reset to 0 if player_id or event changes */
@current_pos := if (@current_player = player_id AND
@current_event = event, @current_pos, 0) + 1 as position,
@current_player := player_id,
@current_event := event
from
(select
/* global variable init */
@current_player := null,
@current_event := null,
@current_pos := 0) set_pos,
points
order by
player_id,
event,
points desc
) pos
WHERE
pos.event = 'A' AND pos.position <= 5
OR
pos.event = 'B' AND pos.position <= 2
OR
pos.event = 'C' AND pos.position <= 3
GROUP BY player_id
ORDER BY SUM( points ) DESC
内部查询选择 (player_id, event, points)-元组,按 player_id 和 event 对它们进行排序,最后给每个元组一个复合数,每次 player_id 或 event 更改时重置为 0。由于顺序的原因,所有具有相同 player_id 的元组都是连续的。外部查询与先前使用的查询对视图的作用相同。
Edit3(见评论)
您可以使用 OLAP ROLLUP 运算符创建中间总和或不同类型的分区。例如,查询将如下所示:
select
player_id, event, sum( points )
from
(
select
player_id,
event,
points,
/* increment current_pos and reset to 0 if player_id or event changes */
@current_pos := if (@current_player = player_id AND
@current_event = event, @current_pos, 0) + 1 as position,
@current_player := player_id,
@current_event := event
from
(select
/* global variable init */
@current_player := null,
@current_event := null,
@current_pos := 0) set_pos,
points
order by
player_id,
event,
points desc
) pos
WHERE
pos.event = 'A' AND pos.position <= 5
OR
pos.event = 'B' AND pos.position <= 2
OR
pos.event = 'C' AND pos.position <= 3
GROUP BY player_id, event WITH ROLLUP
/* NO ORDER BY HERE. SEE DOCUMENTATION ON MYSQL's ROLLUP FOR REASON */
结果现在将首先按 分组player_id, event
,然后按 only分组,player_id
最后按 null 分组(总结所有行)。
第一组看起来像(player_id, event, sum(points)) = {(1, A, 20), (1,B,5)}
20 和 5 是关于player_id
和的点的总和event
。第二组的样子(player_id, event, sum(points)) = {(1,NULL,25)}
。25 是关于 的所有点的总和player_id
。希望有帮助。:-)