有時候,我們想要獲取一個對象關聯關係的數量,但是我們不要所有的關聯對象,我們只想要符合規則的那些關聯對象的數量。 ...
有時候,我們想要獲取一個對象關聯關係的數量,但是我們不要所有的關聯對象,我們只想要符合規則的那些關聯對象的數量。
示例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,Person
和Task
。它們是一對多關係,一個用戶可能有多個任務。
那麼,我們怎麼來獲取一個用戶已完成任務的數量呢?
解決方法
一般來說,都是使用下麵這個辦法:
>>> 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中1,Count
新加入了一個參數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