Django - 使用多个计数进行注释
Django - annotate with multiple Count
我有一个名为 Post
的模型,它有两个字段 upvotes
和 downvotes
。现在,upvotes
、downvotes
是ManyToManyField
到Profile
。这是型号:
class Post(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
upvotes = models.ManyToManyField(Profile, blank=True, related_name='upvoted_posts')
downvotes = models.ManyToManyField(Profile, blank=True, related_name='downvoted_posts')
所以,我想获取所有帖子,以便它们按
的顺序排列
total(upvotes) - total(downvotes)
所以我使用了这个查询:
Post.objects.annotate(
total_votes=Count('upvotes')-Count('downvotes')
).order_by('total_votes')
此查询的问题是 total_votes
总是变成 零。
以下查询将说明情况:
In [5]: Post.objects.annotate(up=Count('upvotes')).values('up')
Out[5]: <QuerySet [{'up': 1}, {'up': 3}, {'up': 2}]>
In [6]: Post.objects.annotate(down=Count('downvotes')).values('down')
Out[6]: <QuerySet [{'down': 1}, {'down': 1}, {'down': 1}]>
In [10]: Post.objects.annotate(up=Count('upvotes'), down=Count('downvotes'), total=Count('upvotes')-Count('downvotes')).values('up', 'down', 'total')
Out[10]: <QuerySet [{'up': 1, 'down': 1, 'total': 0}, {'up': 3, 'down': 3, 'total': 0}, {'up': 2, 'down': 2, 'total': 0}]>
似乎up
和down
都具有相同的值(实际上是up
的值)。我该如何解决这个问题?
我试过这个:
In [9]: Post.objects.annotate(up=Count('upvotes')).annotate(down=Count('downvotes')).values('up', 'down')
Out[9]: <QuerySet [{'up': 1, 'down': 1}, {'up': 3, 'down': 3}, {'up': 2, 'down': 2}]>
但即使这样也会产生相同的输出。
尝试使用dictinct
参数:
Post.objects.annotate(
total_votes=Count('upvotes', distinct=True)-Count('downvotes', distinct=True)
).order_by('total_votes')
来自文档:
Combining multiple aggregations with annotate() will yield the wrong
results because joins are used instead of subqueries. For most
aggregates, there is no way to avoid this problem, however, the Count
aggregate has a distinct parameter that may help.
(我知道这不完全是一个答案,但不能在评论中嵌入代码。)
更好的数据模型是
class Post:
# ...
class Vote:
voter = models.ForeignKey(Profile, on_delete=models.PROTECT)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
score = models.IntegerField() # either -1 or +1; validate accordingly
class Meta:
unique_together = [('voter', 'post'),]
这样你就可以用
计算post的当前总分
Vote.objects.filter(post=post).aggregate(score=Sum('score'))
但是,您应该充分了解每次执行此操作(或您的原始版本)对性能的影响。最好加个
score = models.IntegerField(editable=False)
Post
字段,每次创建、修改或删除投票时都会更新总分。
我有一个名为 Post
的模型,它有两个字段 upvotes
和 downvotes
。现在,upvotes
、downvotes
是ManyToManyField
到Profile
。这是型号:
class Post(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
content = models.CharField(max_length=1000)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
subreddit = models.ForeignKey(Subreddit, on_delete=models.CASCADE)
upvotes = models.ManyToManyField(Profile, blank=True, related_name='upvoted_posts')
downvotes = models.ManyToManyField(Profile, blank=True, related_name='downvoted_posts')
所以,我想获取所有帖子,以便它们按
的顺序排列total(upvotes) - total(downvotes)
所以我使用了这个查询:
Post.objects.annotate(
total_votes=Count('upvotes')-Count('downvotes')
).order_by('total_votes')
此查询的问题是 total_votes
总是变成 零。
以下查询将说明情况:
In [5]: Post.objects.annotate(up=Count('upvotes')).values('up')
Out[5]: <QuerySet [{'up': 1}, {'up': 3}, {'up': 2}]>
In [6]: Post.objects.annotate(down=Count('downvotes')).values('down')
Out[6]: <QuerySet [{'down': 1}, {'down': 1}, {'down': 1}]>
In [10]: Post.objects.annotate(up=Count('upvotes'), down=Count('downvotes'), total=Count('upvotes')-Count('downvotes')).values('up', 'down', 'total')
Out[10]: <QuerySet [{'up': 1, 'down': 1, 'total': 0}, {'up': 3, 'down': 3, 'total': 0}, {'up': 2, 'down': 2, 'total': 0}]>
似乎up
和down
都具有相同的值(实际上是up
的值)。我该如何解决这个问题?
我试过这个:
In [9]: Post.objects.annotate(up=Count('upvotes')).annotate(down=Count('downvotes')).values('up', 'down')
Out[9]: <QuerySet [{'up': 1, 'down': 1}, {'up': 3, 'down': 3}, {'up': 2, 'down': 2}]>
但即使这样也会产生相同的输出。
尝试使用dictinct
参数:
Post.objects.annotate(
total_votes=Count('upvotes', distinct=True)-Count('downvotes', distinct=True)
).order_by('total_votes')
来自文档:
Combining multiple aggregations with annotate() will yield the wrong results because joins are used instead of subqueries. For most aggregates, there is no way to avoid this problem, however, the Count aggregate has a distinct parameter that may help.
(我知道这不完全是一个答案,但不能在评论中嵌入代码。)
更好的数据模型是
class Post:
# ...
class Vote:
voter = models.ForeignKey(Profile, on_delete=models.PROTECT)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
score = models.IntegerField() # either -1 or +1; validate accordingly
class Meta:
unique_together = [('voter', 'post'),]
这样你就可以用
计算post的当前总分Vote.objects.filter(post=post).aggregate(score=Sum('score'))
但是,您应该充分了解每次执行此操作(或您的原始版本)对性能的影响。最好加个
score = models.IntegerField(editable=False)
Post
字段,每次创建、修改或删除投票时都会更新总分。