这份指南描述通过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()
¶
以下是在上述模型的基础上,进行一般的聚合查询的方法:
# Total number of books.
>>> Book.objects.count()
2452
# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73
# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}
# Cost per page
>>> Book.objects.all().aggregate(
... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653}
# All the following queries involve traversing the Book<->Publisher
# many-to-many relationship backward
# 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
[<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
¶
Django的查询语法提供了一种方式描述所有图书的集合。
>>> Book.objects.all()
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}
查询集参考中列出了聚合函数的列表。
如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
>>> 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中的每个对象都会被注上特定的值。
比如,给图书添加作者数量的注解:
# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1
你可以在指定注解时,为默认名称提供一个别名:
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
annotate()。
有任何疑问的话,请检查 SQL query!
query 属性。
多个表上做了交叉连接,导致了多余的行聚合。
¶
但是有时,你也想对所查询对象的关联对象进行聚合。
然后 Django 在就会处理要读取的关联表,并得到关联对象的聚合。
例如,要查找每个商店提供的图书的价格范围,您可以使用注释:
>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
图书模型,然后对每本书的价格进行聚合,得出最小值和最大值。
如果你想知道所有书店中最便宜的书和最贵的书价格分别是多少:
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
例如,想得到所有作者当中最小的年龄是多少,就可以这样写:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
¶
关联模型的小写名称和双下划线也用在这里。
Book 的外键反转关系):
>>> from django.db.models import Count, Min, Sum, Avg
>>> Publisher.objects.annotate(Count('book'))
book__count。
我们也可以按照每个出版商,查询所有图书中最旧的那本:
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
'book__pubdate__min'。)
Book的多对多的反转关系):
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
book__pages__sum。)
或者查询所有图书的平均评分,这些图书由我们存档过的作者所写:
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
'book__rating__avg'。)
¶
¶
exclude()) 都会对聚合涉及的对象进行限制。
>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
¶
exclude() 子句中使用别名。
例如,要得到不止一个作者的图书,可以用:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
这个查询首先生成一个注解结果,然后再生成一个作用于注解上的过滤器。
¶
QuerySet的子句的顺序。
annotate() 不能交换顺序,下面两个查询就是不同的:
>>> Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
另一个查询:
>>> Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
在第二个查询中,过滤器在注解之前,所以,在计算注解值时,过滤器就限制了参与运算的对象的范围。
¶
annotate()子句的一部分。
QuerySet进行排序:
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
¶
这个注解值是根据分组中所有的成员计算而得的:
例如,考虑一个关于作者的查询,查询出每个作者所写的书的平均评分:
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
这段代码返回的是数据库中所有的作者以及他们所著图书的平均评分。
values()子句,结果是完全不同的:
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
两个作者的所有评分都将被计算为一个平均分。
¶
values() 子句产生的分组来计算注解。
values() 子句只能限制输出的字段范围。
annotate() 子句的顺序:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
average_rating 注解会返回在输出结果中。
annotate() 子句。
annotate() 子句之后,你需要显式地包含聚合列。
¶
在做计数时,就会表现地格外明显:
通过例子中的方法,假设有一个这样的模型:
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]
data值出现的次数,可以这样写:
# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))
所以,你应该这样改写:
Item.objects.values("data").annotate(Count("id")).order_by()
data ,这样并不会有副作用,这是因为查询分组中只有这么一个角色了。
values()中的字段。
注意
API stability 原则)。
¶
annotate() 子句的一部分。
例如,如果你想计算每本书平均有几个作者,你先用作者总数注解图书集,然后再聚合作者总数,引入注解字段:
>>> from django.db.models import Count, Avg
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}