问题描述
当总和根据某个顺序由每行的排名加权时,我一直在尝试为表的每个用户计算总和。这是在电子游戏的背景下,最高分值 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