如何在Django ORM中执行GROUP BY ... COUNT或SUM操作?

50

序言:

这是 SO 上经常出现的问题:

我在 SO 文档中编写了一个示例,但由于文档将于2017年8月8日关闭,我将遵循这个广受赞同和讨论的元答案的建议,并将我的示例转换为自我回答的帖子。

当然,我也非常乐意看到任何不同的方法!!


问题:

假设有以下模型:

class Books(models.Model):
    title  = models.CharField()
    author = models.CharField()
    price = models.FloatField()

我如何利用Django ORM在该模型上执行以下查询:

  • GROUP BY ... COUNT

    SELECT author, COUNT(author) AS count
    FROM myapp_books GROUP BY author
    
  • GROUP BY ... SUM

    SELECT author,  SUM (price) AS total_price
    FROM myapp_books GROUP BY author
    
2个回答

106
我们可以使用annotate()values()django.db.modelsCountSum方法,在Django ORM上执行GROUP BY ... COUNTGROUP BY ... SUM SQL等效查询,可选地使用order_by()方法:
  • GROUP BY ... COUNT:

     from django.db.models import Count
    
     result = Books.objects.values('author')
                           .order_by('author')
                           .annotate(count=Count('author'))
    

    Now result contains a dictionary with two keys: author and count:

       author    | count
     ------------|-------
      OneAuthor  |   5
     OtherAuthor |   2
        ...      |  ...
    
  • GROUP BY ... SUM:

     from django.db.models import Sum
    
      result = Books.objects.values('author')
                            .order_by('author')
                            .annotate(total_price=Sum('price'))
    

    Now result contains a dictionary with two columns: author and total_price:

       author    | total_price
     ------------|-------------
      OneAuthor  |    100.35
     OtherAuthor |     50.00
         ...     |      ...
    

更新 2021年4月13日

正如评论中@dgw指出的那样,在模型使用元选项来排序行(例如ordering),order_by()子句对于聚合的成功至关重要


你的意思是在注释之后再次过滤,ORM 足够智能以知道它需要执行 HAVING 操作吗? - Henrietta Martingale
2
这是对我有用的代码:statement_line.objects.filter(pay_date__lt='2019-10-31').select_related('ae').values('ae__opp_own').annotate(tots=Sum('amt')).filter(tots__gt=0) 关键点在于select_related和双下划线表示父字段名。第二个filter转换为了"having"。 str([obj].query)可以确认这一点。另一个便捷的工具。 - Henrietta Martingale
@HenriettaMartingale 这种问答风格更为通用,而您所撰写的内容并不真正与问题或答案相关。您确定这是要作为对此帖子的评论吗? - John Moutafis
2
也许应该强调 order_by(...) 部分。如果模型使用不同的列进行排序,则省略 order_by() 子句将导致聚合失败。 - dgw
只有在你想要从你的集合中计算不同价格的总和时才使用 @djvg。(例如,p1=2,p2=3,p3=2,p4=3,p5=4 的不同总和为9) - undefined
显示剩余5条评论

0
在使用 group by SUM() 时,你可以获得近似于两个字典对象的结果。
inv_data_tot_paid =Invoice.objects.aggregate(total=Sum('amount', filter=Q(status = True,month = m,created_at__year=y)),paid=Sum('amount', filter=Q(status = True,month = m,created_at__year=y,paid=1)))
print(inv_data_tot_paid)
##output -{'total': 103456, 'paid': None}

不要尝试使用超过两个查询过滤器,否则会出现错误,例如:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接