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

Posted

技术标签:

【中文标题】如何在 Django ORM 中执行 GROUP BY ... COUNT 或 SUM?【英文标题】:How to execute a GROUP BY ... COUNT or SUM in Django ORM? 【发布时间】:2018-01-14 19:19:05 【问题描述】:

序幕:

这是SO中经常出现的一个问题:

Django Models Group By Django equivalent for count and group by How to query as GROUP BY in django? How to use the ORM for the equivalent of a SQL count, group and join query?

我已经编写了一个关于 SO 文档的示例,但由于文档将于 2017 年 8 月 8 日关闭,我将按照this widely upvoted and discussed meta answer 的建议将我的示例转换为自我回答的帖子。

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


问题:

假设模型:

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

【问题讨论】:

【参考方案1】:

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

按...计数分组:

 from django.db.models import Count

 result = Books.objects.values('author')
                       .order_by('author')
                       .annotate(count=Count('author'))

现在结果包含一个带有两个键的字典authorcount

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

现在结果包含一个 字典,其中包含两列:authortotal_price

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

2021 年 13 月 4 日更新

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

【讨论】:

您还应该添加带有分组依据和“拥有”过滤器的连接表。对我来说这是违反直觉的,因为在 SQL 中,您通常从父级开始,而在 django 中,您从子级开始。 @HenriettaMartingale 如果我理解正确你的意思,你可以在提取values之前使用filter 你的意思是在annotate之后再次过滤,并且orm足够聪明,知道它需要做有吗? 这对我有用: 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) 关键键是选择相关和父字段名称的双下划线。第二个过滤器确实转向“拥有”。 str([obj].query) 证实了这一点。另一个方便的东西。 也许应该强调order_by(...) 部分。如果模型使用不同的列进行排序,省略order_by() 子句将导致聚合失败。【参考方案2】:

通过 SUM() 分组,您可以获得几乎两个 dict 对象,如

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

不要尝试超过两个查询过滤器,否则你会得到类似的错误

【讨论】:

以上是关于如何在 Django ORM 中执行 GROUP BY ... COUNT 或 SUM?的主要内容,如果未能解决你的问题,请参考以下文章

使用SQL语言了解Django ORM中的分组(group by)和聚合(aggregation)查询

如何使用 django orm 进行嵌套 Group By?

我可以在 django 1.3 的 orm 中控制 GROUP BY 吗?

Django ORM,group_by 按所有值分组

Django ORM 在 SQL Join 中创建幻影别名

可以单独对多个列进行 GROUP BY 并使用 django ORM 将它们中的每一列聚合到其他列?