134

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

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
4

3 回答 3

243

根据文档,您应该使用:

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')
于 2013-09-30T19:44:52.583 回答
53

就像@Alvaro 已经回答了 Django 的直接等效 forGROUP 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: ordering. 这种方法将使声明面向未来:无论未来对class 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'))

那是因为 DjangoGROUP 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() 交互。

于 2017-03-21T21:09:23.067 回答
1

如果您想要反转(较大的值到较小的值)顺序,只需使用-减号。

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('-total')
于 2021-08-30T19:33:50.330 回答