4.3 Model的查询操作API
2025-02-17
- 4.3 Model的查询操作API
- 应用中创建的每一个Model类,Django都会自动添加一个名称为objects的Manager对象
- 它是Model与数据库实现交互的入口,也被称作查询管理器
4.3.1 创建实例
save方法创建Model实例
使用save方法可以创建并保存一个Model实例到数据库中。例如,如果我们有一个名为Book的Model,我们可以使用以下代码创建并保存一个新的Book实例:
book = Book(title='新书', author='作者')
book.save()这将创建一个新的Book实例,并将其保存到数据库中。
create方法创建Model实例
create方法是另一种创建Model实例的方式。它可以直接创建并保存一个实例,而不需要先创建实例然后再调用save方法。例如:
Book.objects.create(title='新书', author='作者')这将直接创建并保存一个新的Book实例到数据库中。
需要注意的是,create方法返回的是一个已经保存到数据库的实例对象,而不是一个未保存的实例对象。
4.3.2 返回单实例的查询方法
Django在查询的时候支持使用pk代替主键名称
查询实例的接口通常返回三种类型
- 单实例
- QuerySet
- RawQuerySet
查询单实例
- get()
- 会抛出两类异常
- DoesNotExist: 给定的查询条件找不到对应的数据记录
- MultipleObjectsReturned: 给定的查询条件匹配了多条数据记录
- get_or_create(): 实例不存在会创建新的实例对象
- get()
get()查询
Topic.objects.get(title='first topic') # 优化 try: topic = Topic.objects.get(title='first topic') except Topic.DoesNotExist: pass except Topic.MultipleObjectsReturned: pass # pk primary_obj = Topic.objects.get(pk=1)get_or_create()查询
- 此方法返回一个:tuple对象(object,created)。第一个元素是实例对象,第二个元素是布尔值
Topic.objects.get_or_create(id=1,title='first topic') - 如果查询条件能够匹配多条记录,也会抛出
MultipleObjectsReturned异常
- 此方法返回一个:tuple对象(object,created)。第一个元素是实例对象,第二个元素是布尔值
4.3.3 返回QuerySet的查询方法
- 返回多条数据记录时,需要使用QuerySet对象
- 可以简单理解为Model集合,可以包含一个,多个或者零个Model实例
- 返回QuerySet对象的常用接口
- all:获取所有的数据记录
- filter:按照查询条件过滤数据记录;在数据库中实现WHERE的功能

- 对于contains、startswith、endswith关键字,也都有对应的忽略大小写的查询版本,只需要在关键字之前加上字母
i就可以了,例如icontains
- 对于contains、startswith、endswith关键字,也都有对应的忽略大小写的查询版本,只需要在关键字之前加上字母
- exclude:反向过滤:与filter方法实现的功能是很类似的,只是用一个布尔值标记自己是正向过滤(filter)还是反向过滤(exclude)
- exclude方法相当于在filter方法的前面加上一个NOT,即过滤出来的结果是不满足给定条件的数据记录
- reverse:获取逆序数据记录
- order_by:获取自定义排序规则
Topic
小技巧:使用print(Topic.objects.all().query)可以打印QuerySet的query属性查看生成的SQL语句
# all
Topic.objects.all()
topic_qs = Topic.objects.all() # 将语句赋值给一个变量时不会去访问数据库的,(惰性求值)
print(Topic.objects.all().query) # 打印QuerySet的query属性查看生成的SQL语句
# reverse
Topic.objects.reverse()
# order_by
Topic.objects.order_by('-title', 'created_time')
# filter
Comment.objects.filter(up_get=30) # up字段与gte关键字使用的连接符是双下画线,up字段与gte关键字使用的连接符是双下画线
## filter_exect是比较特殊的
# 当查询字段没有指定查询关键字,默认就是exact,下面的两条语句是等效的
Topic.objects.filter(title='title')
Topic.objects.filter(title_exect='title')
# exclude:相当于在filter方法的前面加上一个NOT
Comment.objects.exclude(up_lt=29) # 过滤up不小于29的Comment对象;lt(小于)Topic
filter和exclude方法返回的是一个QuerySet,所以,在它们获得的结果后面还可以继续调用filter exclude等方法,来形成链式查询
# 优化后的链式查询
# 需要content中包含good、down不大于20且up大于20的Comment对象,可以使用链式查询
Comment.objects.filter(content__icontains='good', down__lte=20, up__gt=20)
# 优化后的排序查询
# 下面的查询会按照created_time正序排列
Topic.objects.order_by('created_time')
# 可以通过print() 方法查看对应的SQL语句values()方法获取字典结果
values方法返回的是一个QuerySet,与all, filter的区别在于
- all, filter:通过迭代获取Model的实例对象
- values:返回字典,字典中的键对应Model的字段名
给values方法传递参数,限制SELECT的查询范围,没有指定,会查询Model的所有字段
# 只查询Comment表的id和up字段 Comment.objects.values('id','up')
values_list()方法获取元组结果
获取到的结果是元组,不是字典
会按照传递的字段名限制SELECT的查询范围
当只传递一个参数,可以配合flat参数。
flat=True: 迭代返回结果得到的将是字段值- 默认
False: 结果是只有单个元素的元组
Comment.objects.values_list('id','up') Comment.objects.values_list('id',flat=True)
对QuerySet进行切片
在SQL上表现为LIMIT和OFFSET
Comment.objects.all()[:2]
注意
- QuerySet不支持从末尾切片,即索引值不能为负数
- 一个QuerySet执行切片操作会返回另一个QuerySet,不会触发数据库的查询
- 但使用step切片语法,则会触发数据库查询,并返回Model实例列表
- 如
Comment.objects.all()[1:3:2]- 切片后返回的QuerySet不能在执行过滤或者排序操作
Topic: Manager而言,all filter exculde等常用的方法会返回QuerySet外,一些不常用的方法也会返回QuerySet,如:distinct,only,defer等方法
4.3.4 返回RawQuerySet的查询方法
复杂的查询:ORM的表达能力会存在局限性,需要使用SQL语句实现查询
- Manager提供了:
raw()方法允许使用SQL语句实现对Model的查询 raw()返回的是一个RawQuerySet对象,可以迭代得到Model实例对象- RawQuerySet不能在此基础上执行
filterexclude等方法
简单的SQL查询
for comment in Comment.objects.raw('SELECT * FROM post_comment'):
print('%d:%s' % (comment.id,comment.content))注意:Django不会对传递给raw方法的SQL语句进行检查。Django期望它会从数据库中返回一组行数据,但是并不做强制要求。如果查询的结果不是行数据,则会产生一个错误
不要拼接SQL语句
不要手动填充SQL字符串,因为此方式会存在SQL注入攻击的风险
raw()方法提供了
params参数解决SQL注入的风险例子
# 对于参数化查询的SQL来说,只需要在语句中加上%s或者%(key)s之类的占位符 for topic in Topic.objects.raw('SELECT id FROM post_topic WHERE title = %s', {'first_topic'}): print('%d:%s' % (topic.id,topic.content))raw() 接受的是一个:列表或者字典的参数,将SQL语句中的占位符进行替换,完成查询
上面的代码中获取的是id这个字段,但是仍可以获取title字段
RawQuerySet支持索引和切片
Comment.objects.raw('SELECT ** FROM post_comment')[0]
Comment.objects.raw('SELECT ** FROM post_comment')[1:2]总结
raw SQL的查询方法是比较少用到的,一般还是使用常规的ORM模型进行查询
4.3.5 返回其他类型的查询方法
返回QuerySet的对象数量:count()
- 通过 all filter exclude方法得到QuerySet对象后,可以通过
len()方法对象的数量len(Comment.objects.filter(id_gt=1))- 通过
len()方法的效率比较低效
- QuerySet提供的是
count()方法Comment.objects.filter(id_get=1).count()count()方法会在SQL上执行SELECT COUNT(*)操作并返回数字类型
判断QuerySet是否包含对象:exists()
- 根据当前给定的条件返回是否存在匹配实例对象的布尔结果
- 方法1:判断count方法的返回值是否为0
- 方法2:使用
exists()方法 推荐- 包含返回:True,否则为:False
- 在SQL中表现为
LIMIT 1 Comment.objects.filter(id_get=1).exists()
update()方法更新Model实例
更新实例的方法
方法1:
save()方法: 即先查询出Model对象,之后更新字段值comment = Comment.objects.get(id=1) comment.up=90 comment.save()注意的是,虽然只是给up字段重新设定了值,但是save执行会更新Comment表的所有列
方法2:
update()方法:更新特定的列Comment.objects.filter(id=1).update(up=90,down=33)注意:update方法可以一次性更新多个对象,并返回一个整数,标识受影响的记录条数
delete()方法删除Model实例
# 删除id=1的Comment实例
comment = Comment.objects.get(id=1)
comment.delete()
# 删除id=2的实例
Comment.objects.filter(id=2).delete()delete方法返回一个二元组:第一个元素标识删除的实例个数,第二个元素是字典类型,记录每一个Model类型删除的实例个数
4.3.6 关联关系的查询
Model的反向查询
Django中的每一种关联关系都可以实现反向查询
默认情况下,管理器的名称为“小写模型名_set”,对于Topic而言就是comment_set。之前介绍过可以通过related_name参数覆盖,但是通常不需要这样做
# 通过comment_set实现反向查询 topic = Topic.objects.get(id=1) topic.comment_set.all() # 反向查询 topic.comment_set.filter(content='very good!') topic.comment_set.exclude(content_contains='good')ManyToManyField和OneToOneField关系类型也可以实现类似的反向查询
注意:OneToOneField类型的反向查询比较特殊。它的管理器代表的是一个单一的对象,而不是对象集合,且名称变成了小写的Model名
对于OneToOneField的反向查询
# 例如之前定义的CustomUser,它的user字段与Django的User存在OneToOneField的关系,那么,User可以这样实现反向查询 # 如果没有CustomUser对象与User关联,那么,上面的查询将会抛出RelatedObjectDoesNotExist异常 user = User.objects.get(username='admin') user.customuser
跨关联关系查询
- 跨关联查询:只需要使用双下画线与关联Model的字段名称组合在一起,并给出合适的条件就可以完成查询
Comment.objects.filter(topic_title_contains='first'): 查询所有Topic的title字段包含first的Comment对象Comment.objects.filter(topic_user_username='first')
- 支持反向的关联查询,只需要使用关联Model的小写名称即可
Topic.objects.filter(comment_up_gte=30): 查询所有Comment的up值大于等于30的Topic对象
跨关联关系多值查询
topic = Topic.objects.get(id=1)
Comment.objects.create(content='general', up=60, down=40,topic=topic)
# 第一个查询: 对应的是Comment中的content字段值包含very且up字段值小于等于60的所有的Topic对象
Topic.objects.filter(comment_content_contains='very', comment_up_lte=60)
# 第二个查询
Topic.objects.filter(comment_content_contains='very').filter(comment_up_lte=60)4.3.7 F对象和Q对象查询
4.3.8 聚合查询和分组查询
聚合和分组都是用来生成统计值的过程:聚合是指对QuerySet整体(可以理解为Model对象的集合)生成一个统计值;分组是为QuerySet中每一个对象都生成一个统计值。
1. 聚合查詢
对QuerySet计算统计值,需要使用aggregate方法,提供的参数可以是一个或多个聚合函数。aggregate是QuerySet的一个终止子句,它返回的是字典类型,键是聚合的标识,值是聚合的结果。Django提供了一系列的聚合函数,其中Avg(平均值)、Count(计数)、Max(最大值)、Min(最小值)、Sum(加和)最为常用。要使用这些聚合函数,需要将它们引入当前的环境中
from django.db.models import Avg, Max, Min, Sum, Count
# For Example
Comment.object.filter(topic=1).aggregate(Sum('up'))