聚合

本篇文章主要讲解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))
posted @ 2024-06-17 16:15  Python习者  阅读(7)  评论(0)    收藏  举报