【问题标题】:Django - annotate with multiple CountDjango - 用多个计数注释
【发布时间】:2019-01-13 00:39:32
【问题描述】:

我有一个名为Post 的模型,它有两个字段upvotesdownvotes。现在,upvotesdownvotesManyToManyFieldProfile。这是模型:

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}]>

似乎updown 具有相同的值(实际上是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}]>

但即使这样也给出了相同的输出。

【问题讨论】:

  • 对个人资料进行多对多并存储在 m2m 表中是否更有意义?
  • 是的,这是一个带有 Profile 的 ManyToMany,我认为 Django 会处理您所说的那个表!
  • 正确,但您可以定义through 表,从而为关系添加属性:docs.djangoproject.com/en/2.1/ref/models/fields/…

标签: django django-models django-annotate


【解决方案1】:

尝试使用dictinct 参数:

Post.objects.annotate(
    total_votes=Count('upvotes', distinct=True)-Count('downvotes', distinct=True)
).order_by('total_votes')

来自文档:

将多个聚合与 annotate() 组合会产生错误 结果是因为使用连接而不是子查询。对于大多数 聚合,没有办法避免这个问题,但是,计数 聚合有一个独特的参数可能会有所帮助。

【讨论】:

    【解决方案2】:

    (我知道这并不完全是一个答案,但代码不能嵌入到评论中。)

    一个更好的数据模型应该是

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

    这样你就可以简单地计算帖子的当前总分

    Vote.objects.filter(post=post).aggregate(score=Sum('score'))
    

    但是,您应该清楚每次执行此操作(或您的原始版本)对性能的影响。最好加个

    score = models.IntegerField(editable=False)
    

    Post 的字段,每次创建、修改或删除投票时都会使用总得分进行更新。

    【讨论】:

      猜你喜欢
      • 2023-02-14
      • 2016-03-16
      • 2012-03-07
      • 1970-01-01
      • 1970-01-01
      • 2011-10-11
      • 1970-01-01
      • 1970-01-01
      • 2015-10-29
      相关资源
      最近更新 更多