Django 计算匹配分数(联合后注释查询的解决方法)

Posted

技术标签:

【中文标题】Django 计算匹配分数(联合后注释查询的解决方法)【英文标题】:Django compute score of matches (workaround to annotate a query after a union) 【发布时间】:2020-12-12 09:24:33 【问题描述】:

我需要计算拳击比赛期间运动员的排名。在我的模型中,我会跟踪每场比赛的结果以及为每个结果分配给每位运动员的分数。

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, null=True, blank=True)
    trophy_rule = models.ForeignKey(TrophyRule, on_delete=models.SET_NULL, null=True)
    red_member = models.ForeignKey(Member, related_name='reds', on_delete=models.SET_NULL, null=True)
    red_count_ranking = models.BooleanField(default=True)
    blue_member = models.ForeignKey(Member, related_name='blues', on_delete=models.SET_NULL, 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'),
    ),
)

现在,我需要将这两个查询结合起来,并对每个运动员的分数求和。这是我目前卡住的部分。

我尝试使用转换为 SQL UNION 的 union():

red.union(blue)

如果我有 4 个匹配项,则使用 union() 我会得到一个包含 8 个成员(4 个红色和 4 个蓝色)的查询集,这正是我正在寻找的。不幸的是,当我尝试计算最终的点数(运动员为红色时的点数+运动员为蓝色时的点数)时,我触发了错误:在 union() 后调用 QuerySet.annotate() 不受支持(根据@ 987654321@).

red.union(blue).annotate(Sum('points'))

还有其他方法可以使用 Django ORM 实现这一点吗?如果没有必要,我不想恢复到原始 SQL。

【问题讨论】:

【参考方案1】:

这可能在单个请求中实现(未经测试的代码):

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')
)

【讨论】:

此查询将返回比赛中红蓝角运动员的所有分数之和。我需要一名运动员为红色和蓝色时的积分总和,而不是比赛中的另一名运动员。

以上是关于Django 计算匹配分数(联合后注释查询的解决方法)的主要内容,如果未能解决你的问题,请参考以下文章

带有字段比较结果的django注释查询集

根据带有 id 和分数的其他列表对 Django 查询集进行排序

在 Django 中注释 values() 查询

创建 django admin 后获取站点匹配查询不存在错误

需要根据找到的 Q 对象来注释 Django querySet

在 Django Admin 中保存新对象并发送到 Celery 任务后,匹配查询不存在