简体   繁体   English

如何使用 Many2Many 关系中的元素数正确注释 QuerySet?

[英]How to correctly annotate a QuerySet with the number of elements in a Many2Many relation?

I'm modelling amateur soccer Games where two teams of players play against each other.我正在模拟两队球员互相比赛的业余足球比赛。 I'm trying to annotate the QuerySet with the number of players in each team to then identify games with different number of players in each team.我正在尝试使用每个团队中的玩家数量来注释 QuerySet,然后识别每个团队中具有不同玩家数量的游戏。

The models I have are pretty straight forward:我拥有的模型非常简单:

class Player(models.Model):
    nickname = models.CharField(...)

class Team(models.Model):
    players = models.ManyToManyField(Player)

class Game(modeos.Model):
    team1 = models.ForeignKey(Team)
    team2 = models.ForeignKey(Team)

When I go to the shell ( python manage shell ) and execute the following command to get the number of players of the teams in each match I got the expected results : When I go to the shell ( python manage shell ) and execute the following command to get the number of players of the teams in each match I got the expected results :

[(game.team1.players.count(), game.team2.players.count()) for game in Game.objects.all()]
[(7, 7), (7, 10), (7, 7), (7, 7), (7, 7)]

However, when I try to do the same with annotations :但是,当我尝试对annotations做同样的事情时:

Game.objects.annotate(t1c=Count('team1__players'), t2c=Count('team2__players')).values('t1c','t2c')

I got incorrect results :我得到了不正确的结果

<QuerySet [{'t1c': 49, 't2c': 49}, {'t1c': 70, 't2c': 70}, {'t1c': 49, 't2c': 49}, {'t1c': 49, 't2c': 49}, {'t1c': 49, 't2c': 49}]>

Which are the multiplication of both values.这是两个值的乘积。

What am I doing wrong?我究竟做错了什么?

As documented记录的

Combining multiple aggregations with annotate() will yield the wrong results because joins are used instead of subqueries将多个聚合与 annotate() 组合会产生错误的结果,因为使用的是连接而不是子查询

For most aggregates, there is no way to avoid this problem, however, the Count aggregate has a distinct parameter that may help对于大多数聚合,没有办法避免这个问题,但是,Count 聚合有一个distinct参数,可能会有所帮助

Game.objects.annotate(
        t1c=Count('team1__players', distinct=True),
        t2c=Count('team2__players', distinct=True)
    ).values('t1c','t2c')

As the other answer noted, the django docs point out that the result is incorrect because joins are used instead of subqueries.正如另一个答案所述,django 文档指出结果不正确,因为使用的是连接而不是子查询。 The ORM does provide a way to use subqueries instead, which is useful when distinct=True doesn't fix your problem (and in my experience, more performant than joins with distinct=True). ORM 确实提供了一种使用子查询的方法,这在distinct=True不能解决您的问题时很有用(并且根据我的经验,比使用 distinct=True 连接的性能更高)。

First, your model definition for Game is not correct.首先,您对Game的 model 定义不正确。 ForeignKey has to have on_delete defined, and when you have two ForeignKeys to the same model you have to define a related_name for at least one of them or you will get a conflict. ForeignKey 必须定义on_delete ,并且当您有两个 ForeignKeys 指向同一个 model 时,您必须为其中至少一个定义一个 related_name,否则您会发生冲突。 So, I am using this as the definition of the Game model所以,我用这个作为游戏 model 的定义

class Game(models.Model):
    played = models.DateField()
    team1 = models.ForeignKey(Team, on_delete=CASCADE, related_name='team1_game')
    team2 = models.ForeignKey(Team, on_delete=CASCADE, related_name='team2_game')

To do your Count aggregation using a subquery:要使用子查询进行 Count 聚合:

t1c_count_query = Player.objects.filter(
    team__id=OuterRef('team1_id')
).values(
    'team__team1_game__id'  # Required for proper group by in the subquery
).annotate(
    player_count=Count('id')
).values(
    'player_count'  # Required to select only a single value in a subquery
)

Game.objects.annotate(t1c=Subquery(t1c_count_query))

And you can do the same for t2c and use them in the same query and everything works.你可以对 t2c 做同样的事情,并在同一个查询中使用它们,一切正常。

This is made easier by the django-sql-utils app. django-sql-utils应用程序使这变得更容易。 After you pip install django-sql-utils在你pip install django-sql-utils之后

from sql_util.utils import SubqueryCount

Game.objects.annotate(
    t1c=SubqueryCount('team1__players'),
    t2c=SubqueryCount('team2__players')
).values('t1c', 't2c')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM