关于模型层的介绍我们已经涉及了Model类创建的常用细节和多表关系,当前章节我们着重学习Django中对模型类的API的操作,也就是增伤改查,尤其是查询是我们的重点。
再讲具体操作之前我们需要再次重复,Django提供ORM的形式操作数据库,主要表现在两个方面:
1Django会根据我们定义的Model类自动在数据库中创建表结构。
2Django自动提供给我们一套数据库 API,允许你创建,检索,更新和删除对象。
这些API就是我们本章节的重点。
我们现有学生,老师,班级三种数据。
他们之间关系:
1学生和班级:多对一关系,一个班级多个学生,一个学生属于某一个班级。
2学生和老师:多对多,一个学生有不同课程的老师,一个老师给多个学生授课。
以下他们的Model类代码:
from django.db import models
# 班级
class Classmy(models.Model):
name = models.CharField(max_length=20)
num = models.IntegerField()
# 老师
class Teacher(models.Model):
name = models.CharField(max_length=20)
course = models.CharField("课程", max_length=10)
age = models.IntegerField()
# 学生
class Student(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
#学生和班级多一对关系
classmy =models.ForeignKey(Classmy,on_delete=models.CASCADE)
# 学生和老师是多对多关系
teachers = models.ManyToManyField(Teacher)
生成与执行迁移文件之后我们开始接下来的代码测试。
Django中一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录,
新增记录有两种方式,一是save()函数,二是create()函数。
创建一个对象,然后调用它的save()函数,遍可将其存入数据库。
python manage.py shell
>>>from CURD.models import Classmy,Teacher,Student
>>>c = Classmy(name='三年二班',num=50)
>>>c.save() #创建并保存记录,Django执行了insert语句,save()函数没有返回值
>>>t=Teacher(name='pfdu',course='python',age='18')
>>>t.save() #保存教师记录
>>>s=Student(name='张三',age='15',classmy=c) #创建Student对象,指定classmy
>>>s.save() #保存到数据库
>>>s.teachers.add(t)#绑定教师和学生之间的关系
create创建对象相比save()函数使用起来更便捷,一个步骤到位,最终效果都一样。
# 模型.objects 获取到一个Manager对象
c2 = Classmy.objects.create(name='三年二班',num=50)
我们这里着重解释一下objects,objects是Django帮我们自动生成的管理器对象,通过这个管理器提供了对对数据的操作的API函数,比如create创建新的记录, objects类型是models.Manager。
如果想要一次性插入100条数据到数据库,使用循环在代码上看起来没有什么问题,但是这是一种十分消耗内能的方式因为每次插入数据的动作都会开启一个和数据库的连接,所以禁止使用循环的方式,Django提供了bulk_create()函数解决批量插入问题。
#举例
#tlist是一个集合,每一个item都是一个Teacher对象。bulk_create函数执行一条SQL存入多条数据
>>>Teacher.objects.bulk_create(tlist)
查询是相对比较复杂,同样在开发过程中最常用的,我们从简单的一步步深入。
语法:
all_entries = Entry.objects.all()
简单的例子:
>>>#获取到的一个QuerySet
>>> stus = Student.objects.all()
>>># QuerySet并不方便进行查询数据的细节,我们可以通过values将QuerySet中的数据字典列表的方式进行输出
>>>stus.values()
返回的是一个QuerySet 对象,QuerySet包含数据库中模型对应的表的所有记录,稍后我们会介绍它的特性细节。
values()和 values_list()的区别,这是一道常见的Django面试题,values_list与values是作用是一样的,只是返回数据的格式不同,values返回的是字典列表,values_list返回元祖列表.元祖是由指定的字段查询到的值组成的。
values结构:
>>>
values_list结构:
当然values()和values_list()不仅仅只有这些功能,在后续的聚合函数的使用中我们会在将来的章节继续深入学习。
有时候我们需要对数据库中的记录进行条件筛选获取其中的部分内容。实现方式我们需要用用到QuerySet 对象,QuerySet除了保存记录以外还允许在添加过滤条件对原始QuerySet进行筛选,主要提供了两个函数进行过滤。
filter(**kwargs):获取一个新的 QuerySet,包含的对象满足给定查询参数
exclude(**kwargs):获取一个新的 QuerySet,包含的对象 不 满足给定查询参数。
(**kwargs)就是查询参数。
我们先演示一个简单的案例,代码Django代码如下
>>> ts=Teacher.objects.filter(name='pfdu') #查询名字叫pfdu的Teacher
最终Django会自动执行对应的sql语句,转化的sql语句为:
SELECT * FROM CURD_teacher WHERE name='pfdu';
filter函数和exclude函数在调用后返回的仍然是一个QuerySet对象,所以我们可以在此基础上继续筛选,继续调用下一组filter或者exclude,这种方式我们叫做链式过滤器。
>>>ts2=Teacher.objects.filter(name='pfdu').exclude(age=18)#获取名字叫做pfdu,并且年龄不等于18的老师的集合
filter函数中可传入多个条件参数:
#一个filter函数的多个条件参数之间的关系是and,所以以下语句的含义是查询id<=2并且age<20的老师
>>>ts=Teacher.objects.filter(id__lte='2',age__lte='20')
两个filter()链式调用的效果:
#以下语句的含义是查询id<=2或者age<20的老师,同样也是and的关系
>>>ts=Teacher.objects.filter(id__lte='2') .filter(age__lte='20')
过滤条件指的是比如年龄大于18的,id大于等于10的,名字像du的等等,在sql语句中我们有不同的关系运算符,比如 =,>=,<=,like,in,between等,这些符号在django中不能直接使用,我们需要使用查询关键字来实现不同的关系运算。
举例:
>>> Teacher.objects.filter(age__lte=30)#查询年龄小于等于30的记录
>>>> Teacher.objects.filter(age__in=[30,34,50])#年龄是30,或34,或50岁的人
>>>> Teacher.objects.filter(age__rang=(20,30)) #查询年龄在20到30之间的
过滤条件的语法规则:field__lookuptype=value,field是字段名,中间两个下划线,后面跟不同的条件关键字。
常用条件关键字一览:
关键字 | 解释 |
---|---|
__exact | = |
__iexact | 忽略大小写= |
__contains | 包含 like ‘%aaa%’ |
__icontains | 忽略大小写 ilike ‘%aaa%’ |
__gt | 大于 |
__gte | 大于等于 |
__lt | 小于 |
__lte | 小于等于 |
__in | 存在于一个list范围内 |
__startswith | 以…开头 |
__endswith | 以…结尾 |
__range | 在范围之内 |
我们需要对QuerySet有根深一步的讲解,目前已知是它表示的是一个查询结果集合,并且可以链式的继续筛选。除此之外我们还需要知道它的其他特性:
1在链式过滤器中,每一次筛选返回的是独立唯一的QuerySet对象。
2 QuerySet是惰性的,只有当真正使用时,如遍历的时候,才会真正去数据库进行查询,也就说调用all(),filter(),exclude()函数的时候并不会执行sql查询,只有在使用集合中的数据的时候才去操作数据库。
3QuerySet带有缓存,多次调用同一个查询结果对象,不再进行数据库查询,而是使用缓存结果
描述一个场景:在带有表关系的情况下,比如要查询所有的Student数据,使用刚才学习的方式很简单就是能实现:
ts=Teacher.objects.all() #查询所有的教师
这里请思考一个问题,:在Django中我们查询所有的教师信息的同时是否将所属的学生信息一并进行了获取?
答案是没有的,因为我们从Django的特性中知道QuerySet的设计是惰性的,不仅仅针对单个对象,在表关系中惰性也是存在的,也就是说默认情况下,只有我们主动的使用教师的学生信息的时候Django才会再次去查询学生表。
但如果我们的需求就是同时需要获取到教师和学生的信息呢?惰性查询无疑会增加查询的次数,此时惰性查询不再是一种合适的方式了,为了解决表关联查询时减少与数据库的交互次数,Django提供了select_related和prefetch_related。
简单解释他们的功能:对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。
#一次查询。将所有学生和学生所属的教师都查询出来,调用学生获取教师信息不会再查询数据库
>>>stus= Student.objects.select_related().all()
prefetch_related效果和select_related类似,不过使用的场景不同:
1,select_related适用于外键和多对一的关系查询;
2,prefetch_related适用于一对多或者多对多的查询。
all(),filter(),exclude得到的结果总是一个QuerySet集合,即使开发者已知符合条件的记录只有一条。此时我们可以使用Manager的get()函数,它返回的直接是一个对象。
使用案例:
>>>#获取主键值等于1的对象
>>>t2 =Teacher.objects.get(pk=1)
等价于
>>>#获取主键值等于1的对象
>>>t2 =Teacher.objects.filter(pk=1)[0]
get(**kwargs)函数的参数与filter一样,可以过滤条件。需要注意的是使用get()函数如果没有符合条件的记录或者符合条件的记录超过1条会抛出异常,使用时注意异常的处理。
mysql中使用limit和offset关键字可以实现数据的分页效果,这在数据库庞大的时下经常会用到。limit表示结果集最大条目数,offset是游标偏移量。
Django中对分页也进行了封装,可以对QuerySet的条目数进行限制,采用python切片的语法。
例如,这将返回前 5 个对象 (LIMIT 5):
>>> Teacher.objects.all()[:5]
这会返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5):
>>> Teacher.objects.all()[5:10]
不支持负索引 (例如 Teacher.objects.all()[-1])。
排序也是查询中常用的子句,Django提供了:**QuerySet.order_by(*args)**来实现,源关于排序这不是我们第一次提到,在之前的Django模型层详解之定义Model章节中我们在Meta定义时有介绍到,ordering选项提供默认的排序,开发中需要根据不同需求按照不同的方式排序,所以当前介绍的排序其实是对Meta中ordering排序的覆盖。
使用方式:
>>>#参数可指定一个或者多个,“-”表示降序
>>>#查询所有学生,先按照年龄降序排序,再按照id升序排序
>>>Student.objects.all().order_by('-age', 'id')
跨关系查询指的是mysql中的联合查询,在mysql找那个我们可以使用内联(inner join),左外联(left outer join),右外联(right outer join),全联(union)等,相信这些是很多初学者的噩梦,在Django中提供了ORM方式的更加方便的表关联查询方式。
Django中要跨越关系,只需使用跨模型的相关字段的字段名称,用双下划线分隔,直到到达所需的字段。跨域的深度随你所想
举例1:查询三年二班的学生
>>>stus=Student.objects.filter(classmy__name='三年二班')#classmy是Student类中的classmy属性,双下划线"__"表示到达classmy属性对应的模型类,name是classmy的属性。
>>>stus
当调用stus的时候回看到打印出来的sql语句,我们发现django自动为我们执行的就是inner join语句。
举例2:查询pfdu所在的班级
>>> clas=Classmy.objects.filter(student__name='张三')#这是一个反向查询,因为表关系我们是在Student模型中指定的,django支持反向关联查询
Django中对于单个对象的update操作对应的API函数也是save()函数,也就是说save()函数既可以新增记录,也可以修改已经存在的记录。
>>># c对象是我们刚才已经添加到数据库的对象,我们在它的基础上进行修改,为他的name属性重新赋值
>>>c.name='二年五班'
>>>c.save() # 再次调用c对象的save()函数,此处数据库执行的是update函数
>>>c.name #取出来name的值进行查看
'二年五班'
代码上很简单,但我们需要知道Django是怎么区分save()函数到底是新增操作还是修改操作的?
官方介绍它的算法是这样的:
这里使用QuerySet.update()函数。
#将所有叫张三的人的年龄修改成30
Student.objects.filter(name='张三').update(age=30)
在Django中删除记录主要依靠delete()函数。
使用方式很简单,分为单个对象删除和批量删除。
#获取主键值为1的学生
s = Student.objects.get(pk=1)
#调用删除函数进行删除
s.delete()
#删除所有的学生
Student.objects.all().delete()
#删除所有名字叫张三的学生
Student.objects.filter(name='张三').delete()
Q()的作用是用于复杂查询,使用符号&(and)、|(or)、~(not)将多个Q()对象组合起来传递给filter(),exclude(),get()等函数,来实现逻辑与,或,非的操作。
案例:
#导入Q模块
from django.db.models import Q
#每一个Q函数包裹一个条件,有前缀'~'的表示非,使用&,|将他们链接,然后作为一个整体传递给filter
stu=Student.objects.filter(Q(name='张三') | ~Q(age=10))
提问:怎么实现将id为1的学生的年龄在原来年龄的基础上加一?
原生sql语句:
update curd_student set age=age+1 where id =1
当然以上的sql语句并不是我们讨论的重点,我们要做的是在Django的ORM方式中实现。
使用Django很容易想到的一种方式:
>>>stu =Student.objects.get(pk=1) #先得到id=1的学生对象
>>>stu.age+=1 #使用python代码在内存中进行计算赋值
>>>stu.save() #执行数据库修改
这种方式是并不是采用数据库的sql方式进行原数据基础上修改(上面所说的原生sql语句),计算发生在python执行的内存中,虽然也能实现效果,但是我们并不推荐这种方式。
如果我们修改一下提问的内容:将所有的学生的年龄在原来年龄的基础上加一。此时在使用python计算的方式就暴露出是十分笨拙,性能低下的缺点。F()函数的作用就体现出来了,Django使用F()对象生成一个描述数据库级别所需操作的SQL表达式。它能做的是参考某个字段创建SQL语法来描述操作
我们接下里使用以下F()来实现刚才的两个提问:
1实现将id为1的学生的年龄在原来年龄的基础上加一
>>>stu =Student.objects.get(pk=1) #先得到id=1的学生对象
>>>stu.age=F('age')+1 #覆盖Python运算符来创建一个封装的SQL表达式
>>>stu.save() #执行数据库修改
2修改所有学生的年龄在原来的基础上加一:
>>>from django.db.models import F
>>>stus=Student.objects.all() #获取所有的学生对象
>>>stus.update(age=F('age')+1) #执行更新,使用sql的方式
>>># UPDATE `CURD_student` SET `age` = (`CURD_student`.`age` + 1);
使用F()避免竞争关系
使用F()的另一个好处是使用数据库来更新字段而不是用Python,以此避免竞争关系。
如果有两个Python线程执行上面的示例,A线程可能在B线程从数据库检索数据后来检索、增加并保存某个字段值。那么B线程的保存将基于他最初检索到的原始值;而A线程的工作将会丢失。如果有数据库自身负责更新字段,这个过程将会更加健壮:在执行sasve()和update()时基于数据库中当前的字段值,而不是基于被检索出的实例的值。