Django ORM将儿童数量分组

时间:2015-11-29 15:11:21

标签: python django orm

给出以下models.py

class Parent(models.Model):
    name = models.CharField(max_length=100)

class Child(models.Model):
    parent = models.ForeignKey('Parent', related_name='children')
    status = models.CharField(max_length=10, choices=(('ok', 'ok'), ('fail', 'fail')))

我想在父模型/视图上访问父母子女的分组计数。

e.g

parent.num_ok, parent.num_failed

 parent.child_counts_per_status['ok']

计数需要在SQL中完成,因为为所有父项加载所有子项然后在内存中计算它们是一个过多的开销(每个父项可能有成千上万的孩子)

如果我在ORM之外写这个,我会做类似的事情:

select parent.id, parent.name, child.status, count(*) from parent
inner join child on child.parent_id = parent.id
group by parent.id, parent.name, child.status

然而,看到我将限制父母的数量(通过分页),可能会有:

select parent.* from parent where ... (page is)

然后每个父母执行一次:

select status, count(*) from child where parent_id = :parent_id
group by status

这些选项中的任何一个都可以通过Django ORM获得吗?

如果是这样..我如何将其插入对象模型?我正在使用Django Rest Framework,我猜这个查询会进入目前看起来像的views.py:

class ParentViewSet(viewsets.ModelViewSet):

    queryset = Parent.objects.all()

2 个答案:

答案 0 :(得分:2)

如果您想为特定父母(比如parent1)计算'ok'儿童,请使用

parent1.children.filter(status='ok').count()

如果您需要为所有父母计算'ok'儿童,那么您可以使用annotate,例如为每位父母打印儿童数,您将使用

from django.db.models import Count 

parents = Parent.objects.filter(children__status='ok').annotate(c_count=Count('children'))

for p in parents:
  print p.c_count

分别用于查询集,您将使用

Parent.objects.filter(children__status='ok').distinct()

(我们使用distinct来消除重复)

答案 1 :(得分:2)

以下内容将使用您建议的名为num_oknum_fail的属性中的两种类型的子项的计数来注释每个父对象。

这内部创建的SQL几乎与您建议的SQL完全相同,后者将计数保留在数据库中,而不是在Python或Django中完成。

from django.db.models import Count, Case, When, IntegerField

...

queryset = Parent.objects.annotate(
               num_ok=Count(Case(
                            When(children__status='ok', then=1),
                                output_field=IntegerField()))
           ).annotate(
               num_fail=Count(Case(
                              When(children__status='fail', then=1),
                                output_field=IntegerField())))

这将允许迭代Parent对象并按如下方式检索计数:

for parent in queryset:
    print(parent.num_ok)
    print(parent.num_fail)