I have a Django model that looks like this:
class LeaderboardScore(models.Model):
score = models.FloatField()
player_account = models.ForeignKey(PlayerAccount, on_delete=models.CASCADE)
timestamp = models.DateTimeField()
This represents top scores in a game. The scores are ranked by score descending and timestamp ascending, so if two scores are the same, the earliest score is ranked higher.
Every time I do a query on the leaderboards, I'd like to include the global ranking of each instance in the query result. I can do that successfully when I'm querying all the scores like so:
from django.db.models import F, Window
from django.db.models.functions import DenseRank
lq = LeaderboardScore.objects.filter(leaderboard__id=1).annotate(
rank=Window(
expression=DenseRank(),
order_by=[F('score').desc(), F('timestamp').asc()]
)
)
This will correctly at a rank field with the ranking. But if I take the above query and filter it by player_account like so:
# rank will be 1, but should be the global ranking
lq.filter(player_account__id=123)[0].rank
then the ranking resets so that the first item in the query has the rank of 1, even though its global rank should be different. How do I preserve the global ranking?
I couldn't figure out how to do this Django's ORM, but I was able to use SQL as temporary solution:
WITH ranked_leaderboard AS (
SELECT
id,
score,
timestamp,
player_account_id,
DENSE_RANK() OVER (ORDER BY score DESC, timestamp ASC) AS rank
FROM
leaderboards_leaderboardscore
WHERE
leaderboard_id = 1
)
SELECT
id,
score,
timestamp,
rank
FROM
ranked_leaderboard
WHERE
player_account_id = 123
ORDER BY
score DESC, timestamp ASC
This isn't an ideal solution as it only works when I'm searching for a particular player -- I have other queries that might be composed of an arbitrary list of players, which I had use a non-optimal solution for.