本篇以下面的模型为基础进行讨论,根据查询目标列出示例代码,和示例结果。
python
from django.db import models
# 作者
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
# 出版社
class Publisher(models.Model):
name = models.CharField(max_length=300)
# 图书
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
rating = models.FloatField()
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
pubdate = models.DateField()
#
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
count()
查询图书总数
python
>>> Book.objects.count()
2452
出版社名为"WordPress"的图书总数
python
>>> Book.objects.filter(publisher__name="WordPress").count()
73
aggregate()
aggregate()
方法是在整个 QuerySet 上生成聚合值。
所有书的均价,如果没有书则返回默认值0
python
>>> from django.db.models import Avg
>>> Book.objects.aggregate(Avg("price", default=0))
{'price__avg': 34.35}
所有书中最贵的价格,如果没有书则返回默认值0
python
>>> from django.db.models import Max
>>> Book.objects.aggregate(Max("price", default=0))
{'price__max': Decimal('81.20')}
aggregate()
是一个 QuerySet
的终端子句,当调用时,它返回一个字典。名称是聚合值的标识符;值是计算得到的聚合值。名称是从字段名称和聚合函数自动生成的。如果您想手动指定聚合值的名称,可以在指定聚合子句时提供该名称:
python
>>> Book.objects.aggregate(average_price=Avg("price"))
{'average_price': 34.35}
如果您想生成多个聚合值,可以向 aggregate()
子句添加另一个参数。因此,如果我们还想知道所有书的最高价和最低价,可以发出以下查询:
python
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
书中最贵价格和平均价格的差值
python
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
... price_diff=Max("price", output_field=FloatField()) - Avg("price")
... )
{'price_diff': 46.85}
annotate()
使用 annotate()
子句可以生成每一个对象的汇总。当指定 annotate()
子句,QuerySet
中的每一个对象将对指定值进行汇总。
与 aggregate()
相同点,注释的名称是从聚合函数的名称和被聚合字段的名称自动派生的。您可以通过在指定注释时提供别名来覆盖这个默认名称。
与 aggregate()
不同的是,annotate()
不是终端子句。annotate()
子句的输出就是 QuerySet
;这个 QuerySet
可以像其他 QuerySet
一样进行操作,包括 filter()
, order_by()
,甚至可以对 annotate()
进行额外调用。
查询每个出版社的图书总数,并添加到"num_books"属性值中
python
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count("book"))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73
查询每个出版社评分大于5的书数量和小于等于5的书的数量,并添加到"above_5"和"below_5"的属性值中
python
>>> from django.db.models import Q
>>> above_5 = Count("book", filter=Q(book__rating__gt=5))
>>> below_5 = Count("book", filter=Q(book__rating__lte=5))
>>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
>>> pubs[0].above_5
23
>>> pubs[0].below_5
12
找出书籍数量排名前5的出版社,并添加到"num_books"属性值中
python
>>> pubs = Publisher.objects.annotate(num_books=Count("book")).order_by("-num_books")[:5]
>>> pubs[0].num_books
1323
还有个地方需要注意:使用 annotate()
组合多个聚合将产生错误的结果,因为它使用连接(joins)而不是子查询,下面是一个错误示例:
python
>>> book = Book.objects.first()
>>> book.authors.count()
2
>>> book.store_set.count()
3
>>> q = Book.objects.annotate(Count("authors"), Count("store"))
>>> q[0].authors__count
6
>>> q[0].store__count
6
对大部分聚合来说,没办法避免这个问题,但是,Count 聚合可以使用 distinct 参数来避免:
python
>>> q = Book.objects.annotate(
... Count("authors", distinct=True), Count("store", distinct=True)
... )
>>> q[0].authors__count
2
>>> q[0].store__count
3