Django之模型层——聚合查询、分组查询、F与Q查询 、ORM查询优化
聚合查询 aggregate( *args,**kwargs)
1.基本介绍
介绍:聚合查询通常情况下都是配合分组一起使用的. 如果你只想使用聚合函数, 但是不想分组, 那么就应该使用aggregate.
使用:直接在objects后面链接.
返回:返回字典格式的数据. 如果是对price字段求平均值, 那么返回格式是: {'price__avg': 值}
和数据库相关模块的方法:
基本是都在django.db.models里面
如果上述没有那么应该在django.db里面
2.五种聚合函数
- Avg (Average) : 平均值
- Max (Maximum) : 最大值
- Min (Minimum) : 最小值
- Sum (Summary) : 求和
- Count : 个数
3.aggregate()
aggregate()
是QuerySet
的一个终止子句,意思是说,它返回一个包含一些键值对的字典- 键的名称是聚合值的标识符,值是计算出来的聚合值
- 键的名称是按照字段和聚合函数的名称自动生成出来的
- 如果你想要为聚合值指定一个名称,可以向聚合子句提供它
from django.db.models import Avg,Max,Min,Sum,Count # 导入聚合函数 res = models.Book.objects.aggregate(Avg('price')) print(res) res = models.Book.objects.aggregate(avg_price=Avg('price')) # 指定名称 print(res) res = models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Avg('price')) print(res)
分组查询 annotate
1.介绍
分组注意事项
-
分组只能拿到分组的依据. 按照什么分组就只能拿到什么组. 其他字段不能直接获取, 需要借助一些方法(聚合函数)
-
提示: 可以指定多个分组, 指定多个分组, 当然就可以获取多个分组之间的分组依据.
如果执行orm分组查询报错,并且有关键字sql_mode strict mode
移除sql_mode中的only_full_group_by
步骤:
第一步: 指定分组的依据 第一种情况: 默认分组. annotate直接在objects后面链接时, models后面点什么就按照什么分组. 例子: 按照书分组 models.Book.objects.annotate(sum_price=Sum) 第二种情况: 指定分组. annotate跟在values后面, values中指定什么字段就按照什么分组 例子: 按照书中的价格分组. models.Book.objects.values('price').annotate() 第二步: 为分组的字段取别名 第三步: 在annotate后面使用values可以获取分组的依据和分组之后的结果
返回:返回QuerySet对象
2.分组依据
# 默认分组依据 如果 annotate() 直接跟在 objects 后面,则表示直接以当前的基表为分组依据 例 : 按书来分组 : odels.Book.objects.annotate(sum_price=Sum) # 指定分组依据 如果 annotate() 跟在 values() 后面,则表示按照values中指定的字段来进行分组 例 : 按照书中的price进行分组 : models.Book.objects.values('price').annotate()
- values( ) 在 annotate( ) 之前则表示按values括号内的字段分组
- values( ) 在 annotate( ) 之后则表示取values括号内的字段
- filter( ) 在 annotate( ) 之前则表示 where 条件
- filter( ) 在 annotate( ) 之后则表示 having 条件
3.示例
# 分组查询 # 统计每一本书的作者个数 res = models.Book.objects.annotate(authors_num=Count('authors__pk')).values('title','authors_num') print(res) # 统计出每个出版社卖的最便宜的书的价格 res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price') print(res) # 统计不止一个作者的图书 # 第1步:先统计每本书的作者个数 author_num = models.Book.objects.annotate(authors_num=Count('authors__pk')) # 第2步:筛选出作者个数大于1的图书 res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num') print(res) # 查询每个作者出的书的数量和总价格 res = models.Author.objects.annotate(total_num=Count('book__pk'),total_price=Sum('book__price'),).values('name','total_num','total_price') print(res) """按照字段分组""" # 统计每个出版社所出的书的数量 res = models.Book.objects.values('publish_id').annotate(total_num=Count('pk')).values('publish__name','total_num') print(res)
往已有数据的表格中插入数据,执行数据迁移时无法成功,需要设置参数,不然无法添加。有两种方法:
1. 设置默认值:default = 默认数值
2. 允许为空:null = True
"""后续添加两个字段""" stock = models.IntegerField(verbose_name='库存数',default=1000) # 设置默认值 sold = models.IntegerField(verbose_name='卖出数',null=True) # 允许为空
F查询介绍
- 我们之前的例子中对一些字段的过滤和操作都是与一个常量进行比较大小, 如果我们想让字段与字段的值进行比较就无法简单实现, 于是就可以使用 F 查询了
- 作用 : 取出某个字段对应的值
使用示例
""" # 作用: 能够帮助你直接获取到表中某个字段对应的数据 # 使用: from django.db.models import F # 获取到某个字段对应的数据 F("字段__条件") # 查询字段对应的数据是数字类型可以直接加减运算: F('字段__条件') + 500 # 字符类型需要借助Concat和Value方法 from django.db.models.functions import Concat from django.db.models import Value Concat(F('字段__条件'), Value("str")) 注意: 查询字段对应的数据是字符类型不能直接进行拼接. 否则操作的字段对应的所有数据将变成空白. """ # 查询库存数大于卖出数的书籍 from django.db.models import F res = models.Book.objects.filter(stock__gt=F('sold')) print(res) # 将所有书的价格涨80元 models.Book.objects.update(price=F('price')+80) # 将所有书的名称后面都追加”爆款“两个字 from django.db.models.functions import Concat from django.db.models import Value models.Book.objects.update(title=Concat(F('title'),Value('爆款')))
Q查询
Q查询介绍
- filter 的字段筛选条件如果指定多个, 默认是and连接多个条件, 如果想要使用or或者not,则需要Q查询
- 作用 : 构造
或 or
、与 &
、非 ~
条件
示例
""" # 作用: filter的字段筛选条件指定多个, 默认是and连接. 要实现or或者not需要借助Q查询 # 使用: from django.db.models import Q Q(字段__条件=值) # 连接条件and的3种情况 1. filter中指定多个参数逗号隔开: filter(参数1, 参数2) 2. 查询指定多个逗号隔开: filter(Q(), Q()) 3. 使用&连接符: filter(Q() & Q()) # 连接条件or filter(Q() | Q()) # 连接条件not filter(~Q()) # Q查询的高阶用法: 让左边指定的变量形式的查询条件可以是字符串 q = Q() q.connecter = 'or' # 指定连接符. 不指定默认and q.children.append(Q('字段__条件', 值)) res = models.XXX.objects.filter(q) """ from django.db.models import Q # 1. 查询卖出数大于100 和 价格小于900的书籍 --> 连接条件 and # res = models.Book.objects.filter(sale__gt=100, price__lt=900) # res = models.Book.objects.filter(Q(sale__gt=100), Q(price__lt=900)) res = models.Book.objects.filter(Q(sale__gt=100) & Q(price__lt=900)) print(res) # 2. 查询卖出数大于100或者价格小于600的书籍 --> 连接条件 or res = models.Book.objects.filter(Q(sale__gt=100) | Q(price__lt=600)) print(res) # 3. 查询卖出数不大于100或者价格小于600的书籍 --> 连接条件 not res = models.Book.objects.filter(~Q(sale__gt=100) | Q(price__lt=600)) print(res) # 4. Q的高阶用法: 能够将查询条件的左边变量的形式变成字符串的形式 q = Q() # 第一步: 实例化一个q对象 q.connector = 'or' # 第二步: 定义连接条件 q.children.append(('sale__gt', 100)) # 第三步: 指定字符串形式的查询字段条件, 以及范围100 q.children.append(('price__lt', 600)) res = models.Book.objects.filter(q) # 第四步: 将q对象传入filter print(res)
Q查询进阶操作
能够将查询条件的左边变量的形式变成字符串的形式
from django.db.models import Q q_obj = Q() # 1.产生q对象 q_obj.connector = 'or' # 默认多个条件的连接是and可以修改为or q_obj.children.append(('pk',1)) # 2.添加查询条件 q_obj.children.append(('price__gt',480)) # 支持添加多个 res = models.Book.objects.filter(q_obj) # 查询支持直接填写q对象 print(res)
ORM查询优化(重点)
django中orm自带的两个优化机制
1.ORM的查询默认都是惰性查询
光编写orm语句并不会直接执行SQL语句,只有后续的代码用到了才会执行。
2.ORM自带limit分页:减轻数据库和服务器端的压力
res = models.Book.objects.all() print(res)
不执行print时,没有执行日志。
only与defer
only
res = models.Book.objects.only('title', 'price') for obj in res: print(obj.title) # 点击括号内填写的字段,不走SQL查询 print(obj.price) print(obj.publish_time) # 可以点击括号内没有的字段获取数据,但是会走SQL查询
only会将括号内填写的字段封装成一个个的数据对象,对象在点击的时候不会再走数据库查询,但是对象点括号中没有的字段的时候,每次都会走数据库。
总结:对象点only括号内有的字段不会走数据库,点括号中没有的字段会走数据库。
defer
res = models.Book.objects.defer('title', 'price') for obj in res: print(obj.title) print(obj.price) print(obj.publish_time)
defer与only刚好相反,对象点括号内出现的字段会走数据库查询,如果点击了括号内没有的字段也可以获取到数据,每次都不会走数据库查询。所以defer方法就是only方法的取反。
select_related与prefetch_related
select_related
select_related主要针一对一和多对一关系进行优化。
先进行连表,然后将连接之后的大表中所有的数据全部封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询,产生一次查询,效率高。
res = models.Book.objects.select_related('authors') for obj in res: print(obj.publish.name)
总结:
- 1.select_related括号内只能接收外键字段(一对一,一对多)
- 2.内部直接连接表(inner join)——Book表与外键字段对应的表,然后将连接之后的大表中所有的数据一次性全部封装到数据对象中
- 3.得到数据对象直接点两个表中的字段都可以得到相应的数据
- 4.后续对象通过正反向查询跨表,内部不会再走数据库查询
prefetch_related
对于多对多字段,不能使用select_related方法,这样做是为了避免对多对多字段执行JOIN操作从而造成最后的表非常大。
Django提供了prefect_related方法来解决这个问题。
prefect_related可用于多对多关系字段
res = models.Book.objects.prefetch_related('publish') for obj in res: print(obj.publish.name)
总结:
- 1. 底层其实就是子查询,先分别查询两张表,再将两次查询之后的结果封装到数据对象中
- 2. 后续对象通过正反向查询跨表,内部不会再走数据库查询
select_related与prefetch_related对比:
prefetch_related是查询了每个表,然后在语言层面进行连接,而select_related是通过join方法进行数据库的连接,所以select_related在查询的时候是比prefetch_related效率要高的,但是prefetch_related的使用更加广泛,可以进行多对多的查询。