如何不使用for循环实现Django查询:获取用户每道题的最终提交记录
Django 单查询实现竞赛用户最终提交列表获取
针对获取指定竞赛下所有用户每道题最近一次提交的需求,可通过Django内置的窗口函数或子查询实现单SQL查询,完全替代嵌套循环,性能提升显著,实现代码如下:
方案1:窗口函数实现(推荐,Django 2.0+支持)
仅执行1次数据库查询,性能最优:
from django.db.models import F, Window from django.db.models.functions import RowNumber def list_final_submissions(contest_id): # 读取竞赛实例 contest = Contest.objects.get(id=contest_id) # 筛选属于当前竞赛范围内的所有提交(题目属于竞赛、提交者是竞赛参与者) contest_submissions = Submission.objects.filter( problem__in=contest.problems.all(), participant__in=contest.participants.all() ) # 按「用户+题目」分组,每组内按提交时间倒序排行号 ranked_submissions = contest_submissions.annotate( row_num=Window( expression=RowNumber(), partition_by=[F('participant_id'), F('problem_id')], order_by=F('submitted_time').desc() ) ) # 只取每组行号为1的记录,即对应每个用户每道题的最近一次提交 return ranked_submissions.filter(row_num=1)
方案2:子查询实现(兼容低版本Django)
如果你的Django版本低于2.0不支持窗口函数,可改用子查询方案:
from django.db.models import Subquery, OuterRef def list_final_submissions(contest_id): contest = Contest.objects.get(id=contest_id) # 子查询:匹配每个用户+题目组合,返回最新一条提交的ID latest_sub_id = Submission.objects.filter( participant_id=OuterRef('participant_id'), problem_id=OuterRef('problem_id'), problem__in=contest.problems.all(), participant__in=contest.participants.all() ).order_by('-submitted_time').values('id')[:1] # 主查询过滤出所有最新提交记录 return Submission.objects.filter(id__in=Subquery(latest_sub_id)).distinct()
优化建议
- 可给
Submission模型的submitted_time、participant_id、problem_id字段加联合索引,数据量较大时查询效率会有明显提升。 - 如果存在同一用户同一题同一时间提交多次的极端场景,可在排序规则中追加
id倒序,确保取到的是最后生成的提交记录:order_by=[F('submitted_time').desc(), F('id').desc()]
内容的提问来源于stack exchange,提问作者eva
相关产品推荐
相关产品推荐

