在 MySQL 中具有排名的加权 SUM

问题描述

当总和根据某个顺序由每行的排名加权时,我一直在尝试为表的每个用户计算总和。这是在电子游戏的背景下,最高分值 100%,第二个值 95%,第三个值 (0.95 * 0.95) * 100%,依此类推。

公式可以简单地描述为 SUM(score * POW(0.95,score's position)),但我尝试的所有方法似乎都遇到了 MysqL 限制。

首先这个选项不起作用,因为似乎在 SUM 之后调用ORDER BY pls.score DESC(SUM 似乎没有通过更改顺序而改变)。

SELECT p.PlayerId,p.Username,(SELECT SUM(pls.score * POW(0.95,@i := if(@c = p.PlayerId,@i + 1,if(@c := p.PlayerId,0))))
        FROM player_level_stats pls,(SELECT @i := -1,@c := '') params
        WHERE pls.PlayerId = p.PlayerId
        ORDER BY pls.score DESC) * 10 AS score
FROM player p
ORDER BY 3 DESC;

此外,我必须使用这个变量混乱,因为我不能在 ROW_NUMBER() OVER () 中包含 SUM()

现在好了,我接下来要尝试的是在外部查询中求和,这样我就可以在求和之前进行排序:

SELECT p.PlayerId,(SELECT SUM(t.score)
        FROM (SELECT pls.score * POW(0.95,0)))
            FROM player_level_stats pls,@c := '') params
            WHERE pls.PlayerId = p.PlayerId
            ORDER BY pls.score DESC) AS t) * 10 AS score
FROM player p
ORDER BY 3 DESC
LIMIT 50;

理论上听起来不错,但在实践中存在 MysqL 限制,您无法将参数传递给嵌套子查询(有关此问题的答案基本上是告诉您尝试其他方法)。由于嵌套子查询FROM 中,似乎很难将某些内容传递给中间查询

当然总是会有慢 SUM(pls.score * POW(0.95,(SELECT COUNT(*) FROM player_level_stats ipls WHERE pls.score > ipls.score))) 但这是 O(分数 ^ 2) 而不是 O(分数数 * log(分数)) 使整个事情变得更慢。

有什么办法可以在 MysqL 中做到这一点吗?或者有什么办法可以用 MariaDB 做到这一点?

编辑: 这是一个最小的版本(也不起作用)

SELECT p.PlayerId,(SELECT SUM(pls.score * (RANK() OVER (ORDER BY pls.score DESC)))
        FROM player_level_stats pls
        WHERE pls.PlayerId = p.PlayerId
        ORDER BY pls.score DESC) AS Weightedscore
FROM player p;

这只是将每个玩家的分数乘以该分数的排名(最佳成绩 * 1 + 第二佳成绩 * 2 + ... n 最佳成绩 * n)。这本质上是相同的问题,但简化了。

解决方法

如果我正确理解了问题,您可以做的是一个带有总和的子查询,然后使用 row_number() over(order by) 来获得排名。像这样:

SELECT playerID,SUM(score * weighted) AS totalscore
FROM
(
 SELECT 
  playerID,score,POW(0.95,(ROW_NUMBER() OVER(partition by playerID ORDER BY score desc)-1)) as weighted
  FROM player_level_stats
 ) AS totals
GROUP BY playerID