具有多个 ForeignKey 引用的 Django 注释出现问题

Trouble with Django annotation with multiple ForeignKey references

我正在努力处理注释,但没有找到有助于我理解的示例。以下是我模型的相关部分:

    class Team(models.Model):
        team_name = models.CharField(max_length=50)
    
    class Match(models.Model):
        match_time = models.DateTimeField()
        team1 = models.ForeignKey(
            Team, on_delete=models.CASCADE, related_name='match_team1')
        team2 = models.ForeignKey(
            Team, on_delete=models.CASCADE, related_name='match_team2')
        team1_points = models.IntegerField(null=True)
        team2_points = models.IntegerField(null=True)

我想要结束的是对 Teams 对象的注释,它会给我每个团队的总分。有时,一个团队是 match.team1(因此他们的分数在 match.team1_points 中),有时他们是 match.team2,他们的分数存储在 match.team2_points.

这是我所获得的最接近结果,大概尝试了一百次左右:

teams = Team.objects.annotate(total_points = 
Value(
  (Match.objects.filter(team1=21).aggregate(total=Sum(F('team1_points'))))['total'] or 0 + 
  (Match.objects.filter(team2=21).aggregate(total=Sum(F('team2_points'))))['total'] or 0, 
  output_field=IntegerField())
)

这很好用,但是(当然)将 pk=21 的团队的 total_points 注释给查询集中的每个团队。如果有更好的方法来解决所有这些问题,我很乐意看到它,但除此之外,如果你能告诉我如何将那些“21”值变成对外部团队 pk 的引用,我认为这会起作用吗?

编辑:我最终结合使用了 elyas 的答案并注释了原始 SQL 语句来解决我的问题。我无法阻止普通注释从查询集中删除非唯一分数,但原始 SQL 似乎有效。

这是原始注释:

teams = Team.objects.raw('select id, sum(points) as total_points from (select team1_id as id, team1_points as points from leagueman_match union all select team2_id as id, team2_points as points from leagueman_match) group by id order by total_points desc;')

注释 QuerySet 的另一种解决方案可能是使 total_points 成为 Team 模型的 @property(取决于用例):

from django.db.models import Case, Q, Sum, When


class Team(models.Model):
    team_name = models.CharField(max_length=50)

    @property
    def total_points(self):
        return Match.objects.filter(Q(team1=self.id) | Q(team2=self.id)).aggregate(
            total_points=Sum(Case(
                When(team1=self.id, then='team1_points'),
                When(team2=self.id, then='team2_points')
            ))
        )['total_points']

缺点是不能用于后续的QuerySet操作,例如.values(), .order_by().

Django 还有一个 @cached_property decorator 会在第一次调用时缓存属性的输出。

尝试了其他解决方案

最初我认为您可以利用 Team 模型中的反向关系 match_team1match_team2 来生成一个简单的注释:

teams = Team.objects.annotate(
    total_points=(
        Sum('match_team1__team1_points', distinct=True)
        + Sum('match_team2__team2_points', distinct=True)
    )
)

不幸的是,此解决方案在处理重复项时遇到困难。 distinct=True 参数消除了同一场比赛的分数被多次求和的问题。但它引入了一个不同的问题,即得分相同的不同比赛将被排除在外。

也许我会建议重构您的数据模型。即使您找到了这个特定问题的解决方案,您也可能需要考虑一下。 这是一个解决方案:

class Team(models.Model):
    team_name = models.CharField(max_length=50)

class Match(models.Model):
    match_time = models.DateTimeField()
    team1 = models.ForeignKey(
        Team, on_delete=models.CASCADE, related_name='match_team1')
    team2 = models.ForeignKey(
        Team, on_delete=models.CASCADE, related_name='match_team2')

class Points(models.Model):
    match = models.ForeignKey(
        Match, on_delete=models.CASCADE, related_name='match')
    team = models.ForeignKey(
        Team, on_delete=models.CASCADE, related_name='team')
    points = models.IntegerField(null=True)

有了这个你可以很容易地总结任何球队的积分,也可以按比赛筛选。