聚合
本篇文章主要讲解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)
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)
QuerySet基础上进行聚合
Django提供两种创建聚合的方法。第一种方法就是在总的QuerySet上产生汇总数据。
- 通过QuerySet实例对象的aggregate方法进行聚合
- 导入聚合计算的方法,并传入aggregate方法中
- 返回值是一个字典
# 计算书籍的平均售价
from django.db.models import Avg
Book.objects.all().aggregate(Avg("price"))
# 结果类似 {'price__avg':34.35}
返回值字典的键名是由Django根据字段名和聚合方法自动生成的,如果要覆盖这种行为,可以在aggregate方法中传入参数。
Book.objects.all().aggregate(average_price=Avg("price"))
# 结果类似{'average_price':34.5}
aggregate方法也支持一次利用多种聚合方法
from django.db.models import Avg, Max, Min
Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
为QuerySet中的每个元素产生聚合结果
为每个QuerySet中的元素产生聚合结果,主要利用annotate方法。并在其中传入响应的聚合方法。
from django.db.models import Count
q = Book.objects.annotate(Count('authors'))
q[0].authors__count
获取相关数据的属性名是Django自动产生的,如果要覆盖这种行为,可以为其传递参数值。
q = Book.objects.annotate(num_authors=Count('authors'))
q[0].num_authors
annotate()方法的返回值是一个QuerySet,其结果依旧支持filter,order_by等方法。
组合多个聚合
在annotate()方法中传入多个聚合方法时,会导致错误的结果,因为这种情况下会执行join操作,而不是独立的子查询。
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参数解决。
q = Book.objects.annotate(
Count("authors", distinct=True),
Count("store", distinct=True)
)
正向跨表字段聚合
当需要执行跨表字段的聚合查询时,Django支持双下划线指向相关字段,此时Django执行join查询,并由此返回相应的聚合结果。
from django.db.models import Max, Min
Store.objects.annotate(min_price=Min("books__price"), max_price=("books__price"))
Store.objects.aggregate(min_price=Min("books__price"), max_price=("books__price"))
反向跨表聚合查询
当进行反向查询聚合时,在聚合函数中写入关联的模型小写名称。
from django.db.models import Avg, Count, Min, Sum
Publisher.objects.annotate(Count("book"))
Publisher.objects.aggregate(oldest_pubdate=Min("book__pubdate"))
聚合和QuerySet其他方法的聚合
filter和exclude
数据模型当执行filter和exclude方法后,过滤掉的数据都会反应在聚合的结果上。
当在annotate方法前执行filter方法,将限制有哪些数据对象进行聚合计算。
from django.db.models import Avg, Count
# 计算Django开头书籍的作者数量
Book.objects.filter(name__startswith="Django").annotate(num_authors=Count("authors"))
# 计算Django开头书籍的均价
Book.objects.filter(name__startswith="Django").aggregate(Avg("price"))
聚合结果的过滤
Annotate结果还可以被进一步过滤,其字段还可被用于filter和exclude方法。
Book.objects.annotate(num_authors=Count("authors").filter(num_authors__gt=1)
当需要进行两个聚合,且每个聚合具有不同的过滤条件时,可以使用聚合函数的filter参数。
highly_rated = Count("book", filter=Q(book__rating__gte=7))
Author.objects.annotate(
num_books=Count("book"),
hightly_rated_books=highly_rated)
注意!
避免在只有单个annotation或聚合时使用filter参数。使用QuerySet的filter方法效率更高。聚合函数的filter参数只有在相同的查询中,不同聚合具有不同的条件时使用
聚合和过滤的顺序
当复杂查询中即包括annotate()和filter()时,需要注意其作用的QuerySet。
a,b = Publisher.objects.annotate(num_books=Count("book",distinct=True)).fitler(book__rating_gt=3)
a,b = Publisher.objects.filter(book__rating_gt=3).annotate(num_books=Count("book",distinct=True))
两者的结果是不一样的,因为annotate和filter只针对其前面的QuerySet作用。
annotate与values共同作用
annotate和values共同作用时具有意想不到的结果。因为annotate默认以每个模型的实例对象为一组进行聚合计算;而引入values后,将一个其形参值进行分组,如果在此基础上进行annotate将是以此为组进行聚合计算。
Author.objects.annotate(average_rating=Avg("book__rating"))
Author.objects.values("name").annotate(average_rating=Avg("book__rating"))
annotate和values的顺序影响
当values方法在annotate方法前面执行时,返回结果中会自动包含annotate中形参的字段;
当annotate方法在values方法前面执行时,需要在values中明确要输出的字段名。
在annotate字段中聚合
annotate字段聚合后也支持进一步的aggregate操作。
from django.db.models import Avg, Count
Book.objects.annotate(num_authors=Count("authors")).aggregate(Avg("num_authors")
在空组上聚合
默认情况下,Django聚合在空组上的结果为None,如果要改变这种行为,可以在聚合函数中传入default参数进行设置。
from django.db.models import Sum
Book.objects.filter(name__contains="web").aggregate(Sum("price"))
Book.objects.filter(name__contains="web").aggregate(Sum("price",default=0))

浙公网安备 33010602011771号