You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Django查询玩家前4高得分总和并生成排行榜?

问题

我有一个用于追踪玩家多场游戏得分的数据库,希望基于每位玩家的4场最高得分生成排行榜。

模型定义

class Event(models.Model):
    name = models.CharField(max_length=120)

class Player(models.Model):
    name = models.CharField(max_length=45)

class Game(models.Model):
    name = models.CharField(max_length=15)

class Score(models.Model):
    player = models.ForeignKey(Player, on_delete=models.CASCADE)
    game = models.ForeignKey(Game, on_delete=models.CASCADE)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    score = models.IntegerField()

    class Meta:
        unique_together = ["player", "event", "game"]

我尝试用子查询实现,但切片似乎被忽略,返回的总和是玩家的所有得分,而非仅前4高得分:

scores = Score.objects.filter(player=OuterRef('pk'), event=myEvent).order_by('-score')[:4]
scores = scores.annotate(total=Func(F("score"), function="SUM")).values('total')
leaders = Players.objects.annotate(total=Subquery(scores)).order_by('-total')

请问如何让该子查询生效?或者有没有更优的实现方案?


解决方案

修复子查询方案

你的问题核心是子查询里的求和操作在切片之前执行,导致计算了所有得分的总和,而非前4个最高分的和。调整子查询逻辑,先筛选前4个最高分再求和:

from django.db.models import OuterRef, Subquery, Sum, Func

# 为每个玩家的得分按降序排名,只保留前4名
ranked_scores = Score.objects.filter(
    player=OuterRef('pk'),
    event=myEvent
).annotate(
    rank=Func(
        F('score'),
        function='RANK',
        template='%(function)s() OVER (PARTITION BY player_id ORDER BY %(expressions)s DESC)'
    )
).filter(rank__lte=4)

# 子查询计算前4个最高分的总和
total_score = ranked_scores.annotate(
    total=Sum('score')
).values('total')

# 生成排行榜
leaders = Player.objects.annotate(
    total=Subquery(total_score)
).order_by('-total')

这里用RANK()窗口函数先给每个玩家的得分排序,筛选出前4名后再求和,就能得到正确结果。

更优的窗口函数方案

直接用窗口函数一次性完成排名和求和,数据库查询次数更少,效率更高:

from django.db.models import Window, Sum, F
from django.db.models.functions import Rank

# 第一步:筛选当前赛事的得分,为每个玩家的得分排名,取前4名并求和
leaderboard = Score.objects.filter(event=myEvent).annotate(
    player_rank=Window(
        expression=Rank(),
        partition_by=F('player'),
        order_by=F('score').desc()
    )
).filter(player_rank__lte=4).values('player').annotate(
    total_score=Sum('score')
).order_by('-total_score')

# 如果需要关联Player模型的其他字段(比如玩家名称),可以再关联查询
leaderboard_with_player = Player.objects.filter(
    pk__in=leaderboard.values('player')
).annotate(
    total_score=Subquery(
        leaderboard.filter(player=OuterRef('pk')).values('total_score')
    )
).order_by('-total_score')

关键说明

  • 原代码中,annotate(total=Func(F("score"), function="SUM"))是对所有符合条件的得分求和,切片[:4]只是限制了返回的结果条数,并没有限制求和的范围,这才导致错误。
  • 窗口函数RANK()可以精准地为每个玩家的得分单独排序,确保我们只计算前4个最高分的总和。

内容的提问来源于stack exchange,提问作者desired login

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:57:12