具有多个 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_team1
和 match_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)
有了这个你可以很容易地总结任何球队的积分,也可以按比赛筛选。
我正在努力处理注释,但没有找到有助于我理解的示例。以下是我模型的相关部分:
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_team1
和 match_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)
有了这个你可以很容易地总结任何球队的积分,也可以按比赛筛选。