【Django】聚合在Django的详细解析以及运用在企业级项目里的方法

2022-12-13 18:30:05 浏览数 (1)

聚合

Django数据库抽象API描述了使用Django查询来添加、删除、查询和修改单个对象的方法。然而,有时需要根据一组对象聚合您想要获得的值。本主题指南介绍如何使用Django查询生成和返回聚合值。

代码语言:javascript复制
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)

Django提供了两种生成聚合的方法。第一种方法是从整个QuerySet生成摘要值。例如,想计算所有在售图书的平均价格。Django的查询语法提供了一种描述所有藏书的方法。 传递给聚合()的参数描述了要计算的聚合值。在此示例中,将计算Book模型上价格字段的平均值。可以在QuerySet引用中找到可用聚合函数的列表。 Aggregate()是QuerySet的一个结束语句。使用后,它将返回一个“name value”字典,其中“name”是聚合值的标志,“value”是计算的聚合结果。名称是根据字段名称和聚合函数自动生成的。如果要指定聚合值的名称,可以在指定聚合子句时提供指定的名称。

代码语言:javascript复制
>>> 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

与聚合()不同,annotate()不是终端子句。annotate()子句的输出是QuerySet;此QuerySet由其他QuerySet操作修改,包括filter()',order_by(),您甚至可以进行其他调用来注释()。

代码语言:javascript复制
>>> 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

连接(Joins)和聚合

到目前为止,我们已经处理了查询模型字段的聚合。但是,有时要聚合的值属于所查询模型的关联模型。 在聚合函数中指定聚合字段时,Django允许您在筛选相关字段时使用相同的双下划线符号。Django将处理需要检索和聚合相关值的任何表连接。 例如,要查找每个书店提供的图书价格范围,可以使用以下注释:

代码语言:javascript复制
>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
代码语言:javascript复制
>>> from django.db.models import Avg, Count, Min, Sum
>>> Publisher.objects.annotate(Count('book'))

(结果字典中将有一个名为“oldest_pubdate”的关键字。如果未指定此类别名,则它将是一个长名称“book__pubdate__min”。) 它不仅用于外键,还用于多对多关系。例如,我们可以查询每个作者,并注释作者(联合)创建的书籍的总页数(注意我们如何使用“book”指定author->book反转多对多跳转):

代码语言:javascript复制
Author.objects.annotate(total_pages=Sum('book__pages'))

聚合也可以参与过滤。应用于公共模型字段的任何过滤器()(或exclude())都将具有约束被认为是聚合的对象的效果。 当使用annotate()子句时,过滤器具有约束注释对象计算的效果。例如,可以使用查询生成所有书籍的注释列表。此列表的标题以“Django”开头。

代码语言:javascript复制
>>> from django.db.models import Avg, Count
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

annotate()和filter()子句的顺序¶ 在开发涉及annotate()和filter()子句的复杂查询时,请特别注意应用于QuerySet的子句的顺序。 当注释()子句应用于查询时,将根据查询状态计算注释,直到请求的注释。这实际上意味着filter()和annotate()不是可互换的操作。 例如: 出版商A有两本评分为4和5的书。 出版商B有两本评分为1和4的书。 出版商C有一本评分为1的书。 以下是Count聚合的示例

代码语言:javascript复制
>>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books

两个查询返回至少有一本书的评级为3的出版商列表,因此不包括C。 在第一个查询中,注释优先于过滤器,因此过滤器不会影响注释。Distinct=True用于避免查询错误。 第二个查询查询每个出版商得分超过3的图书数量。过滤器优先于注释,因此过滤器限制了计算注释时要考虑的对象。 第一个查询请求具有至少一本得分大于3的书的平均得分。第二个查询仅请求得分超过3的作者书的平均分数。 很难直观地理解ORM如何将复杂的查询集转换为SQL查询。因此,如果有疑问,请使用str(queryset.query)`检查SQL并编写大量测试。

0 人点赞