Django ORM中,如何使用Count来关联对象的子集数量

时间:2022-07-22 11:48:40

有时候,我们想要获取一个对象关联关系的数量,但是我们不要所有的关联对象,我们只想要符合规则的那些关联对象的数量。

示例models

# models.py
from django.db import models class Person(models.Model):
name = models.CharField('名称', max_length=32) def __str__(self):
return self.name class Task(models.Model):
name = models.CharField('任务名称', max_length=64)
is_done = models.BooleanField('是否完成', default=False)
owner = models.ForeignKey(Person, verbose_name='任务执行人') def __str__(self):
return "{}({})".format(self.name, 'x' if self.is_done else 'o')

上面的代码中,我们使用了两个model,PersonTask。它们是一对多关系,一个用户可能有多个任务。

那么,我们怎么来获取一个用户已完成任务的数量呢?

解决方法

一般来说,都是使用下面这个办法:

>>> person = Person.objects.first()
>>> person.task_set.all().count()
15
>>> person.task_set.filter(is_done=True).count()
10

这个办法,确实可以解决问题。但是如果你有很多Person,都需要获取已完成任务的数量,使用这个方法会依次对每个person的tasks集合使用单独的SQL,开销极大。

我们知道,Django ORM有一个aggregate函数,叫做Count。一般情况下,可以用它来获取一个对象关联对象的数量,并生成一个额外的computed字段。而在Django2.0中1Count新加入了一个参数filter,使用它,可以在计算count数量之前先对集合进行过滤

>>> from django.db.models import Count, Q
>>> person = Person.objects.annotate(
... Count('task_set', filter=Q(task_set__is_done=True))
... ).first()
>>> person.task_set__count
10

而在Django2.0之前,我们应该怎么办呢?可以用SQL的Case/When来解决:

>>> from django.db.models import Count, When, Case
>>> person = Person.objects.annotate(Count(Case(When(task_set__is_done=True, then=0))).first()
>>> person.task_set__count
10