文章目录
查询方法
查找是数据库操作中一个非常重要的技术。查询一般就是使用filter、exclude以及get三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都是使用字段__查询条件
的方式来使用的。
filter可以查询多个数据。get只能查询一条数据,如果查询结果为多条会报错。exclude下方在详细介绍。
查看执行的SQL语句
在介绍查询方法前,我先来介绍两种可以查看ORM模型底层将我们的Python代码转换成的SQL语句是什么样的。
- 如果我们查询返回为QuerySet对象,我们可以直接调用此对象的
.queries
属性,此属性会返回当前对象执行的SQL语句。
obj = Obj.objects.all()
print(obj.query)
- 如果我们执行的对象并非为QuerySet对象(比如.get()方法返回值就并不是QuerySet对象),我们需要先引入
from django.db import connection
,然后在调用connection.queries
可以查看当时所有执行的SQL语句。
第二种查询SQL执行语句的方式的前提条件是当前命令Django已经转换为SQL语句并且执行才有效。我们生成的QuerySet对象并不一定马上就会转换为SQL语句去执行,只有QuerySet对象会被使用的时候才会去调用,比如说进行迭代,切片,判断,或者调用函数的时候才会正常触发QuerySet对象生成SQL语句。
# 当前print(connection.queries)输出为空,(`.all()`为查询所有数据)
from django.db import connection
obj = Obj.objects.all()
print(connection.queries)
# 当前print(connection.queries)输出为执行了的SQL语句,(`.all()`为查询所有数据)
from django.db import connection
for obj in Obj.objects.all():
print(obj)
print(connection.queries)
简单来说上述两种方法的区别是:
第一种方法查询的为当前QuerySet对象对应的SQL语句,不管QuerySet对象是否执行,都会有对应的SQL语句可以查询。
第二种方法多用于查询出来的对象并不是QuerySet对象,不是QuerySet对象就没有.query属性,我们就需要查询当前以及执行的所以SQL语句从中查找我们想要看到的查询命令所对应的SQL语句。
查询条件
上述也提到过查询方式为字段__查询条件
,下列就来演示一下常用的查询条件
精准与模糊查询
exact
与iexact
: 精准查询,其中exact相当于等号=
,写不写效果一样,而iexact为使用like进行查找。
# 下列两种方法的SQL语句完全相同
角色信息 = Query.objects.filter(角色='萝莉')
角色信息 = Query.objects.filter(角色__exact='萝莉')
# iexact和exact的区别就是like和=的区别,查询结果在大部分情况下都是相同的。
角色信息 = Query.objects.filter(角色__iexact='萝莉')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE 萝莉
contains
与icontains
: 模糊查询,其中contains
为大小写敏感的模糊查询,而icontains
为大小写不敏感的模糊查询
角色信息 = Query.objects.filter(角色__contains='萝')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE BINARY %萝%
角色信息 = Query.objects.filter(角色__icontains='萝')
# SELECT * FROM `query_query` WHERE `query_query`.`角色` LIKE %萝%
in
: 提取那些给定的字段的值是否在给定的容器中。容器可以为list、tuple或者任何一个可以迭代的对象,包括QuerySet对象。
角色们 = Query.objects.filter(技能_id__in=[3, 4])
技能 = Skill.objects.filter(技能号__in=角色们)
for i in 技能:
print(i.技能名)
print(技能.query)
比较条件
比较运算符 | 作用 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
伤害 = Skill.objects.filter(攻击力__gte="30")
print(伤害.query)
print(伤害)
# SELECT * FROM `技能外键` WHERE `技能外键`.`攻击力` >= 30 ORDER BY `技能外键`.`技能号` DESC
# <QuerySet [<Skill: Skill object (11)>, <Skill: Skill object (3)>]>
比较条件除了大于小于外,还会出现一些范围区间的需求,这时候我们就可以使用range
进行范围查找,range条件需要一个列表或者元组传入一个范围区间进行筛选。
防御 = Skill.objects.filter(防御力__range=['0', '10'])
print(防御.query)
print(防御)
start = date(year=2020, month=3, day=25)
end = date(year=2020, month=3, day=26)
时间 = Query.objects.filter(创建时间__range=(start, end))
print(时间.query)
print(时间)
时间条件
时间条件有date
日期,year
年份,time
时间,day
天等,精准查询时间使用较少,大部分为使用range
进行范围查询,因为精准查询的时候需要写微秒很难达成
其中date可以处理存日期和日期时间两张形式。
年份 = Query.objects.filter(创建时间__year=2020)
# SELECT * WHERE `query_query`.`创建时间` BETWEEN 2019-12-31 16:00:00 AND 2020-12-31 15:59:59.999999
聚合函数
聚合函数同样也是SQL携带的一种快速查询的方式,因为聚合函数在数据库层面进行数据筛选,效率会比传入Django后在做筛选高不少。
聚合函数主要通过aggregate
和annotate
方法进行查找。
- aggregate:返回使用聚合函数后的字段和值。(如果需要整体字段的聚合函数则使用此方法)
- annotate:在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。(如果需要局部字段聚合,则使用此方法)
下列我们用聚合函数来区分aggregate
与annotate
的不同
这是我用到的数据库,聚合函数需要从django.db.models
中导入,如下
from django.db.models import Avg, Count, Max, Min, Sum
查询法语为:聚合函数(查询条件)
Avg:求平均值。
当前我们来求所有角色的等级平均值,使用aggregate即可,默认情况下会返回一个字典,其中键值默认为查询条件__聚合函数
,如何我们想要指定键名,可以写成键名=聚合函数(查询条件)
比如我们想要查看平均等级可以写为
lever_avg = Member.objects.aggregate(Avg('等级'))
SQL:
SELECT AVG( `query_member`.`等级` ) AS `等级__avg` FROM `query_member`
聚合函数还一个特性是可以从其外键中添加条件,比如下面我们从Query表中求平均等级
lever_avgs = Query.objects.aggregate(平均值=Avg('member__等级'))
当如果我们想要求每个角色的等级平均值,这时候就需要用到的annotate
lever_avgs = Query.objects.annotate(平均值=Avg('member__等级'))
想必看到此,你对aggregate
与annotate
还是有比较大的疑惑,我们下面来分析一下这个方法产生的SQL不同之处
其中aggregate的SQL语句为连接表后直接求其平均值,求出的平均值为总平均值
而annotate方法的SQL语句会在其添加分组,利用分组,我们可以求出单独每个角色的平均值
Count:获取指定的对象的个数。
求指定个数的聚合函数和将数据查询出来后使用len()方法求长度获取的值相同,不同于len()方法的是,count可以在查询个数时候传入distinct
可以去掉重复的数据。
query_len = len(Query.objects.all())
query_cou = Query.objects.aggregate(角色数量=Count('角色'))
query_cou_dis = Query.objects.aggregate(角色数量=Count('角色', distinct=True))
print(query_len, query_cou, query_cou_dis)
其中query_len与query_cou查询出的数量都为4,而query_cou_dis则为3
Count查询个数时,指定表头中的数据如果为空值,算个数,如果为Null,这不算一个数,为了防止出现Null,大多数时候我们都会把其指定为主键进行个数计算。
Max和Min:获取指定对象的最大值和最小值。
如果我们想要一次查询多个聚合函数,可以直接在方法内以逗号将多个方法隔开即可
max_min = Query.objects.aggregate(最大值=Max('member__等级'), 最小值=Min('member__等级'))
print(max_min)
Sum:求指定对象的总和。
统计时我们还能指定统计一个范围内的数据,比如我们想统计萝莉的等级总和
add = Query.objects.aggregate(等级总和=Sum('member__等级'))
print(add)
# 统计萝莉的等级总和
add = Query.objects.filter(角色='萝莉').aggregate(萝莉等级=Sum('member__等级'))
print(add)
从输出的SQL语句可以看出,ORM模型会将我们的多个方法自动转换为一句SQL语句执行,增强效率
F、Q表达式
F与Q表达式需要导入的库和聚合函数相似为from django.db.models import F, Q
F表达式
F表达式是用来优化ORM操作数据库的,他可以动态获取数据库中的内容,简化数据库操作
比如我们想要给数据库中所有玩家的等级都提高,虽然不用F表达式也能做到。
member_lever = Member.objects.all()
for lever in member_lever:
lever.等级 += 1
lever.save()
但我们发现不仅我们写的时候比较麻烦,在转换成的SQL也是很长一段,这时候F表达式就起到了作用,这时候我们需要配合,这时候我们需要用到一个.update()
这个更新方法
member_lever = Member.objects.update(等级=F("等级")+10)
# UPDATE `query_member` SET `等级` = (`query_member`.`等级` + 10)
只需要一句SQL就可以执行完成,F表达式可以动态从当前数据库中取出指定的值
Q表达式
默认我们可以在一个方法中传入多个查询条件,每个查询条件以逗号,
隔开,这种查询时候默认使用的为逻辑与&
,我们如果想要使用逻辑或|
,或者非~
,这时候就需要使用到Q表达式。
# 获得等级小于等于110级的萝莉
Young_min = Member.objects.filter(角色__角色='萝莉', 等级__lte=110)
# Young_min = Member.objects.filter(Q(角色__角色='萝莉') & Q(等级__lte=110))
# 获取角色为萝莉或者等级大于一百的成员
hegemony = Member.objects.filter(Q(角色__角色='萝莉') | Q(等级__gte=100))
# 获取所以角色不为萝莉的成员
old_man = Member.objects.filter(~Q(角色__角色='萝莉'))
QuerySet API
我们通常做查询操作的时候,都是通过模型名字.objects的方式进行操作。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个“空壳”的类,他本身是没有任何的属性和方法的。他的方法全部都是通过Python动态添加的方式,从QuerySet类中拷贝过来的
我们上述介绍了一些QuerySet类只是其中一小部分的方法,下面我们会介绍一些常用的方法
必用QuerySet
常用QuerySet | 作用 |
---|---|
.get() | 查询单条数据, 返回查询数据 |
.filter() | 查询所有满足条件的数据, 返回一个新的QuerySet |
.exclude() | 排除满足条件的数据, 查询不满足条件的所有数据,返回一个新的QuerySet |
.all() | 查询此ORM模型中所有数据 |
.aggregate() | 返回使用聚合函数后的字段和值。 |
.annotate() | 在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。 |
.first() | 返回满足条件的首条数据 |
.last() | 返回满足条件的最后一条数据 |
.update() | 执行更新操作,在SQL底层走的也是update命令。 |
上述QuerySet在之前查询数据时大部分都使用过,下面使用一些我们没有使用过的进行展示
# 下述两种写法生成的SQL语句完全相同
old_man = Member.objects.filter(~Q(角色__角色='萝莉'))
old_man = Member.objects.exclude(角色__角色='萝莉')
# 使用update进行常规更新
up = Query.objects.filter(角色='欧洲人').update(角色='欧皇')
常用QuerySet
排序规则(order_by)
排序有多重方法,之前我也介绍过在ORM模型中models.py中如何设定取出数据的排序,不过除此之外,我们使用order_by可以更加灵活的排序,只需在查询出的条件中使用.order_by(表头)
即可,默认为正序(从小到大)如果需要倒叙,表头前加入负号即可。
old_man = Member.objects.exclude(角色__角色='萝莉').order_by('-等级')
取出内容(values, values_list)
之前我们都是先将所以内容取出后在获取需要的内容,这样无疑会造成性能浪费,这时候我们就可以使用.values(表头,表头)
即可指定取出内容,其中values返回值为字典而values_list返回值为列表
old_man = Member.objects.exclude(角色__角色='萝莉').values('昵称', '等级')
old_man_list = Member.objects.exclude(角色__角色='萝莉').values_list('昵称', '等级')
print(old_man)
print(old_man.query)
print(old_man_list)
print(old_man_list.query)
从上述代码中可以看出values与values_list在SQL语句上完全相同,只是在Django中进行了不同的处理
关联查询(select_related, prefetch_related)
关联查询主要用于优化查询,用更少的查询次数来完成查询,
select_related
:在提取某个模型的数据的同时,也提前将相关联的数据提取出来。主要针对一对一和一对多的问题。
prefetch_related
: 访问多个表中的数据的时候,减少查询的次数。主要针对多对一和多对多的问题。
select_related常用于如果一个模型有外键的情况下用其指定外键所代表的的表头时,会将其外键连接查询
Young_min = Member.objects.get(pk=1)
print(Young_min.角色)
Young_min = Member.objects.select_related('角色').get(pk=1)
print(Young_min.角色)
select_related将两个表连接后在进行查询,可以减少数据库的查询次数。
prefetch_related常用于被外键指定的表中的数据,可以查询被那些外键所指定,如果我们使用常规的方式写
querys = Query.objects.all()
for query in querys:
print('\n角色:', query.角色)
members = query.member_set.all()
for member in members:
print('昵称:', member.昵称)
其SQL语句会比较复杂繁琐
有多少数据,就要查询多少次
但如果我们把all改成prefetch_related如下
querys = Query.objects.prefetch_related('member_set')
for query in querys:
print('\n角色:', query.角色)
members = query.member_set.all()
for member in members:
print('昵称:', member.昵称)
这时候再来查看我们使用的SQL会发现只调用了两条
创建数据(create, get_or_create)
我在之前的博客中提到过如何保存数据,此处保存数据和之前博客提到的保存数据略有不同,之前保存的数据需要使用.save()
方法来确定保存,但是使用QuerySet中的create保存数据并不需要在调用.save()
方法
如果使用get_or_create,则会只保存数据库中不存在的信息,此方法的返回值为一个元组:(创建\查询QuerySet对象, 是否创建)
retain = Member.objects.create(昵称='大佬', 等级='50', 角色_id=1)
get_retain = Member.objects.get_or_create(昵称='萌新', 等级='107', 角色_id=2)
上述代码执行两遍后数据库为
切片操作 [::]
此切片用法和Python中一致,但其实在数据库层面的切片(实际为数据库中的分组,但步长会在Python中进行),会有更高的效率
比如说下列的
Member.objects.filter(等级__lte=100).values('昵称', '等级')[1:3]
在数据库中执行为
SELECT `query_member`.`昵称`, `query_member`.`等级` FROM `query_member` WHERE `query_member`.`等级` <= 100 LIMIT 2 OFFSET 1
QuerySet
QuerySet | 作用 |
---|---|
.exists() | 判断某个条件的数据是否存在。此方法的效率非常高 |
.db | 返回现在执行此查询时使用的数据库。 |
.reverse() | 反转QuerySet的顺序。 |
.extra() | 手动添加SQL语句 |
.bulk_update() | 更新数据库中每个给定对象中的给定字段。 |
更多的QuerySet
可以自行查询,大部分会使用到的QuerySet方法上述都已介绍。使用率极地的QuerySet如果需要可以自行查看