正文
Django 数据库抽象API 描述了使用Django 查询来增删查改单个对象的方法。 然而,有时候你要获取的值需要根据一组对象聚合后才能得到。 这个主题指南描述了如何使用Django的查询来生成和返回聚合值的方法。
整篇指南我们都将引用以下模型。这些模型用来记录多个网上书店的库存。
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)
num_awards = models.IntegerField()
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)
pubdate = models.DateField()
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
registered_users = models.PositiveIntegerField()
速查表
下面是在上面的模型上如何执行常见的聚合查询:
# book 总数.
>>> Book.objects.count()
2452
# publisher=BaloneyPress的book总数.
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73
# 所有book的平均价格.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
# 所有book的最高价格
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}
# 每页均价
>>> from django.db.models import F, FloatField, Sum
>>> Book.objects.all().aggregate(
... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653}
# 下面的所有查询都涉及到遍历 Book<->Publisher
# foreign key relationship backwards.
# Each publisher, each with a count of books as a "num_books" attribute.
>>> 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
# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323
QuerySet
聚合
Django提供了两种生成聚合的方法。第一种方法是从整个 QuerySet
生成统计值。 比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有图书的集合。:
>>> Book.objects.all()
我们需要在 QuerySet
对象上计算出汇总的值。这可以通过在 QuerySet
后面添加 aggregate()
子句来实现:
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
其实 all()
在这里可以省略,简化为:
>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}
aggregate()
子句的参数是想要计算的聚合值,在这个例子中,是 Book
模型中 price
字段的平均值。 查询集参考 有所有的聚合函数。
aggregate()
是 QuerySet
的一个终止子句,意思是说,它返回一个包含键值对的字典。 键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。 如果你想要为聚合值指定一个名称,可以在聚合子句中指定:
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
如果你想要计算多个聚合,你可以在 aggregate()
子句作为参数添加。比如, 如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
>>> 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')}
QuerySet
逐个对象的聚合
生成汇总值的第二种方法,是为 QuerySet
中每一个对象都生成一个独立的汇总值。 比如,你可能想知道每一本书有多少作者参与。每本书和作者是多对多的关系。 我们需要汇总 QuerySet
中每本书的这种关系。
逐个对象的汇总结果可以由 annotate()
子句生成。 当 annotate()
子句被指定之后, QuerySet
中的每个对象都会被注上特定的值。
annotate(注解)的语法都和 aggregate()
子句相同。 annotate()
的每个参数都描述了将要被计算的聚合值。
比如,给图书添加作者数量的注解:
# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# 查询queryset中的第一个对象
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# 查询queryset中的第二个对象
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1
和 aggregate()
一样,注解的名称也根据聚合函数的名称和聚合字段的名称自动生成。 同样可以在指定注释时,通过提供别名来覆盖此默认名称:
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors