模型
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()
在查询集上生成聚合
Django提供了两种生成聚合的方法。第一种方法是从整个查询集生成统计值
比如想要计算所有在售书籍的平均价钱
>>> Book.objects.aggregate(Avg('price')) {'price__avg': 34.35}
all 可以省略。
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。可以为聚合值指定一个名称
from django.db.models import Avg, Max, Min
Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 97.23913, 'price__max': Decimal('210.00'), 'price__min': Decimal('10.50')}
为查询集的每一项生成聚合
使用annotate子句为每个对象注上特定的值。
给图书添加作者数量的注解:
q = Book.objects.annotate(Count('authors'))
q[11].authors__count
1
aggregate()一样,注解的名称也根据聚合函数的名称和聚合字段的名称得到的。你可以在指定注解时,为默认名称提供一个别名
q = Book.objects.annotate(nums=Count('authors'))
要想弄清楚你的查询到底发生了什么,可以考虑检查你QuerySet的 query 属性。
连接和聚合
对关联对象进行聚合
st = Store.objects.annotate(min_p=Min('books__price'), max_p=Max('books__price'))
st[1].min_p
Decimal('21.00')
st[3].min_p
Decimal('42.00')
首先获取书店模型,然后连接图书模型,计算每个商店图书价格最低和最高。商店1最便宜书价格21.00。
商店3最便宜书42.00.
st2 = Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
st2
{'max_price': Decimal('210.00'), 'min_price': Decimal('10.50')}
得出所有的书店最便宜的书和最贵的书
继续延伸,如求所有作者年龄最小
Store.objects.aggregate(min_age=Min('books__authors__age'))
{'min_age': 4}
遵循反向关系
查询所有出版商,并注上一共出了多少本书
p = Publisher.objects.annotate(book_nums=Count('book'))
p[0].book_nums
0
p[11].book_nums
1
p[15].book_nums
1
QuerySet结果中的每一个Publisher都会包含一个额外的属性叫做book__nums。
查询所有图书中最旧的那本
Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
{'oldest_pubdate': datetime.date(2017, 8, 19)}
查询每个作者,注上它写的所有书(以及合著的书)一共有多少页(注意我们如何使用 'book'来指定Author -> Book的多对多的反转关系
Author.objects.annotate(total_page=Sum('book__pages'))[15].total_page
60
聚合和其他查询集子句
filter()和exclude()
使用annotate() 子句时,过滤器有限制注解对象的作用。例如,你想得到每本以 "Django" 为书名开头的图书作者的总数:
>>> from django.db.models import Count, Avg >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
用aggregate()子句时,过滤器有限制聚合对象的作用。例如,你可以算出所有以 "Django" 为书名开头的图书平均价格:
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
对注解过滤
注解值也可以被过滤。 像使用其他模型字段一样,注解也可以在filter()和exclude()子句中使用别名。
例如,要得到不止一个作者的图书,可以用:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
这个查询首先生成一个注解结果,然后再生成一个作用于注解上的过滤器。
annotate() 和filter() 子句的顺序
编写一个包含 annotate() 和 filter() 子句的复杂查询时,要特别注意作用于QuerySet的子句的顺序。
当一个annotate() 子句作用于某个查询时,要根据查询的状态才能得出注解值,而状态由 annotate() 位置所决定。以这就导致filter() 和 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'))
两个查询都返回了至少出版了一本好书(评分大于 3 分)的出版商。 但是第一个查询的注解包含其该出版商发行的所有图书的总数;而第二个查询的注解只包含出版过好书的出版商的所发行的图书总数(过滤掉了小于3分的出版商)。 在第一个查询中,注解在过滤器之前,所以过滤器对注解没有影响。 在第二个查询中,过滤器在注解之前,所以,在计算注解值时,过滤器就限制了参与运算的对象的范围
values()
使用values()先对结果进行唯一分组,再根据每个分组算出注解值。
查询出每个作者所写的书的平均评分:
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
这段代码返回的是数据库中所有的作者以及他们所著图书的平均评分。
但是如果你使用了values()子句,结果是完全不同的:
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
在这个例子中,作者会按名称分组,所以你只能得到某个唯一的作者分组的注解值。 这意味着如果你有两个作者同名,那么他们原本各自的查询结果将被合并到同一个结果中;两个作者的所有评分都将被计算为一个平均分
annotate() 和values() 字句的顺序
如果values()在前,就会根据values子句产生的分组来计算注解。
如果values()在后,根据整个查询集生成注解。values() 只能限制字段输出。
与默认排序交换或order_by()
在查询集中的order_by() 部分(或是在模型中默认定义的排序项) 会在选择输出数据时被用到,即使这些字段没有在values() 调用中被指定。这些额外的字段可以将相似的数据行分在一起,也可以让相同的数据行相分离。在做计数时,就会表现地格外明显:
通过例子中的方法,假设有一个这样的模型:
from django.db import models class Item(models.Model): name = models.CharField(max_length=10) data = models.IntegerField() class Meta: ordering = ["name"]
关键的部分就是在模型默认排序项中设置的name字段。如果你想知道每个非重复的data值出现的次数,可以这样写:
# Warning: not quite correct! Item.objects.values("data").annotate(Count("id"))
...这部分代码想通过使用它们公共的 data 值来分组 Item对象,然后在每个分组中得到 id 值的总数。但是上面那样做是行不通的。这是因为默认排序项中的 name也是一个分组项,所以这个查询会根据非重复的 (data, name) 进行分组,而这并不是你本来想要的结果。所以,你应该这样改写:
Item.objects.values("data").annotate(Count("id")).order_by()
...这样就清空了查询中的所有排序项。 你也可以在其中使用 data ,这样并不会有副作用,这是因为查询分组中只有这么一个角色了
order_by()
注解可以用来做为排序项。
根据一本图书作者数量的多少来进行排序:
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
聚合注解
每本书平均有几个作者
计算每本书平均有几个作者,先计算每本书有几个作者,再计算平均值。引入注解字段
Book.objects.annotate(auths=Count('authors')).aggregate(Avg('auths'))
{'auths__avg': 0.913}