Django Aggregation聚合
在当今根据需求而不断调整而成的应用程序中,通常不仅需要能依常规的字段,如字母顺序或创建日期,来对项目进行排序,还需要按其他某种动态数据对项目进行排序。Djngo聚合就能满足这些要求。
以下面的Model为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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() |
快速了解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # books总数量. >>> Book.objects.count() 2452 # Total number of books with publisher=BaloneyPress >>> Book.objects. filter (publisher__name = 'BaloneyPress' ).count() 73 # books的平均price. >>> from django.db.models import Avg >>> Book.objects. all ().aggregate(Avg( 'price' )) { 'price__avg' : 34.35 } # books的最大price. >>> from django.db.models import Max >>> Book.objects. all ().aggregate( Max ( 'price' )) { 'price__max' : Decimal( '81.20' )} # All the following queries involve traversing the Book<->Publisher # many-to-many relationship backward # 为每个publisher添加个num_books属性,即每个pulisher出版的book的数量. >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books = Count( 'book' )) >>> pubs [<Publisher BaloneyPress>, <Publisher SalamiPress>, ...] >>> pubs[ 0 ].num_books 73 # 根据num_book属性排序. >>> pubs = Publisher.objects.annotate(num_books = Count( 'book' )).order_by( '-num_books' )[: 5 ] >>> pubs[ 0 ].num_books 1323 |
聚合生成Generating aggregates over a QuerySet
Django有两种方法来生成聚合。第一种方法是为整个QuerySet生成聚合值,例如为全部的books生成price的平均值:
1 2 3 | >>> from django.db.models import Avg >>> Book.objects. all ().aggregate(Avg( 'price' )) { 'price__avg' : 34.35 } |
可以简略为:
1 2 | >>> Book.objects.aggregate(Avg( 'price' )) { 'price__avg' : 34.35 } |
函数aggregate()的参数是一系列聚合函数aggregate functions:
Avg
返回平均值
Count
class Count(field, distinct=False)
返回计数。当参数distinct=True时,返回unique的对象数目。
Max
返回最大值
Min
返回最小值.
StdDev
class StdDev(field, sample=False)
返回标准偏差
有一个参数sample
默认情况下sample=False,返回总体标准偏差,如果sample=True,返回样本标准偏差。
Sum
返回总值
Variance
class Variance(field, sample=False)
返回方差
有一个参数sample,默认返回总体方差,sample设为True时返回样本方差。
aggregate()方法被调用时,返回一个键值对字典,可以指定key的名字:
1 2 | >>> Book.objects.aggregate(average_price = Avg( 'price' )) { 'average_price' : 34.35 } |
如果你想生成多个聚合,你只需要添加另一个参数。所以,如果我们还想知道所有书的最高和最低的价格:
1 2 3 | >>> 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
这是生成聚合值的第二种方法。比如你要检索每本书有多少个作者。book和author是manytomany的关系,我们可以为每本书总结出这种关系。
每个对象的总结可以用方法annotate()生成:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 建立一个annotate QuerySet >>> from django.db.models import Count >>> q = Book.objects.annotate(Count( 'authors' )) # 第一个对象 >>> q[ 0 ] <Book: The Definitive Guide to Django> >>> q[ 0 ].authors__count 2 # 第二个对象 >>> q[ 1 ] <Book: Practical Django Projects> >>> q[ 1 ].authors__count 1 |
也可以指定生成属性的名字:
1 2 3 4 5 | >>> q = Book.objects.annotate(num_authors = Count( 'authors' )) >>> q[ 0 ].num_authors 2 >>> q[ 1 ].num_authors 1 |
和aggregate()不同,annotate()的输出是一个QuerySet。
联合聚合Joins and aggregates
目前为止,我们聚合查询的field都属于我们要查询的Model,我们也可以用其它Model的field来进行聚合查询,例如:
1 2 | >>> from django.db.models import Max , Min >>> Store.objects.annotate(min_price = Min ( 'books__price' ), max_price = Max ( 'books__price' )) |
这样就可以查询每个Store里面books的价格范围
联合链的深度可以随心所欲:
1 | >>> Store.objects.aggregate(youngest_age = Min ( 'books__authors__age' )) |
反向关系Following relationships backwards
通过book反向查询publisher:
1 2 | >>> from django.db.models import Count, Min , Sum , Avg >>> Publisher.objects.annotate(Count( 'book' )) |
返回的QuerySet的每个publisher都会带一个属性book_count。
查询出版最久的书的出版日期:
1 | >>> Publisher.objects.aggregate(oldest_pubdate = Min ( 'book__pubdate' )) |
查询每个作者写的书的总页数:
1 | >>> Author.objects.annotate(total_pages = Sum ( 'book__pages' )) |
查询所有作者写的书的平均rating:
1 | >>> Author.objects.aggregate(average_rating = Avg( 'book__rating' )) |
聚合和其它查询集操作Aggregations and other QuerySet clauses
filter() and exclude()
聚合可以和filter和exclude一起使用:
1 2 3 | >>> from django.db.models import Count, Avg >>> Book.objects. filter (name__startswith = "Django" ).annotate(num_authors = Count( 'authors' )) >>> Book.objects. filter (name__startswith = "Django" ).aggregate(Avg( 'price' )) |
可以根据聚合值进行筛选:
1 | >>> Book.objects.annotate(num_authors = Count( 'authors' )). filter (num_authors__gt = 1 ) |
编写一个包含annotate()和filter()从句的复杂查询时,要特别注意作用于QuerySet的从句的顺序顺序的不同,产生的意义也不同:
1 2 | >>> 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分)总数。在第一个查询中,注解在过滤器之前,所以过滤器对注解没有影响。在第二个查询中,过滤器在注解之前,所以,在计算注解值时,过滤器就限制了参与运算的对象的范围
order_by()
可以根据聚合值进行排序:
1 | >>> Book.objects.annotate(num_authors = Count( 'authors' )).order_by( 'num_authors' ) |
values()
通常,注解annotate是添加到每一个对象上的,一个执行了注解操作的查询集 QuerySet 所返回的结果中,每个对象都添加了一个注解值。但是,如果使用了values()从句,它就会限制结果中列的范围,对注解赋值的方法就会完全不同。就不是在原始的 QuerySet 返回结果中对每个对象中添加注解,而是根据定义在 values() 从句中的字段组合对先结果进行唯一的分组,再根据每个分组算出注解值,这个注解值是根据分组中所有的成员计算而得的:
1 | >>> Author.objects.values( 'name' ).annotate(average_rating = Avg( 'book__rating' )) |
这样的写法下,QuerySet会根据name进行组合,返回的是每个unique name的聚合值。如果有两个作者有相同的名字,这两个作者会被当做一个计算,他们的books会合在一起。
1 | >>> Author.objects.annotate(average_rating = Avg( 'book__rating' )).values( 'name' , 'average_rating' ) |
位置互换后,会为每个author都生成一个average_rating,而且只会输出每个author的name和average_rating。
默认排序下使用聚合:
1 2 3 4 5 6 7 8 | from django.db import models class Item(models.Model): name = models.CharField(max_length = 10 ) data = models.IntegerField() class Meta: ordering = [ "name" ] |
如果你想知道每个非重复的data值出现的次数,你可能这样写:
1 2 | # Warning: 不正确的写法 Item.objects.values( "data" ).annotate(Count( "id" )) |
这部分代码想通过使用它们公共的data值来分组Item对象,然后在每个分组中得到id值的总数。但是上面那样做是行不通的。这是因为默认排序项中的name也是一个分组项,所以这个查询会根据非重复的(data,name)进行分组,而这并不是你本来想要的结果。所以,你需要这样写来去除默认排序的影响:
1 | Item.objects.values( "data" ).annotate(Count( "id" )).order_by() |
Aggregating annotations
也可以根据annotation结果生成聚合值,例如计算每本书平均有几个作者:
1 2 3 | >>> from django.db.models import Count, Avg >>> Book.objects.annotate(num_authors = Count( 'authors' )).aggregate(Avg( 'num_authors' )) { 'num_authors__avg' : 1.66 } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探