django模型层(orm相关知识点)
一、模型层之前期准备
模型层的了解
模型(Model)负责业务是对象和数据库的关系映射(ORM),即对象关系映射。
ORM是“对象-关系-映射”的简称,主要任务是:
- 建立模型类和表之间的对应关系,允许我们通过面向对象的方式来操作数据库。
- 将对象、列表的操作,转换为sql语句。
- 根据设计的模型类生成数据库中的表格。
- 将sql查询到的结果转换为对象、列表
而我们的models.py主要负责程序中用于处理数据逻辑的部分(如数据的存取)。它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表。
模型
我们知道了模型层的作用,你有没有想过模型是一个什么东西呢?下面带领大家一起来学习。
什么是模型?
- 模型是一个Python类,它是由django.db.models.Model派生出的子类。
- 一个模型类代表数据库的一张数据表。
- 模型类中的每一个属性都代表数据库中的一个字段。
- 模型是数据交互的接口,是表示和操作数据库的方法和方式。
模型层的前置知识点
为什么要将Django自带的sqlite3数据库替换成MySQL?
Django自带的sqlite3数据库对时间字段不敏感,有时候会展示错乱,所以我们习惯切换成常见的数据库比如MySQL。
ps:django的ORM并不会自动帮我们自动创建库,所以需要提前准备好。
如何单独测试Django的某个功能层
这里我们说的功能层,指代的就是代表某一层的py文件,通常来说别的功能层的py文件并不能单独测试,彼此都是有关联的。这里我们主要指代的还是模型层的my文件models.py。
默认情况下Django并不允许单独测试某个py文件
创建测试环境的方式一:
pycharm提供的python console
创建测试环境的方式二:
自己搭建(可以使用应用中自带的test.py文件或者自己创建一个新的py文件)
在打开对应的py文件后,先拷贝manage.py前四行
接着我们再添加两行代码
import django
django.setup()
django的orm底层还是SQL语句,我们是可以查看的。
临时查看的方法
如果我们手上是一个QuerySet对象 那么可以直接点query查看SQL语句
Django终端打印SQL语句
如果想查看所有orm底层的SQL语句也可以在配置文件添加日志记录
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
二、ORM常用关键字
1.create()
创建数据并直接获取当前创建的数据对象
res = models.User.objects.create(name='阿兵', age=28)
res = models.User.objects.create(name='oscar', age=18)
res = models.User.objects.create(name='jerry', age=38)
res = models.User.objects.create(name='jack', age=88)
print(res)
2.filter()
根据条件筛选数据,结果是QuerySet [数据对象1,数据对象2]
res = models.User.objects.filter()
res = models.User.objects.filter(name='jason')
res = models.User.objects.filter(name='jason', age=19) # 括号内支持多个条件但是默认是and关系
3.first()、last()
QuerySet支持索引取值但是只支持正数 并且orm不建议你使用索引
res = models.User.objects.filter()[1]
res = models.User.objects.filter(pk=100)[0] # 数据不存在索引取值会报错
res = models.User.objects.filter(pk=100).first() # 数据不存在不会报错而是返回None
res = models.User.objects.filter().last() # 数据不存在不会报错而是返回None
4.update()
更新数据(批量更新)
models.User.objects.filter().update() 批量更新
models.User.objects.filter(id=1).update() 单个更新
5.delete()
删除数据(批量删除)
models.User.objects.filter().delete() 批量删除
models.User.objects.filter(id=1).delete() 单个删除
6.all()
查询所有数据,结果是QuerySet [数据对象1,数据对象2]
res = models.User.objects.all()
7.values()
根据指定字段获取数据,结果是QuerySet [{},{},{},{}]
res = models.User.objects.all().values('name')
res = models.User.objects.filter().values()
res = models.User.objects.values()
8.values_list()
根据指定字段获取数据,结果是QuerySet [(),(),(),()]
res = models.User.objects.all().values_list('name','age')
9.distinct()
去重 数据一定要一模一样才可以 如果有主键肯定不行
res = models.User.objects.values('name','age').distinct()
10.order_by()
根据指定条件排序 默认是升序 字段前面加负号就是降序
res = models.User.objects.all().order_by('age')
print(res)
11.get()
根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用
res = models.User.objects.get(pk=1)
print(res)
res = models.User.objects.get(pk=100, name='jason')
print(res)
12.exclude()
取反操作
res = models.User.objects.exclude(pk=1)
print(res)
13.reverse()
颠倒顺序(被操作的对象必须是已经排过序的才可以)
res = models.User.objects.all()
res = models.User.objects.all().order_by('age')
res1 = models.User.objects.all().order_by('age').reverse()
print(res, res1)
14.count()
统计结果集中数据的个数
res = models.User.objects.all().count()
print(res)
15.exists()
判断结果集中是否含有数据 如果有则返回True 没有则返回False
res = models.User.objects.all().exists()
print(res)
res1 = models.User.objects.filter(pk=100).exists()
print(res1)
三、ORM执行SQL语句
django中的ORM提供的操作功能有限,在模型提供的查询API不能满足实际工作需要时,可以在ORM中直接执行原生sql语句。
Django 提供两种方法使用原生SQL进行查询:
一种是使用raw()方法,进行原生SQL查询并返回模型实例;
另一种是完全避开模型层,直接执行自定义的SQL语句。
方式1:
models.User.objects.raw('select * from app01_user;')
方式2:
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursor.fetchall())
四、神奇的双下划线查询
我们在使用ORM操作对数据进行操作的时候,结果通常都是queryset对象,只要结果还是queryset对象就可以无限制的点queryset对象能使用的各种方法。例:
queryset.filter().values().filter().values_list().filter()...
我们在使用ORM操作数据库的时候,会发现一些符号、一些查找条件并不能跟sql语句中一样直接用,比如大于号小于号等符号,如果我们想要在ORM中使用这些条件,需要使用双下滑线查询方法。
常见的双下划线查询方法
方法 | 描述 |
---|---|
__gt | 大于 |
__lt | 小于 |
__gte | 大于等于 |
__lte | 小于等于 |
__in | 或 |
__range | 取指定范围内对应数据,并且首尾都要 |
__contains | 模糊查询,查询出指定字符数据,区分大小写 |
__icontains | 忽略大小写 |
__startswith | 查询以指定字符开头数据 |
__endswith | 查询以指定字符结尾数据 |
代码实操:
# 查询年龄大于18的用户数据
# res = models.User.objects.filter(age__gt=18)
# print(res)
# 查询年龄小于38的用户数据
# res = models.User.objects.filter(age__lt=38)
# print(res)
# 大于等于 小于等于
# res = models.User.objects.filter(age__gte=18)
# res = models.User.objects.filter(age__lte=38)
# 查询年龄是18或者28或者38的数据
# res = models.User.objects.filter(age__in=(18, 28, 38))
# print(res)
# 查询年龄在18到38范围之内的数据
# res = models.User.objects.filter(age__range=(18, 38))
# print(res)
# 查询名字中含有字母j的数据
# res = models.User.objects.filter(name__contains='j') # 区分大小写
# print(res)
# res = models.User.objects.filter(name__icontains='j') # 不区分大小写
# print(res)
# 查询注册年份是2022的数据
# res = models.User.objects.filter(register_time__year=2022)
# print(res)
'''针对django框架的时区问题 是需要配置文件中修改的 后续bbs讲解'''
五、ORM外键字段的创建
复习MySQL外键关系
一对多
外键字段建在多的一方
多对多
外键字段统一建在第三张关系表
一对一
建在任何一方都可以 但是建议建在查询频率较高的表中
ps:关系的判断可以采用换位思考原则 熟练的之后可以瞬间判断
外键字段的创建
1.创建基础表(书籍表、出版社表、作者表、作者详情)
代码在下方
2.确定外键关系
一对多 ORM与MySQL一致 外键字段建在多的一方
多对多 ORM比MySQL有更多变化
1.外键字段可以直接建在某张表中(查询频率较高的)
内部会自动帮你创建第三张关系表
2.自己创建第三张关系表并创建外键字段
详情后续讲解
一对一 ORM与MySQL一致 外键字段建在查询较高的一方
3.ORM创建
针对一对多和一对一的外键字段,同步到表中之后会自动加_id的后缀。
即ForeignKey 和 OneToOneField,会自动给字段加_id后缀
publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
针对多对多 不会在表中有展示 而是创建第三张表
authors = models.ManyToManyField(to='Author')
4.完整的创建代码
from django.db import models
# Create your models here.
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32, verbose_name='书名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
# 八位整数,两位小数
publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')
# 这里要区别auto_now和auto_now_add的区别,auto_now在创建时会将创建时间当成数据记录,在修改的时候会用修改时间进行数据记录
#而auto_now_add只会记录创建的时间信息,修改的时候不会进行记录
# 创建书籍与出版社的一对多外键字段
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
'''django1.x版本外键字段默认都是级联更新删除 django2.x及以上需要自己申明 on_delete=models.XXX '''
# 创建书籍与作者的多对多外键字段
authors = models.ManyToManyField(to='Author')
'''多对多字段为虚拟字段,用于告诉ORM自动创建第三张表,数据库迁移(也就是创建了对应的表)之后,并没有这个字段,但是会有一张新的表'''
# def __str__(self):
# return f'书籍对象:{self.title}'
class Publish(models.Model):
"""出版社表"""
name = models.CharField(max_length=32, verbose_name='名称')
address = models.CharField(max_length=64, verbose_name='地址')
# def __str__(self):
# return f'出版社对象:{self.name}'
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
# 创建作者与作者详情的一对一外键字段
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
# on_delete就是设置外键的级联更新,这里的to的值是创建外键连接的表的名称,如果不写称字符串就会出现查找顺序的问题
# def __str__(self):
# return f'作者对象:{self.name}'
class AuthorDetail(models.Model):
"""作者详情表"""
phone = models.BigIntegerField(verbose_name='手机号')
address = models.CharField(max_length=64, verbose_name='家庭住址')
# def __str__(self):
# return f'详情对象:{self.address}'
ps:模型层写好还要进行数据库迁移
六、外键字段相关操作
数据准备
book表中的数据是用下方的代码创建的,多对多外键创建的表不用手动创建数据,其他的数据需要我们手动创建。因为创建book中的数据的时候会用到别的表中的字段值。
# 针对一对多 插入数据可以直接填写表中的实际字段
models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)
models.Book.objects.create(title='人性的弱点', price=777.55, publish_id=1)
# 针对一对多 插入数据也可以填写表中的类中字段名
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.create(title='水浒传', price=555.66, publish=publish_obj)
'''一对一与一对多一致,既可以传数字也可以传对象'''
# 针对多对多关系绑定
# 添加记录
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(1) # 在第三张关系表中给当前书籍绑定作者
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(2, 3) # 书和作者的外键是authors
book_obj = models.Book.objects.filter(pk=3).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj.authors.add(author_obj1)
book_obj = models.Book.objects.filter(pk=4).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj.authors.add(author_obj1, author_obj2)
# 修改记录
# 这里是用值修改
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set((1, 3)) # 修改关系
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set([2, ]) # 修改关系
# 这里是用获取到的对象进行修改
book_obj = models.Book.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj.authors.set((author_obj1,))
book_obj.authors.set((author_obj1, author_obj2))
# 删除记录,也是分成根据值对象进行删除
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(2)
book_obj.authors.remove(1, 3)
book_obj.authors.remove(author_obj1,)
book_obj.authors.remove(author_obj1,author_obj2)
book_obj.authors.clear()
参数使用讲解
add()
把指定的model对象添加到第三张关联表中。
参数可以是多个位置参数(数字或对象)
remove()
从关联对象集中移除执行的model对象(移除对象在第三张表中与某个关联对象的关系)
参数可以是多个位置参数(数字或对象)
set()
更新某个对象在第三张表中的关联对象。不同于上面的add是添加,set相当于重置
可迭代对象(参数要放到元组或列表中,元组或列表中存放的参数可以是数据值或对象)
clear()
从关联对象集中移除一切对象。(移除所有与对象相关的关系信息)
七、ORM跨表查询
复习MySQL跨表查询的思路
子查询
分步操作:将一条SQL语句用括号括起来当做另外一条SQL
语句的条件
连表操作
先整合多张表之后基于单表查询即可
inner join 内连接
left join 左连接
right join 右连接
union 全连接
正反向查询的概念(重要)
-
正向查询
由外键字段所在的表数据查询关联的表数据是正向 或者说外键在自己手上则是正向查询
-
反向查询
没有外键字段的表数据查询关联的表数据是反向 或者说外键在别人手上则是反向查询
ps:正反向的核心就看外键字段在不在当前数据所在的表中
ORM跨表查询的口诀(重要)
正向查询按外键字段
反向查询按表名小写
八、基于对象的跨表查询
'''基于对象的跨表查询'''
# 1.查询主键为1的书籍对应的出版社名称
# 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=1).first()
# 再判断正反向的概念 由书查出版社 外键字段在书所在的表中 所以是正向查询
print(book_obj.publish.name)
# 2.查询主键为4的书籍对应的作者姓名
# 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=4).first()
# 再判断正反向的概念 由书查作者 外键字段在书所在的表中 所以是正向查询
print(book_obj.authors) # app01.Author.None
print(book_obj.authors.all())
print(book_obj.authors.all().values('name'))
# 3.查询jason的电话号码
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.author_detail.phone)
# 4.查询北方出版社出版过的书籍
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
print(publish_obj.book_set) # app01.Book.None
print(publish_obj.book_set.all())
# 5.查询jason写过的书籍
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set) # app01.Book.None
print(author_obj.book_set.all())
# 6.查询电话号码是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
print(author_detail_obj.author)
print(author_detail_obj.author.name)
ps:在进行反向查询的时候,因为会用掉小写的表名,这时候需要在表名后面跟下_set才能获取到信息,这时候获取到的内容如下:
app01.Book.None
获取到内容后点all方法就能看到内部的数据值
九、基于双下划线的跨表查询
'''基于双下划线的跨表查询'''
# 1.查询主键为1的书籍对应的出版社名称
res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res)
# 2.查询主键为4的书籍对应的作者姓名
res = models.Book.objects.filter(pk=4).values('title', 'authors__name')
print(res)
# 3.查询jason的电话号码
res = models.Author.objects.filter(name='jason').values('author_detail__phone')
print(res)
# 4.查询北方出版社出版过的书籍名称和价格
res = models.Publish.objects.filter(name='北方出版社').values('book__title','book__price','name')
print(res)
# 5.查询jason写过的书籍名称
res = models.Author.objects.filter(name='jason').values('book__title', 'name')
print(res)
# 6.查询电话号码是110的作者姓名
res = models.AuthorDetail.objects.filter(phone=110).values('phone', 'author__name')
print(res)
十、进阶操作
'''基于双下划线的跨表查询'''
# 1.查询主键为1的书籍对应的出版社名称
res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res)
# 2.查询主键为4的书籍对应的作者姓名
res = models.Book.objects.filter(pk=4).values('title', 'authors__name')
print(res)
# 3.查询jason的电话号码
res = models.Author.objects.filter(name='jason').values('author_detail__phone')
print(res)
# 4.查询北方出版社出版过的书籍名称和价格
res = models.Publish.objects.filter(name='北方出版社').values('book__title','book__price','name')
print(res)
# 5.查询jason写过的书籍名称
res = models.Author.objects.filter(name='jason').values('book__title', 'name')
print(res)
# 6.查询电话号码是110的作者姓名
res = models.AuthorDetail.objects.filter(phone=110).values('phone', 'author__name')
print(res)
'''进阶操作'''
# 1.查询主键为1的书籍对应的出版社名称
res = models.Publish.objects.filter(book__pk=1).values('name')
print(res)
# 2.查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=4).values('name','book__title')
print(res)
# 3.查询jason的电话号码
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone')
print(res)
# 4.查询北方出版社出版过的书籍名称和价格
res = models.Book.objects.filter(publish__name='北方出版社').values('title','price')
print(res)
# 5.查询jason写过的书籍名称
res = models.Book.objects.filter(authors__name='jason').values('title')
print(res)
# 6.查询电话号码是110的作者姓名
res = models.Author.objects.filter(author_detail__phone=110).values('name')
print(res)
'''补充'''
# 查询主键为4的书籍对应的作者的电话号码
res = models.Book.objects.filter(pk=4).values('authors__author_detail__phone')
print(res)
res = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res)
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res)
十一、图书管理系统讲解
1.表设计
先考虑普通字段再考虑外键字段
数据库迁移、测试数据录入
2.首页展示
3.书籍展示
4.书籍添加
5.书籍编辑
后端如何获取用户想要编辑的数据、前端如何展示出待编辑的数据
6.书籍删除
十二、聚合查询
在ORM中支持单独使用聚合函数,需要使用aggregate方法。
聚合函数:Max最大、Min最小、Sum总和、Avg平均、count统计
示例1
from django.db.models import Max, Min, Sum, Count, Avg
res = models.Book.objects.aggregate(Max('price'), Count('pk'), 最小价格=Min('price'), allPrice=Sum('price'),平均价格=Avg('price'))
print(res)
ps:我们在进行聚合查询的时候,可以给聚合查询的内容命名,如果不主动命名,也会自动用下划线拼接两个字段名,但是这里的命名不建议用中文(虽然能用)。
示例2
from django.db.models import Max, Min, Sum, Avg, Count
1.查找所有书籍中价格最高的书籍
res = models.Book.objects.aggregate(我是世界上最贵的书=Max('price'))
print(res) # 没有分组之前如果单纯的时候聚合函数 需要关键字aggregate
2.获取书籍中最贵的 最便宜的 全部需要多少钱 平均一本书多少钱 总共有几本书
res1 = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
print(res1)
'''通过 别名 = 聚合函数的方法可以取一个别名'''
十三、分组查询
小提示:
如果执行orm分组查询报错,并且有关键字sql_mode,需要去mysql配置文件中修改严格模式的配置信息。(Mac不会有)
strict mode
移除sql_mode中的only_full_group_by
# 分组查询
from django.db.models import Max, Min, Sum, Count, Avg
# 统计每一本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
print(res)
# 统计出每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)
# 统计不止一个作者的图书
# 1.先统计每本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk'))
print(res)
# 2.筛选出作者个数大于1的数据
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title',
'author_num')
print(res)
# 查询每个作者出的书的总价格
"""
models.表名.objects.annotate() 按照表分组
models.表名.objects.values('字段名').annotate() 按照values括号内指定的字段分组
"""
res = models.Author.objects.annotate(总价=Sum('book__price'),count_book=Count('book__pk')).values('name','总价','count_book')
print(res)
res = models.Book.objects.values('publish_id').annotate(count_pk=Count('pk')).values('publish_id', 'count_pk')
print(res)
十四、ORM中如何给表再次添加新的字段
当我们在对数据库使用ORM进行操作的时候,如果出现需要新增字段的情况,在模型层中修改了代码后,进行数据库迁移操作前,需要对新增的字段值进行设置,否则不让进行数据库迁移。
我们可以把字段值设置成一个固定的值,也可以设置字段值可以为空。
代码
首先我们先给我们的书籍表格再添加两条数据(库存以及已销售)
storage_num = models.IntegerField(verbose_name='库存数')
sale_num = models.IntegerField(verbose_name='已销售')
但是出现报错了
we can't do that (the database needs something to populate existing rows)
现在这张表已经有数据了 再添加两个新的字段 没有数据值 它的数据值呢 ?
两种方式 一个默认值 一个退出不执行这个命令再去修改(所以我们得给他加上数据)
storage_num = models.IntegerField(verbose_name='库存数', null=True)
sale_num = models.IntegerField(verbose_name='已销售', default=1000)
如果不对新的字段值进行设置会出现一下提示:
十五、F与Q查询
F查询
# 1.查询库存数大于卖出数的书籍
'''当查询条件不是明确的 也需要从数据库中获取 就需要使用F查询'''
from django.db.models import F
res = models.Book.objects.filter(kucun__gt=F('maichu'))
print(res)
# 2.将所有书的价格涨800
models.Book.objects.update(price=F('price') + 800)
# 3.将所有书的名称后面追加爆款
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('新款')))
在用ORM进行查询的时候,我们会发现当查询条件不明确的时候不能执行语句,因此需要用到F查询。
Q查询
# 查询主键是1或者价格大于2000的书籍
res = models.Book.objects.filter(pk=1, price__gt=2000) # 逗号默认是and关系
from django.db.models import Q
res = models.Book.objects.filter(Q(pk=1), Q(price__gt=2000)) # 逗号是and
res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000)) # |是or
res = models.Book.objects.filter(~Q(pk=1) | Q(price__gt=2000)) # ~是not
print(res.query)
十六、Q查询进阶操作
这里主要就是让查询数据的时候,可以使用input获取的信息,进行用户交互。
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', 2000)) # 支持添加多个
res = models.Book.objects.filter(q_obj) # 查询支持直接填写q对象
print(res)
十七、ORM查询优化
- 1.ORM的查询默认都是惰性查询(当我们不执行打印操作的时候,ORM语句不会执行,想要看到这个现象需要打开日志功能,即在配置文件中进行配置)
- 2.ORM的查询自带分页处理(可以通过日志展示的代码查看,日志返回的sql代码后端会有一个limit)
- 3.only与defer
only
前置说明
这里需要做一些具体的说明,方便大家理解only和defer。
当我们在Django中执行ORM操作进行数据库查询的时候,其实内部的代码把所有的数据库中的记录,都封装到了ORM操作的对象中去了,因此我们可以通过点的方式或是索引等方式查询到对应的数据。
但是当遇到查询的时候需要查询不在条件中的记录时,就需要执行sql语句进行查询了。
比如我们在查询的时候,需要的结果在外键对应的表中,这时候去外键对应的表中查询数据,就需要执行sql语句进行查询,并且查询一条记录需要执行一次sql语句
而我们的only的作用是把写在括号内的参数中的字段的值封装到对象中,让后续查找的时候 不需要执行sql语句进行查询,加快执行速度。或是起到一个减少代码封装的数据量,加快运行的作用。
而defer则是和only相反,写在括号内的字段值不会被封装到对象中,别的字段反而会被封装到对象中。
only
'''数据对象+含有指定字段对应的数据'''
# res = models.Book.objects.only('title', 'price')
# print(res) # queryset [数据对象、数据对象]
# for obj in res:
# print(obj.title) # 点击括号内填写的字段 不走SQL查询
# print(obj.price)
# print(obj.publish_time) # 可以点击括号内没有的字段获取数据 但是会走SQL查询
defer
res = models.Book.objects.defer('title', 'price')
# print(res) # queryset [数据对象、数据对象]
for obj in res:
# print(obj.title) # 点击括号内填写的字段 走SQL查询
# print(obj.price)
print(obj.publish_time) # 点击括号内没有的字段获取数据 不走SQL查询
-
4.select_related与prefetch_related
select_related括号内填写一对多、一对一字段 自动连表然后继续数据封装
prefetch_related括号内填写一对多、一对一字段 基于子查询然后封装数据
# res = models.Book.objects.all()
# for obj in res:
# print(obj.publish.name) # 每次查询都需要走SQL
# res = models.Book.objects.select_related('authors') # 先连表后查询封装
# res1 = models.Author.objects.select_related('author_detail') # 括号内不支持多对多字段 其他两个都可以
# print(res1)
# for obj in res:
# print(obj.publish.name) # 不再走SQL查询
res = models.Book.objects.prefetch_related('publish') # 子查询
for obj in res:
print(obj.publish.name)
案例截图
十八、ORM事务操作
"""
1.事务的四大特性(ACID)
原子性、一致性、隔离性、持久性
2.相关SQL关键字
start transaction;
rollback;
commit;
savepoint;
3.相关重要概念
脏读、幻读、不可重复读、MVCC多版本控制...
"""
django orm提供了至少三种开启事务的方式
方式1:配置文件的数据库相关配置中添加键值对
全局有效
"ATOMIC_REQUESTS": True
ps:每次请求所涉及到的orm操作同属于一个事务
方式2:装饰器
局部有效,这个视图函数内的一些orm操作属于一个事务
from django.db import transaction
@transaction.atomic
def index():pass
方式3:with上下文管理
局部有效,写在with下方的orm操作属于一个事务
from django.db import transaction
def reg():
with transaction.atomic():
pass
ps:这里的三种方法有个小区别,前面两种方式执行事务,遇到返回值类型不对,orm操作雀食是可以正常执行的,但是with上下文管理的方式操作事务的话,则不行,操作会回退。
十九、ORM常用字段类型
名称 | 含义 |
---|---|
AutoField() | Int自增列 必须填入参数 primary_key=True 当model中如果没有自增列 则自动会创建一个列名为id的列 |
CharField() | 字符类型 必须提供max_length参数 max_length表示字符长度 |
IntegerField() | 一个整数类型 范围在 -2147483648 to 2147483647 (一般不用它来存手机号(位数也不够) 直接用字符串存) |
BigIntegerField() | 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 |
DateField() | 日期字段 日期格式 YYYY-MM-DD 相当于Python中的datetime.date()实例 |
DateTimeField() | 日期时间字段 格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 相当于Python中的datetime.datetime()实例 |
DecimalField() | 10进制小数 参数 max_digits 小数总长度 decimal_places,小数位长度 |
EmailField() | 字符串类型 Django Admin以及ModelForm中提供验证机制 |
BooleanField() | 布尔值类型 传布尔值存数字0或1 |
TextField() | 文本类型 存储大段文本 |
FileField() | 字符串 路径保存在数据库 文件上传到指定目录 参数 upload_to = " " 上传文件的保存路径 storage = None 存储组件 默认django.core.files.storage.FileSystemStorage |
ForeignKey() | 外键类型在ORM中用来表示外键关联关系 一般把ForeignKey字段设置在 '一对多’中’多’的一方 ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系 |
OneToOneField() | 一对一字段 通常一对一字段用来扩展已有字段 通俗的说就是一个人的所有信息不是放在一张表里面的,简单的信息一张表,隐私的信息另一张表,之间通过一对一外键关联 |
ManyToManyField() | 简单来说就是在多对多表关系并且这一张多对多的关系表是有Django自动帮你建的情况下 下面的方法才可使用create add set remove clear |
ORM还支持用户自定义字段类型,比方说ORM中没有设置char类型的字段,我们可以使用自定义字段来实现他。
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length
这里是调用我们定义的字段类型
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
二十、ORM常用字段参数
名称 | 含义 |
---|---|
primary_key | 主键 |
verbose_name | 注释 |
max_length | 字段长度 |
max_digits | 小数总共多少位 |
decimal_places | 小数点后面的位数 |
auto_now | 每次操作数据自动更新事件 |
auto_now_add | 首次创建自动更新事件后续不自动更新 |
null | 允许字段为空 |
default | 字段默认值 |
unique | 唯一值 |
db_index | 给字段添加索引 |
choices | 当某个字段的可能性能够被列举完全的情况下使用。如:性别、学历、工作状态、... |
to | 关联表 |
to_field | 关联字段(不写默认关联数据主键) |
on_delete | 当删除关联表中的数据时,当前表与其关联的行的行为。 |
需要特别拿出来讲的是关联表时用的字段参数:
on_delete
它的值决定了关联表之前的关联操作行为
这里是代码使用展示
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id",
on_delete=models.SET(func)
)
不同的值对应的功能
1、models.CASCADE
级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2、models.SET_NULL
当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
3、models.PROTECT
当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4、models.SET_DEFAULT
当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
5、models.SET()
当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
6、models.DO_NOTHING
什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似
choices
当字段数据的可能性是可以完全列举出来的时候 应该考虑使用该参数
class UserInfo(models.Model):
username = models.CharField(max_length=32)
gender_choice = (
(1, '男性'),
(2, '女性'),
(3, 'other'),
)
gender = models.IntegerField(choices=gender_choice)
user_obj = models.UserInfo.objects.filter(pk=1).first()
print(user_obj.gender) # 获取的是真实数据
print(user_obj.get_gender_display())
user_obj1 = models.UserInfo.objects.filter(pk=2).first()
user_obj2 = models.UserInfo.objects.filter(pk=3).first()
user_obj3 = models.UserInfo.objects.filter(pk=4).first()
print(user_obj1.get_gender_display())
print(user_obj2.get_gender_display())
print(user_obj3.get_gender_display()) # 如果没有则按照真实数据返回
二十一、多对多三种创建方式
1.全自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
优势:自动创建第三张表 并且提供了add、remove、set、clear四种操作
劣势:第三张表无法创建更多的字段 扩展性较差
2.纯手动创建
class Book(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表完全由自己创建 扩展性强
劣势:编写繁琐 并且不再支持add、remove、set、clear以及正反向概念
3.半自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author'))
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表完全由自己创建 扩展性强 正反向概念依然清晰可用
劣势:编写繁琐不再支持add、remove、set、clear
二十二、批量操作数据
当我们给数据库插入很多的数据时,如果我们使用orm操作一条条用for循环插入数据,效率很低。
这里就需要介绍两个批量操作数据的方法:
models.Books01.objects.bulk_create
models.Books01.objects.bulk_update
这里的create是批量创建数据,update是批量更新数据。
在进行批量操作数据之前,下方代码使用的方式是把数据封装到对象中,然后把对象添加到一个列表中,最后执行批量操作的时候把这个列表当作参数放到小括号内即可。
def ab_bk_func(request):
# 1.往books表中插入10万条数据
# for i in range(1, 100000):
# models.Books.objects.create(title='第%s本书' % i)
"""直接循环插入 10s 500条左右"""
book_obj_list = [] # 可以用列表生成式[... for i in ... if ...] 生成器表达式(... for i in ... if ...)
for i in range(1, 100000):
book_obj = models.Books01(title='第%s本书' % i) # 单纯的用类名加括号产生对象
book_obj_list.append(book_obj)
# 批量插入数据
models.Books01.objects.bulk_create(book_obj_list)
"""使用orm提供的批量插入操作 5s 10万条左右"""
# 2.查询出所有的表中并展示到前端页面
book_queryset = models.Books01.objects.all()
return render(request, 'BkPage.html', locals())