问题描述
我需要计算拳击比赛中运动员的排名。在我的模型中,我跟踪每次比赛的结果以及每个结果归属于每个运动员的分数。
class Member(models.Model):
surname = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
class Tournament(models.Model):
name = models.CharField(max_length=200)
class TrophyRule(models.Model):
win = models.IntegerField()
loose = models.IntegerField()
draw = models.IntegerField()
class Ring(models.Model):
code = models.CharFieldmax_length=1)
tournament = models.ForeignKey(Tournament,on_delete=models.CASCADE)
class Match(models.Model):
ring = models.ForeignKey(Ring,null=True,on_delete=models.SET_NULL)
winner = models.CharField(max_length=20,blank=True)
trophy_rule = models.ForeignKey(TrophyRule,on_delete=models.SET_NULL,null=True)
red_member = models.ForeignKey(Member,related_name='reds',null=True)
red_count_ranking = models.BooleanField(default=True)
blue_member = models.ForeignKey(Member,related_name='blues',null=True)
blue_count_ranking = models.BooleanField(default=True)
基于此模型,我需要将运动员在红色拐角处获得的分数与运动员在蓝色拐角处获得的分数相加。结果应该是一个包含所有成员及其总点数的查询集。
为了实现这一目标,我首先计算了运动员在红色拐角处获得的积分:
from apps.members.models import Member
from django.db.models import Case,Sum,When,Q
red = Member.objects.filter(reds__ring__tournament_id=11402).annotate(
points=Case(
When(Q(reds__winner='red') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__win'),When(Q(reds__winner='draw') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__draw'),When(Q(reds__winner='blue') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__loose'),),)
对于蓝角处的运动员所获得的积分,我也做同样的事情:
blue = Member.objects.filter(blues__ring__tournament_id=11402).annotate(
points=Case(
When(Q(blues__winner='blue') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__win'),When(Q(blues__winner='draw') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__draw'),When(Q(blues__winner='red') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__loose'),)
现在,我需要将两个查询结合起来,并为每个运动员加分。这是我目前停留的部分。
我尝试使用union()转换为sql UNION:
red.union(blue)
如果我有4个匹配项,则使用union()会得到一个包含8个成员(4个红色和4个蓝色)的查询集,这正是我要寻找的。不幸的是,当我尝试计算最终的点数(运动员是红色的点+运动员是蓝色的点)时,我触发了错误:不支持在union()之后调用QuerySet.annotate()(按照{ {3}})。
red.union(blue).annotate(Sum('points'))
是否还有另一种方法可以通过Django ORM实现这一目标?我宁愿不要在不必要的情况下恢复为原始sql。
解决方法
这可能在单个请求中(未经验证的代码):
from django.db.models import Case,When,Q,F
members = Member.objects.filter(Q(reds__ring__tournament_id=11402)|Q(blues__ring__tournament_id=11402)).annotate(
red_points=Case(
When(Q(reds__winner='red') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__win'),When(Q(reds__winner='draw') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__draw'),When(Q(reds__winner='blue') & Q(reds__red_count_ranking=True),then='reds__trophy_rule__loose'),),blue_points=Case(
When(Q(blues__winner='blue') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__win'),When(Q(blues__winner='draw') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__draw'),When(Q(blues__winner='red') & Q(blues__blue_count_ranking=True),then='blues__trophy_rule__loose'),points=F('red_points') + F('blue_points')
)