相当于Django的计数和分组依据


91

我有一个看起来像这样的模型:

class Category(models.Model):
    name = models.CharField(max_length=60)

class Item(models.Model):
    name = models.CharField(max_length=60)
    category = models.ForeignKey(Category)

我想要为每个类别选择项目的计数(只是计数),因此在SQL中,它将像这样简单:

select category_id, count(id) from item group by category_id

有没有相当于做这种“ Django方式”?还是纯SQL是唯一的选择?我熟悉Django中的count()方法,但是我看不到group by如何适合那里。



@CiroSantilli巴拿马文件六四事件法轮功 这个问题是在2008年提出的,而您指的是2年后的问题。
谢尔盖·戈洛维琴科

当前的共识是通过“质量”来结束:< meta.stackexchange.com/questions/147643/… >由于“质量”是不可衡量的,所以我只是赞成。;-)可能归结为哪个问题在标题上击中了最佳的新手Google关键字。
西罗Santilli郝海东冠状病六四事件法轮功

Answers:


131

正如我刚刚发现的,这里是如何使用Django 1.1聚合API进行此操作:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

3
像Django中的大多数东西一样,没有一个看起来很有意义,但是(不同于Django中的大多数东西),一旦我实际尝试过,它就很棒:P
jsh 2011年

3
请注意,您需要使用order_by()if 'category'不是默认顺序。(请参见丹尼尔的更全面的回答。)
瑞克·韦斯特拉

之所以可行,是因为.annotate().values() “:” 之后,工作原理略有不同:“但是,当使用values()子句约束结果集中返回的列时,用于评估注释的方法略有不同。而不是返回带注释的方法对于原始QuerySet中每个结果的结果,原始结果将根据values()子句中指定的字段的唯一组合进行分组。”
mgalgs

58

更新Django 1.1现在包含了对ORM聚合的完全支持。忠实于以下有关使用私有API的警告,此处记录的方法在Django 1.1以后的版本中不再有效。如果您使用的是1.1或更高版本,则无论如何都应使用真实的汇总API。)

1.0中已经提供了核心聚合支持;它只是未记录的,不受支持的,并且还没有一个友好的API。但是,无论如何,直到1.1到来之前,您都可以使用它(由您自担风险,并且完全知道query.group_by属性不是公共API的一部分,并且可能会更改):

query_set = Item.objects.extra(select={'count': 'count(1)'}, 
                               order_by=['-count']).values('count', 'category')
query_set.query.group_by = ['category_id']

如果然后遍历query_set,则每个返回的值将是带有“类别”键和“计数”键的字典。

您不必在此处按-count进行排序,只是用来演示它是如何完成的(必须在.extra()调用中完成,而不必在queryset构造链中的其他位置进行)。另外,您也可以说用count(id)代替count(1),但是后者可能会更有效。

还要注意,设置.query.group_by时,值必须是实际的数据库列名称('category_id'),而不是Django字段名称('category')。这是因为您要在查询内容的内部进行调整,使所有内容都以DB而非Django的形式出现。


+1为旧方法。即使当前不支持,至少可以说也很有启发。真的很棒
空袭

docs.djangoproject.com/en/dev/topics/db/aggregation/上查看Django聚合API,可以用它完成其他复杂的任务,在那里您将找到一些强大的示例。
serfer2

@ serfer2是的,这些文档已经从此答案的顶部链接了。
卡尔·梅耶

56

由于我对Django 1.1中的分组方式有些困惑,因此我想在这里详细说明如何使用它。首先,重复迈克尔说的话:

正如我刚刚发现的,这里是如何使用Django 1.1聚合API进行此操作:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

还需要注意from django.db.models import Count

这将仅选择类别,然后添加名为的注释category__count。根据默认顺序,这可能是您所需要的,但是如果默认顺序使用的字段不是category此字段,则将不起作用。这样做的原因是,还选择了订购所需的字段,并使每一行都是唯一的,因此您不会按需要对内容进行分组。解决此问题的一种快速方法是重置顺序:

Item.objects.values('category').annotate(Count('category')).order_by()

这应该产生所需的精确结果。要设置注释的名称,可以使用:

...annotate(mycount = Count('category'))...

然后,您将mycount在结果中调用一个注释。

关于分组的其他一切对我来说都很简单。请务必查看Django聚合API,以获取更多详细信息。


1
对外键字段Item.objects.values('category__category')。annotate(Count('category__category'))。order_by()执行相同的一组操作
Mutant

如何确定默认的订购字段是什么?
Bogatyr 2015年

2

这个怎么样?(除了慢。)

counts= [ (c, Item.filter( category=c.id ).count()) for c in Category.objects.all() ]

它的优点是很短,即使它确实获取了很多行。


编辑。

一个查询版本。顺便说一句,这通常比数据库中的SELECT COUNT(*)更快。试试看。

counts = defaultdict(int)
for i in Item.objects.all():
    counts[i.category] += 1

很好,也很简短,但是我想避免为每个类别单独进行数据库调用。
谢尔盖·戈洛夫琴科

对于简单的情况,这是一种非常好的方法。当您拥有大型数据集时,它会下降,并且您希望根据计数进行排序+限制(即分页),而不会提取大量不需要的数据。
卡尔·迈尔

@卡尔·迈尔(Carl Meyer):是的-对于大型数据集,它可以是小狗。您需要进行基准测试以确保这一点。另外,它也不依赖不受支持的东西。在不支持的功能受支持之前,它会暂时起作用。
S.Lott
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.