django聚合查询
聚合¶
Django 数据库抽象API 描述了使用Django 查询来增删查改单个对象的方法。然而,有时候你需要获取的值需要根据一组对象聚合后才能得到。这份指南描述通过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)
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()
速查表¶
急着用吗?以下是在上述模型的基础上,进行一般的聚合查询的方法:
# 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')} # Cost per page
>>> Book.objects.all().aggregate(
... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653} # All the following queries involve traversing the Book<->Publisher
# many-to-many relationship backward # 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
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73 # 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
在查询集上生成聚合¶
Django提供了两种生成聚合的方法。第一种方法是从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有图书的集合。
>>> Book.objects.all()
我们需要在QuerySet.对象上计算出总价格。这可以通过在QuerySet后面附加aggregate() 子句来完成。
>>> 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字段的平均值。查询集参考中列出了聚合函数的列表。
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> 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')}
为查询集的每一项生成聚合¶
生成汇总值的第二种方法,是为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()一样,注解的名称也根据聚合函数的名称和聚合字段的名称得到的。你可以在指定注解时,为默认名称提供一个别名:
>>> 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()。
有任何疑问的话,请检查 SQL query!
要想弄清楚你的查询到底发生了什么,可以考虑检查你QuerySet的 query 属性。
例如,在annotate() 中混入多个聚合将会得出错误的结果,因为多个表上做了交叉连接,导致了多余的行聚合。
连接和聚合¶
至此,我们已经了解了作用于单种模型实例的聚合操作, 但是有时,你也想对所查询对象的关联对象进行聚合。
在聚合函数中指定聚合字段时,Django 允许你使用同样的 双下划线 表示关联关系,然后 Django 在就会处理要读取的关联表,并得到关联对象的聚合。
例如,要查找每个商店提供的图书的价格范围,您可以使用注释:
>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
这段代码告诉 Django 获取书店模型,并连接(通过多对多关系)图书模型,然后对每本书的价格进行聚合,得出最小值和最大值。
同样的规则也用于 aggregate() 子句。如果你想知道所有书店中最便宜的书和最贵的书价格分别是多少:
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
关系链可以按你的要求一直延伸。 例如,想得到所有作者当中最小的年龄是多少,就可以这样写:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
遵循反向关系¶
和 跨关系查找的方法类似,作用在你所查询的模型的关联模型或者字段上的聚合和注解可以遍历"反转"关系。关联模型的小写名称和双下划线也用在这里。
例如,我们可以查询所有出版商,并注上它们一共出了多少本书(注意我们如何用 'book'指定Publisher -> Book 的外键反转关系):
>>> from django.db.models import Count, Min, Sum, Avg
>>> Publisher.objects.annotate(Count('book'))
QuerySet结果中的每一个Publisher都会包含一个额外的属性叫做book__count。
我们也可以按照每个出版商,查询所有图书中最旧的那本:
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
(返回的字典会包含一个键叫做 'oldest_pubdate'。如果没有指定这样的别名,它会更长一些,像 'book__pubdate__min'。)
这不仅仅可以应用挂在外键上面。还可以用到多对多关系上。例如,我们可以查询每个作者,注上它写的所有书(以及合著的书)一共有多少页(注意我们如何使用 'book'来指定Author -> Book的多对多的反转关系):
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
(每个返回的QuerySet中的Author 都有一个额外的属性叫做total_pages。如果没有指定这样的别名,它会更长一些,像 book__pages__sum。)
或者查询所有图书的平均评分,这些图书由我们存档过的作者所写:
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
(返回的字典会包含一个键叫做'average__rating'。如果没有指定这样的别名,它会更长一些,像'book__rating__avg'。)
聚合和其他查询集子句¶
filter() and exclude()¶
聚合也可以在过滤器中使用。 作用于普通模型字段的任何 filter()(或 exclude()) 都会对聚合涉及的对象进行限制。
使用annotate() 子句时,过滤器有限制注解对象的作用。例如,你想得到每本以 "Django" 为书名开头的图书作者的总数:
>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
使用aggregate()子句时,过滤器有限制聚合对象的作用。例如,你可以算出所有以 "Django" 为书名开头的图书平均价格:
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
对注解过滤¶
注解值也可以被过滤。 像使用其他模型字段一样,注解也可以在filter()和exclude() 子句中使用别名。
例如,要得到不止一个作者的图书,可以用:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
这个查询首先生成一个注解结果,然后再生成一个作用于注解上的过滤器。
annotate() 和filter() 子句的顺序¶
编写一个包含 annotate() 和 filter() 子句的复杂查询时,要特别注意作用于 QuerySet的子句的顺序。
当一个annotate() 子句作用于某个查询时,要根据查询的状态才能得出注解值,而状态由 annotate() 位置所决定。以这就导致filter() 和 annotate() 不能交换顺序,下面两个查询就是不同的:
>>> 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 分)的出版商。 但是第一个查询的注解包含其该出版商发行的所有图书的总数;而第二个查询的注解只包含出版过好书的出版商的所发行的图书总数。 在第一个查询中,注解在过滤器之前,所以过滤器对注解没有影响。 在第二个查询中,过滤器在注解之前,所以,在计算注解值时,过滤器就限制了参与运算的对象的范围。
order_by()¶
注解可以用来做为排序项。 在你定义 order_by() 子句时,你提供的聚合可以引用定义的任何别名做为查询中 annotate()子句的一部分。
例如,根据一本图书作者数量的多少对查询集 QuerySet进行排序:
>>> 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() 子句之后,你需要显式地包含聚合列。
与默认排序交换或order_by()¶
在查询集中的order_by() 部分(或是在模型中默认定义的排序项) 会在选择输出数据时被用到,即使这些字段没有在values() 调用中被指定。这些额外的字段可以将相似的数据行分在一起,也可以让相同的数据行相分离。在做计数时,就会表现地格外明显:
通过例子中的方法,假设有一个这样的模型:
from django.db import models class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField() class Meta:
ordering = ["name"]
关键的部分就是在模型默认排序项中设置的name字段。如果你想知道每个非重复的data值出现的次数,可以这样写:
# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))
...这部分代码想通过使用它们公共的 data 值来分组 Item对象,然后在每个分组中得到 id 值的总数。但是上面那样做是行不通的。这是因为默认排序项中的 name也是一个分组项,所以这个查询会根据非重复的 (data, name) 进行分组,而这并不是你本来想要的结果。所以,你应该这样改写:
Item.objects.values("data").annotate(Count("id")).order_by()
...这样就清空了查询中的所有排序项。 你也可以在其中使用 data ,这样并不会有副作用,这是因为查询分组中只有这么一个角色了。
这个行为与查询集文档中提到的 distinct() 一样,而且生成规则也一样:一般情况下,你不想在结果中由额外的字段扮演这个角色,那就清空排序项,或是至少保证它仅能访问 values()中的字段。
注意
你可能想知道为什么 Django 不删除与你无关的列。主要原因就是要保证使用 distinct()和其他方法的一致性。Django 永远不会 删除你所指定的排序限制(我们不能改动那些方法的行为,因为这会违背 API stability 原则)。
聚合注解¶
你也可以在注解的结果上生成聚合。 当你定义一个 aggregate() 子句时,你提供的聚合会引用定义的任何别名做为查询中 annotate() 子句的一部分。
例如,如果你想计算每本书平均有几个作者,你先用作者总数注解图书集,然后再聚合作者总数,引入注解字段:
>>> from django.db.models import Count, Avg
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}
django聚合查询的更多相关文章
- Django聚合查询 orm字段及属性
目录 一 聚合查询 1. 级联 级联删除 级联更新 2. 聚合函数 使用 aggregate 使用场景 3. 分组查询 语法 使用 annotate 代码 4. F与Q查询 F查询 Q查询 二 ORM ...
- Django 聚合查询 分组查询 F与Q查询
一.聚合查询 需要导入模块:from django.db.models import Max, Min, Sum, Count, Avg 关键语法:aggregate(聚合结果别名 = 聚合函数(参数 ...
- Django聚合分组查询、常用字段
首先回顾sql中聚合和分组的概念: 如果没有分组,会把整张表作为一个大组,查询字段必须是聚合结果:如果有分组,分组之后,必须要使用聚合的结果作为having的条件. 聚合查询 聚合:aggregate ...
- django数据查询之聚合查询和分组查询
<1> aggregate(*args,**kwargs): 通过对QuerySet进行计算,返回一个聚合值的字典.aggregate()中每一个参数都指定一个包含在字典中的返回值.即在查 ...
- Django聚合与分组查询中value与annotate的顺序问题
在学习Django聚合与分组查询中,发现value与annotate的顺序不同时,查询结果大相径庭,经过一下午的研究,终于弄明白了,现在分享给大家,先上结论: 结论 value在annotate前面时 ...
- Django 聚合与查询集API实现侧边栏
本文从Django官方文档总结而来,将聚合的主要用法和查询集的常见方法做一归纳. 聚合 1. 聚合的产生来源于django数据库查询,通常我们使用django查询来完成增删查改,但是有时候需要更复杂的 ...
- {django模型层(二)多表操作}一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询、分组查询、F查询和Q查询
Django基础五之django模型层(二)多表操作 本节目录 一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询.分组查询.F查询和Q查询 六 xxx 七 ...
- Django的mode的分组查询和聚合查询和F查询和Q查询
1.聚合查询 # 聚合函数aggregate,求指定字段的最大值,最小值,平均值,和的值,方法如下 from django.db.models import Avg from django.db.mo ...
- django系列5.5--分组查询,聚合查询,F查询,Q查询,脚本中调用django环境
一.聚合查询 aggregate(*args, **args) 先引入需要的包,再使用聚合查询 #计算所有图书的平均价格 from django.db.models import Avg Book.o ...
随机推荐
- ddctf2019--web部分writeup
0x00前言 上周五开始的DDCTF 2019,整个比赛有一周,题目整体来说感觉很不错,可惜我太菜了,做了4+1道题,还是要努力吧 0x01 web 滴~ 打开看着url,就像文件包含 文件名1次he ...
- 使用SpringSecurity
前几天写了一个SpringBoot对拦截器的使用,在实际项目中,对一些情况需要做一些安全验证,比如在没有登录的情况下访问特定的页面应该解释的拦截处理.这一篇介绍使用SpringSecurity来做简单 ...
- SpringBoot的第一个web项目
这一节主要是讲springboot搭建简单的web项目. 首先pom文件新增spring-boot-starter-web依赖,pom文件如下所示 <?xml version="1.0 ...
- Redis自学笔记:1.简介
博主教材:李子骅.人民邮电出版社.<redis入门指南> 博主操作系统系统:虚拟机Ubuntu16.04 博主redis版本:3.0.6 第1章:简介 redis是一个开源的.高性能的.基 ...
- 在Android中调用USB摄像头
在网上找了很长时间,网上的资料基本都是说用外国人写的库,但实际上这个库的案例都是不能直接用的(因为权限问题),并不适合学习. 之后偶然发现有国人把这个库重新封装了,并且有源代码以及中文教程: http ...
- 在addroutes后,$router.options.routes没有更新的问题(手摸手,带你用vue撸后台 读后感)
参照<着手摸手,带你用vue撸后台>一文,本人做了前端的权限判断 https://segmentfault.com/a/1190000009275424 首先就是在addroutes后,$ ...
- 1418 抱歉 ACM 欧拉公式
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1418 思路:一看题目,明显是要求我们找规律,于是我就在草稿纸上画了很多个图像,还是没有找到规律,于是我就在 ...
- 2955 ACM 杭电 抢银行 01背包 乘法
题意: 强盗抢银行,在不被抓住的情况下,想尽量多的偷点钱.已知各个银行的金钱和被抓的概率,以及强盗能容忍的最大不被抓的概率(小于等于该概率才能不被抓),求最多能抢到钱? 并不是简单的01背包问题? 1 ...
- [机器学习入门篇]-Logistic函数与Softmax函数
1.Logistic函数 在维基百科中,对logistic函数这样介绍道: A logistic function or logistic curve is a common "S" ...
- BZOJ4095 : [Usaco2013 Dec]The Bessie Shuffle
首先将排列和整个序列以及询问都反过来,问题变成给定一个位置$x$,问它经过若干轮置换后会到达哪个位置. 每次置换之后窗口都会往右滑动一个,因此其实真实置换是$p[i]-1$. 对于每个询问,求出轮数, ...