如何在Django中选择COUNT(*)GROUP BY和ORDER BY?


95

我正在使用事务处理模型来跟踪通过系统的所有事件

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

如何获得系统中的前5名演员?

在SQL中,基本上

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC

Answers:


176

根据文档,您应该使用:

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

values():指定要用于“分组依据”的列

Django文档:

“当使用values()子句约束结果集中返回的列时,评估注释的方法略有不同。原始结果不是按照原始QuerySet中的每个结果返回带注释的结果,而是按照到values()子句中指定的字段的唯一组合”

annotate():指定对分组值的操作

Django文档:

生成摘要值的第二种方法是为QuerySet中的每个对象生成一个独立的摘要。例如,如果您要检索一本书的清单,则可能想知道每本书有多少作者。每本书与作者都有多对多的关系;我们想为QuerySet中的每本书总结这种关系。

可以使用annotate()子句生成每个对象的摘要。当指定annotate()子句时,将使用指定的值对QuerySet中的每个对象进行注释。

order by子句不言自明。

总结一下:分组依据,生成作者的查询集,添加注释(这将为返回的值添加一个额外的字段),最后,您按此值对其进行排序

请参阅https://docs.djangoproject.com/en/dev/topics/db/aggregation/了解更多信息

值得注意的是:如果使用Count,则传递给Count的值不会影响聚合,只会影响最终值的名称。聚合器按值的唯一组合(如上所述)分组,而不是按传递给Count的值分组。以下查询是相同的:

Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')
Transaction.objects.all().values('actor').annotate(total=Count('id')).order_by('total')

对我来说,它的工作方式是Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'),请不要忘记从django.db.models导入Count。谢谢
Ivancho

3
值得注意的是:如果使用Count(可能还有其他聚合器),则传递给该值的值Count不会影响聚合,而只会影响最终值的名称。聚合器按的唯一组合values(如上所述)分组,而不是按传递给的值分组Count
kronosapiens 2015年

您甚至可以将其用于postgres搜索结果查询集以获取更多信息!
yekta

2
@kronosapiens至少在当今,它确实会影响它(我正在使用Django 2.1.4)。在示例中,total给出的名称和sql中使用的计数COUNT('actor')在这种情况下无关紧要,但是,例如values('x', 'y').annotate(count=Count('x')),如果您将得到COUNT(x),not COUNT(*)COUNT(x, y),只需在./manage.py shell
timdiels

33

就像@Alvaro回答了Django直接等效的for GROUP BY语句一样:

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

通过使用values()annotate()方法如下:

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

但是,还必须指出一件事:

如果模型具有在中定义的默认顺序class Meta,则该.order_by()子句对于获得正确的结果是必需的。即使不打算订购,您也不能跳过它。

此外,对于高质量的代码,即使没有,也建议始终.order_by()在其后放置一个子句。这种方法将使该语句适应未来发展:无论将来对进行任何更改,它都将按预期工作。annotate()class Meta: orderingclass Meta: ordering


让我为您提供一个例子。如果模型具有:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

然后,这种方法将行不通:

Transaction.objects.values('actor').annotate(total=Count('actor'))

这是因为Django GROUP BY在其中的每个字段上都执行附加操作class Meta: ordering

如果要打印查询:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

很明显,聚合将不会按预期方式工作,因此.order_by()必须使用该子句清除此行为并获得正确的聚合结果。

请参阅:与默认命令或 Django官方文档中的order_by()的交互


3
.order_by()orderingMeta中救了我。
巴本·瓦尔丹扬
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.