Django - 模型 聚合

本文介绍了Django中模型聚合的概念,包括在QuerySet上生成聚合、为每个QuerySet项生成聚合、结合多个聚合等,详细讲解了annotate()和aggregate()方法的使用,以及如何处理连接和聚合、与其他QuerySet字句的配合,展示了聚合在数据库查询中的应用场景。
摘要由CSDN通过智能技术生成

参考文献:Django官方文档 模型聚合 友情赞助:有道词典

目录

聚合(Aggregation)

速查表

在 QuerySet 上生成聚合

Generating aggregates for each item in a QuerySet(为 QuerySet 中的每个项生成聚合)

combining multiple aggregations(结合多个聚合)

Joins and aggregates(连接和聚合)

Following relationships backwards(反向关系)

Aggregations and other QuerySet clauses(聚合和其他 QuerySet 字句)

filter() 和 exclude()

order_by()

values()

Aggregating annotations(聚合注释)


聚合(Aggregation)

Django 的数据库抽象 API 描述了使用 Django queries 来增删改查单个对象的方法。然而,有时候你要获取的值需要根据一组对象聚合后才能得到。这个主题指南描述了如何使用 Django queries 来生成和返回聚合值的方法。

整篇指南我们将引用以下模型。这些模型用来记录多个网上书店的库存:

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)

速查表

下面是根据以上模型执行常见的聚合查询:

# 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')}

# Difference between the highest priced book and the average price of all books.
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
{'price_diff': 46.85}

# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.

# 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
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73

# Each publisher, with a separate count of books with a rating above and below 5
>>> from django.db.models import Q
>>> above_5 = Count('book', filter=Q(book__rating__gt=5))
>>> below_5 = Count('book', filter=Q(book__rating__lte=5))
>>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
>>> pubs[0].above_5
23
>>> pubs[0].below_5
12

# 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

在 QuerySet 上生成聚合

Django 提供了两种生成聚合的方法。

第一种方法是从整个 QuerySet 生成汇总值。比如你想要计算所有在售书的平均价格。Django 的查询语法提供了一种用来描述所有图书集合的方法:

>>> Book.objects.all()

可以通过在 QuerySet 后添加 aggregate() 子句来计算 QuerySet 对象的汇总值。(PS:aggregate() 是用于属于 QuerySet 整个对象的汇总值)

>>> 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}

aggregate() 子句的参数描述了我们想要计算的聚合值,在本例中,是 Book 模型上 price 字段的平均值。可以在 QuerySet referance 中找到可用的聚合函数列表。

aggregate() 是 QuerySet 的一个终端子句,当调用它时,返回一个 name-value 字典。name 是聚合值的标识符;该值是计算得到的聚合。name 由字段名和聚合函数名自动生成。如果您想手动指定聚合值的名称,可以在指定聚合子句时提供该名称:

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}

如果希望生成多个聚合,只需向 aggregate() 子句添加另一个参数。所以,如果我们还想知道所有书籍的最高和最低价格,进行以下查询:

>>> 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')}

Generating aggregates for each item in a QuerySet(为 QuerySet 中的每个项生成聚合)

生成聚合值的第二种方法是为 QuerySet 中的每个对象生成独立的聚合。例如,如果您正在检索图书列表,您可能想知道有多少作者为每本书贡献了内容。每一本书都与作者有多对多的关系;我们想为 QuerySet 中的每本书聚合这种关系。

可以使用 annotate() 子句生成每个对象的聚合。当指定了 annotate() 子句时,QuerySet 中的每个对象都将使用指定的值进行注释。

这些注释的语法与用于 aggregate() 子句的语法相同。每个要 annotate() 的参数都描述要计算的聚合。例如,用作者的数量来标注书籍:

# 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

与 aggregate() 一样,annotation 的名称自动派生自聚合函数的名称和被聚合字段的名称。您可以通过在指定注释时提供别名来覆盖此默认名称:

>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1

与 aggregate() 不同,annotate() 不是一个终端子句。annotate() 子句的输出是 QuerySet;可以使用任何其他 QuerySet 操作修改这个 QuerySet,包括 filter()、order_by(),甚至额外的调用来 annotate()。

combining multiple aggregations(结合多个聚合)

将多个聚合与 annotate() 组合将产生错误的结果,因为使用的是连接而不是子查询:

>>> 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 # wrong
>>> q[0].store__count
6 # wrong

对于大多数聚合,没有办法避免这个问题,但是,Count 聚合有一个 distinct 参数,可能会有所帮助:

>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
>>> q[0].authors__count
2
>>> q[0].store__count
3

注解:如果有疑问,检查SQL查询语句。为了了解查询中发生了什么,请考虑检查 QuerySet 的 query 属性。


Joins and aggregates(连接和聚合)

到目前为止,我们已经处理了属于被查询模型的字段的聚合。然而,有时您想要聚合的值属于与您正在查询模型的相关模型。

当指定要在聚合函数中聚合的字段时,Django 允许您使用与在过滤器中引用相关字段时相同的双下划线表示法。Django 将处理检索和聚合相关值所需的任何表连接。

例如,要查找每个书店提供的图书的价格范围,可以使用 annotation:

>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))

这告诉 Django 检索 Store 模型,( 通过多对多关系 ) 与 Book 模型连接,并在 Book 模型的 price 字段上聚合,以生成最小值和最大值。

同样的规则也适用于 aggregate() 子句。如果你想知道在任何一家书店出售的任何一本书的最低和最高价格,你可以使用以下组合:

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))

连接链可以根据您的需要加深,例如,要提取任何一本书最年轻作者的年龄,您可以发出以下查询:

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))

Following relationships backwards(反向关系)

与跨越关系的查找类似,模型或与您正在查询的模型相关的模型字段上的 aggregations 和 annotations 可以包括遍历“反向”关系。这里也使用了相关模型的小写名称和双下划线。

例如,我们可以要求所有 publishers ,用他们各自的图书总库存计数器进行 annotated ( 注意我们如何使用 book 指定 Publisher->Book reverse foreign key hop ):(没看懂)

>>> from django.db.models import Avg, Count, Min, Sum
>>> Publisher.objects.annotate(Count('book'))

QuerySet 结果集中的每个 Publisher 都有一个名为 book_count 的额外属性。

我们还可以向每一位 publisher 索要其中一本最古老的书:

>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

生成的 dictionary 将有一个名为 oldest_pubdate 的键。如果没有指定这样的别名,它将是相当长的 book_pubdate_min。

这不仅适用于外键,也适用于多对多关系。例如,我们可以求每个作者,考虑到作者 ( 合作 ) 所写的所有书籍,用总页数来注释( 注意我们如何使用 book 来指定 Author—> Book reverse many-to-many hop ):(没看懂)

>>> Author.objects.annotate(total_pages=Sum('book__pages'))

QuerySet 结果集中的每个作者都有一个名为 total_pages 的额外属性。如果没有指定这样的别名,那么它将是相当长的book_pages_sum。

或求我们存档的所有作者所写书籍的平均评分:

>>> Author.objects.aggregate(average_rating=Avg('book__rating'))

生成的 dictionary 有一个名为 average_rating 的键。如果没有指定这样的别名,它将是相当长的 book_rating_avg。


Aggregations and other QuerySet clauses(聚合和其他 QuerySet 字句)

filter() 和 exclude()

Aggregates 还可以加入 filters。应用于普通模型字段的任何 filter() ( 或 exclude() ) 都会对考虑用于聚合的对象进行约束。

当与 annotate() 子句一起使用时,filter 的作用是约束其计算 annotation 的对象。例如,您可以使用以下查询生成以 Django 开头的所有图书的带注释的列表:

>>> from django.db.models import Avg, Count
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

Filtering on annotations

还可以过滤带注释的值。注释的别名可以在 filter() 和 exclude() 子句中使用,方法与任何其他模型字段相同。

例如,要生成包含多个作者的图书列表,可以发出以下查询:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

该查询生成一个带注释的结果集,然后基于该注释生成一个过滤器。

如果需要两个带有两个单独过滤器的注释,可以对任何聚合使用 filter 参数。例如,生成一个拥有高评价书籍数量的作者列表:

>>> highly_rated = Count('books', filter=Q(books__rating__gte=7))
>>> Author.objects.annotate(num_books=Count('books'), highly_rated_books=highly_rated)

结果集中的每个作者都有 num_books 和 highly_rated_books 属性。

注解:在 filter 和 QuerySet.filter() 之间进行选择:避免在单个 annotation 或 aggregation 中使用 filter 参数。使用 QuerySet.filter() 来排除行更有效。Aggregation filter argument 仅在对具有不同条件的相同关系使用两个或多个聚合时才有用。

Order of annotate() and filter() clauses (annotate() 和 filter() 子句的顺序)

在开发包含 annotate() 和 filter() 子句的复杂查询时,要特别注意子句应用于 QuerySet 的顺序。

当将 annotate() 子句应用于查询时,将根据查询的状态计算注释(即先 filter,再 annotate),直到请求注释为止。这意味着 filter() 和 annotate() 不是可交换操作。

给出:

  1. Publisher A has two books with ratings 4 and 5.(PA 有两本评分分别是4和5的书)
  2. Publisher B has two books with ratings 1 and 4.(PB有两本评分分别是1和4的书)
  3. Publisher C has one book with rating 1.(PC有一本评分为1的书)

下面是一个 Count 聚合的例子:(这什么神奇的写法???)

>>> 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
(<Publisher: B>, 1)

这两个查询都返回一个出版商列表,其中至少有一本书的评级超过3.0,因此不包括PublisherC。

在第一个查询中,annotation 在 filter 之前,所以 filter 对 annotation 没有影响。distinct=True 是避免查询错误所必需的。

第二个查询计算每个出版商的评分超过3.0的图书数量。filter 位于 annotation 之前,因此 annotation 在计算注释时约束考虑的对象。

下面是 Avg 聚合的另一个例子:

>>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5)  # (1+4)/2

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 4.0)  # 4/1 (book with rating 1 excluded)

第一个查询求对至少有一本评级超过 3.0 的图书的出版商的所有图书的平均评级。第二个查询求出版商图书的平均评分,仅针对那些评分超过 3.0 的图书。

很难凭直觉知道 ORM 将如何将复杂的查询集转换成 SQL 查询,因此当有疑问时,使用 str(queryset.query) 检查SQL并编写大量测试。

order_by()

注释可以用作排序的基础。当您定义 order_by() 子句时,您提供的聚合可以引用查询中作为 annotate() 子句的一部分定义的任何别名。

例如,要按对本书有贡献的作者的数量排序,可以使用以下查询:

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

values()

通常,注解值会添加到每个对象上,即一个被注解的 QuerySet 将会为初始 QuerySet 的每个对象返回一个结果集。然而,当使用 values() 子句来对结果集进行约束时,生成注解值的方法会稍有不同。不是在原始 QuerySet 中对每个对象添加注解并返回,而是根据定义在 values() 子句中的字段组合先对结果进行分组,再对每个单独的分组进行注解,这个注解值是根据分组中所有的对象计算得到的。

下面是一个关于作者的查询例子,查询每个作者所著书的平均评分:

>> Author.objects.annotate(average_rating=Avg('book__rating'))

这段代码返回的是数据库中的所有作者及其所著书的平均评分。

但是如果你使用 values() 子句,结果会稍有不同:

>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

在这个例子中,作者会按名字分组,所以你只能得到不重名的作者分组的注解值。这意味着如果你有两个作者同名,那么他们原本各自的查询结果将被合并到同一个结果中;两个作者的所有评分都将被计算为一个平均分。

annotate() 和 values() 的顺序

和使用 filter() 一样,作用于某个查询的 annotate() 和 values() 子句的顺序非常重要。如果 values() 子句在 annotate() 之前,就会根据 values() 子句产生的分组来计算注解。 然而如果 annotate() 子句在 values() 之前,就会根据整个查询集生成注解。这种情况下,values() 子句只能限制输出的字段。 举个例子,如果我们颠倒上个例子中 values() 和 annotate() 的顺序:
 

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

这段代码将为每个作者添加一个唯一注解,但只有作者姓名和 average_rating 注解会返回在输出结果中。

您还应该注意,average_rating 已经显式地包含在要返回的值列表中。这是必需的,因为 values() 和 annotate() 子句的顺序不同。

如果 values() 子句位于 annotate() 子句之前,则任何注释都会自动添加到结果集中。但是,如果 values() 子句应用于 annotate() 子句之后,则需要显式地包含聚合列。

Interaction with default ordering or order_by() (与默认 ordering 或 order_by() 交互)

在 queryset 的 order_by() 部分中提到的字段 ( 或者在模型的默认顺序中使用的字段 )即使在 values() 调用中没有指定它们但仍在选择输出数据时使用。这些额外的字段用于将“like”结果分组在一起,它们可以使其他相同的结果行看起来是分开的。这一点在计算时尤其明显。(没咋看懂,看例子吧)

举个例子,假设你有这样一个模型:

from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()

    class Meta:
        ordering = ["name"]

这里的重要部分是 name 字段是默认排序字段。如果你想计算每个不同的数据值出现的次数,你可以这样做:

# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))

…它将根据项目对象的公共 data 值对其进行分组,然后计算每个组中的 id 值的数量。只是它不会完全奏效。默认的按 name 排序也将在分组中发挥作用,因此该查询将按不同的( data、name)对进行分组,这不是您想要的。相反,您应该构造这个queryset:

Item.objects.values("data").annotate(Count("id")).order_by()

…清除查询中的任何顺序。您还可以按数据排序,而不会产生任何有害影响,因为数据已经在查询中发挥了作用。

这种行为和在 distinct()  的 queryset 文档提到的及一般规则是一样的:通常你不想额外列成为结果集的一部分,因此清除 ordering,或至少确保它是仅限于在 values() 调用的字段。

注解:

您可能会问Django为什么不为您删除无关的列。主要原因是 distinct() 和其他一些地方:Django不会删除您指定的顺序约束( 和我们不能改变其他方法的行为,因为这样会违反我们的 API的稳定性 政策 )。


Aggregating annotations(聚合注释)

您还可以根据注释的结果生成聚合。定义 aggregate() 子句时,提供的聚合可以引用查询中作为 annotate() 子句的一部分定义的任何别名。

例如,如果您想计算每本书的平均作者数量,那么您首先使用作者数量对图书集进行注释,然后聚合该作者数量,并引用 annotation 字段:

>>> from django.db.models import Avg, Count
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Django模型是ORM(Object Relational Mapping)的实现,它允许你通过Python代码来操作数据库,而不必手动编写SQL语句。下面是一些常见的Django模型操作: 1. 定义模型 在models.py文件中,定义一个继承自django.db.models.Model的类作为模型。在该类中定义属性,每个属性都是一个字段,可以是CharField、IntegerField、BooleanField等等。 2. 迁移数据库 运行`python manage.py makemigrations`命令,将模型定义转换为迁移文件。然后运行`python manage.py migrate`命令,将迁移应用到数据库。 3. 创建、查询、更新和删除对象 创建对象的方法是通过模型类的构造函数来实现的,例如`MyModel.objects.create(field1=value1, field2=value2)`。查询对象的方法是通过模型类的Manager对象来实现的,例如`MyModel.objects.filter(field1=value1)`。更新对象的方法是通过查询得到的QuerySet对象调用update函数来实现的,例如`MyModel.objects.filter(field1=value1).update(field2=new_value)`。删除对象的方法是通过查询得到的QuerySet对象调用delete函数来实现的,例如`MyModel.objects.filter(field1=value1).delete()`。 4. 关联查询 在模型中定义外键或多对多关系字段,然后可以使用ORM进行关联查询。例如,如果一个模型有一个外键字段指向另一个模型,那么可以使用`my_model.related_field`来获取该关联对象。 5. 聚合和分组查询 使用ORM可以很方便地进行聚合和分组查询。例如,可以使用`MyModel.objects.aggregate(Sum('field'))`来计算某个字段的总和,使用`MyModel.objects.annotate(count=Count('field')).filter(count__gt=10)`来查询某个字段的值大于10的数据。 以上是Django模型操作的一些基本方法,还有很多高级用法可以去官方文档中了解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值