聚合函数(Max,Min,Sum,Avg,Count)
Max最大值、Min最小值、Sum求和、Avg求平均值、Count统计个数。
聚合查询通常在分组之后使用,如果想单独使用需要用aggregate将聚合函数括起来,使用之前需要先导入函数
from django.db.models import Mix,Min,Sum,Avg,Count
# 1.书籍表中书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
print(res)
# {'price__avg': 377.91}
# 2.聚合函数组合使用
models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
print(res)
"""
{'price__max': Decimal('666.11'), 'price__min': Decimal('123.11'),
'price__sum': Decimal('1889.55'), 'pk__count': 5, 'price__avg': 377.91}"""
只要是跟数据库相关的模块基本都在django.db.models
里面,这里面没有就在django.db
里面
分组查询
分组查询的关键字是annotate
1.统计每一本书的作者个数
from django.db.models import Mix, Min, Sum, Avg, Count
# 方式1
res = models.Book.objects.annocate(author_num=Count('authors__pk')).values('title','authors_num')
# 方式2 简写
res1 = models.Book.objects.annocate(author_num=Count('authors')).values('title','authors_num')
print(res1)
models后面点什么表就是按照什么进行分组,也就是按书进行分组;author_num是我们自定义的字段,用来存储聚合函数Count统计出来的结果,就是每本书对应的作者个数。是通过作者数据的主键值进行统计的,orm还支持我们简写,直接写字段自动对主键进行统计。Count括号内用到了正向跨表查询,直接点外键字段由Book表跨到Author表,然后__pk取主键值。
2.统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price = Min('book__price')).values('book__title','min_price')
print(res)
首先按出版社进行分组,反向查询点表名小写跨到Book表内,Min函数从中查询出每个出版社出版的最便宜书的价格,并自定义一个min_price来存储查询的结果,最后values内取出想要的字段的值即可。
3.统计不止一个作者的图书,已经图书的作者个数
res = models.Book.objects.annotate(author_num = Count('author')).filter(author_num__gt=1).values('title','author_num')
print(res)
可分为两步:
- 先按照图书进行分组,求每本书对应的作者个数。
- 进一步过滤出不止一个作者的图书。
# 第一步
models.Book.objects.annotate(author_num=Count('author'))
# 第二步
models.Book.objects.annotate(author_num = Count('author')).filter(author_num__gt=1)
# 取值
res = models.Book.objects.annotate(author_num = Count('author')).filter(author_num__gt=1).values('title','author_num')
print(res)
4.查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res)
首先按作者进行分组,反向查询点表名小写跨到Book表内,Sum函数计算出每个作者出过的书的总价格,并自定义一个sum_price来存储结果,最后values内取出想要的字段的值即可。
如果我想按照指定的字段分组该如何处理呢?
annotate前加values(),在values括号内指定分组的依据
models.Book.objects.values.('price').annotate() # 按照书籍表的price字段进行分组。
F与Q查询
F查询
首先在Book表中增加两条字段库存(reserve)和卖出(sales)
reserve = models.IntegerField(default= 1000)
sales = models.IntegerField(default= 1000)
手动的去数据库中将两个字段的值修改一下,改成满足下面题目的数据
sales | reserve | title |
---|---|---|
2000 | 500 | 三国演义 |
500 | 1000 | 红楼梦 |
10 | 990 | 论语 |
1000 | 1000 | 聊斋 |
800 | 1200 | 庄子 |
能够帮你直接获取到表中的某个字段对应的数据
1.查询卖出数大于库存数的书籍
题目中的筛选条件不在数具体的数据了,而是一个字段,需要我们将卖出数和库存数进行比较,这个时候就需要用到F查询了。
F查询能够帮助你直接获取到表中某个字段对应的数据,需要提前导入。
from django.db.models import F
res = models.Book.objects.filter(sales__gt=F('reserve')).values('title')
print(res)
# <QuerySet [{'title': '三国演义'}]>
2.将所有书籍的价格提升500块
models.Book.objects.update(price=F('price')+500)
3.将所有书的名称后面加上畅销两个字
在操作字符类型的数据的时候,F不能直接做到字符串的拼接,还需要导入两个方法才能完成拼接字符。
from django.db.models.functions import Concat
from django.db.models import values
models.Book.objects.update(title=Concat(F('title'), Value('畅销')))
"""
三国演义畅销
红楼梦畅销
论语畅销
聊斋畅销
庄子畅销
"""
models.Book.objects.update(title=F('title') + 畅销')
# 不借助两个方法直接拼接会导致所有的名称变成零
"""
title
0
0
0
0
0
"""
Q查询
1.查询卖出数大于100或者价格小于600的书籍
from django.db.models import Q
res = models.Book.objects.filter(maichu__gt=100,price__lt=600)
print(res) # <QuerySet []> filter括号内逗号隔开的各个条件是and关系
res = models.Book.objects.filter(Q(sales__gt=100),Q(price__lt=600))
# print(res.query) # 查看sql语句条件是and与关系
print(res) # <QuerySet []>
res = models.Book.objects.filter(Q(sales__gt=100)|Q(price__lt=600)) # 将逗号改成|,可以将条件改成or或关系
# print(res.query)
print(res)
"""
<QuerySet [{'title': '三国演义'}, {'title': '红楼梦'},
{'title': '聊斋'}, {'title': '庄子'}]>"""
# 补充 ~ 非
res = models.Book.objects.filter(~Q(sales__gt=100)|Q(price__lt=600)).values('title')
# print(res.query)
print(res) # <QuerySet [{'title': '论语'}]>
Q查询高阶用法
够将查询条件的左边可以是字符串的形式
q = Q()
q.connector ='or' # 将多个条件的连接关系改成or或关系
q.children.append(('sales__gt',100))
q.children.append(('price__lt',600))
res = models.Book.objects.filter(q).values('title')
print(res)
"""
<QuerySet [{'title': '三国演义'}, {'title': '红楼梦'},
{'title': '聊斋'}, {'title': '庄子'}]>"""
django中如何开启事务
事务的四个特性ACID:
- A原子性:不可分割的最小单位
- C一致性:跟原子性是相辅相成,要么同时成功,要么同时失败
- I隔离性:事务之间互相不干扰
- 持久性:事务一旦确认永久生效
事务回滚:rollback
事务确认:commit
from django.db import transaciton
with transaction.atomic():
# sql1
# sql2
...
# 在with代码快内书写的所有orm操作都是属于同一个事务
#处理报错增加异常捕获
from django.db import transaction
try:
with transaction.atomic():
...
except Exception as e:
print(e)
print('执行其他操作')
orm常用字段及参数
AutoField (主键字段)
-primary_key=True
CharField (varchar)
-verbose_name 字段的注释
-max_length 长度
IntegerField (int)
BigIntegerField(bigint)
DecimalField
-max_digits=8 总位数
-decimal_places=2 小数部分位数
EmailFiled (varchar(254))
DateField (date)
DateTimeField (datetime)
-auto_now:每次修改数据的时候都会自动更新当前时间
-auto_now_add:只在创建数据的时候记录创建时间后续不会自动修改了
BooleanField(Field)
-该字段传布尔值(False/True) 数据库里面存0/1
TextField(Field) - 文本类型
-该字段可以用来存大段内容(文章、博客...) 没有字数限制
FileField(Field) - 字符类型
-upload_to = "/data"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保 存到数据库中/data/a.txt
外键字段及参数补充
ForeignKey(unique=True) === OneToOneField() #两者等价
# 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者但是前者也能用
db_index
-如果db_index=True 则代表着为此字段设置索引
"""
django2.X及以上版本 需要你自己指定外键字段的级联更新级联删除:
"""
on_delete = True
orm自定义字段
# 定义
from django.db.models import Field
class MyCharField(Field):
def __init__(self, max_length, *args, **kwargs):
# 调用父类的init方法
super().__init__(max_length=max_length, *args, **kwargs) # 一定要是关键字的形式传入
self.max_length = max_length
def db_type(self, connection):
"""
返回真正的数据类型及各种约束条件
:param connection:
:return:
"""
return f'char({self.max_length})'
# 使用
myfield = MyCharField(max_length=16,null=True)
数据库查询优化
orm语句的特点
惰性查询:如果只是单单书写orm语句,而在后面的代码中根本没有使用到该语句的结果,那么orm会自动识别,将不会执行没有使用的语句。
res = models.Book.objects.all() # 没有使用,不走数据库
only与defer
only:查询的对象中只获取only()括号内的字段,对象的其他字段不拿
代码体现:
res = models.Book.objects.only('title') # 查询的对象中只有title字段数据,没有其他字段数据
for i in res:
print(i.title) # 配置sql日志后可以看到,循环打印title不再走数据库
print(i.price) # 打印其他字段时,查询到的对象中没有,循环打印还需去数据库中相继查询字段的数据
defe:查询的对象除了defer()括号内的字段,其他字段全部获取
代码体现:
res = models.Book.objects.defer('title') # 查询的对象中只有title字段数据没有,而其他字段数据都有
for i in res:
print(i.title) # 查询到的对象中没有title字段,循环打印还需去数据库中相继查询字段的数据
print(i.price) # 打印其他字段时,查询到的对象中有字段数据无需走数据库
select_related与prefetch_related
select_related(联表):类似于数据库sql语句的联表操作,先将两张表以内连接inner join的方式连接成一张虚拟的大表,然后一次性将虚拟表里面的所有数据全部封装给查询出来的对象,这样无论是取左表的数据还是取右边的数据都不需要去数据库中再次的查询了。
代码体现:
res = models.Book.objects.all()
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询
res = models.Book.objects.select_related('publish')
for i in res:
print(i.publish.name) # 每循环时无需去数据库中查询,封装给 对象的虚拟表中有Book表和Publish公有的数据。
prefetch_related(子查询):该方法内部其实就是子查询,只不过它将子查询的所有结果也封装到对象里面去了,表面上时一次就查询完了,实际内部也是分步进行了子查询。
res = models.Book.objects.prefetch_related('publish')
for i in res:
print(i.publish.name)