django 模型层
django 模型层
一、ORM操作(必知必会16条)
1. create() # 添加数据,返回值得到queryset对象 2. all() # 查询全部数据,返回值得到queryset对象 3. filter() # 条件查询数据,返回值得到queryset对象 4. update() # 更新数据,返回值得到的是受影响的行数 5. delete() # 删除数据,返回值得到的是受影响的表和行数 6. first() # 取第一个元素,返回值得到数据对象 7. last() # 取最后一个元素,返回值得到数据对象 8. get() # 取值,返回值直接获取对象本身,不推荐使用,当查询条件不存在直接报错 9. value() # 取值,返回值得到queryset对象 [{}, {}, {}, {}, ...] 列表套字典 10. value_list() # 取值,返回值得到queryset对象 [(), (), (), (), ...] 列表套元组 11. order_by() # 排序,返回值得到queryset对象 12. count() # 计数,返回值是数据的条数 13. exclude() # 排除什么什么之外,返回值得到queryset对象 14. exists() # 判断是否存在,返回的是布尔值 15. reverse() # 反转但不是倒序,返回值得到queryset对象 16. distinct() # 去重,去重的前提是 数据完全一模一样的(包括主键id),返回的是布尔值
app01_movie数据表中的数据(ORM操作演示以app01_movie表数据为例):
class Movie(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=8, decimal_places=2) publish_time = models.DateField() ''' auto_now:每次修改数据的时候,都会自动将最新的更新记录下来 auto_now_add:只在创建数据的时候将创建时间自动记录下来,之后不会自动改变 ''' # publish_time = models.DateTimeField() def __str__(self): return self.name
1、通过ORM操作查看原生sql语句
① 只要返回的结果是queryset对象就可以直接 .query 查看当前queryset对象内部的sql语句
# 例:查看movie表中的所有数据
from app01 import models data = models.Movie.objects.all() print(data.query)
打印结果:
SELECT `app01_movie`.`id`, `app01_movie`.`name`, `app01_movie`.`price`, `app01_movie`.`publish_time` FROM `app01_movie`
② 查看所有orm操作内部的sql语句,django终端会自动打印SQL语句(固定的日志文件配置,拷贝到settings.py配置文件中即可)
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level': 'DEBUG', }, } }
# 例:查看movie表中的所有数据 from app01 import models data = models.Movie.objects.all() print(data)
2、create()
添加数据,返回值得到queryset对象
models.Movie.objects.create(name='黑猫警长', price=1451.14, publish_time='2020-1-1') # 添加日期数据的第一种方式 from datetime import datetime ntime = datetime.today() models.Movie.objects.create(name='西游记', price=1000.11, publish_time=ntime) # 添加日期的第二种方式,直接传日期对象
3、all()
查询全部数据,返回值得到queryset对象
data = models.Movie.objects.all() print(data)
4、filter()
where条件查询数据,返回值得到queryset对象,括号内可以放多个条件,关系是and
# data = models.Movie.objects.filter(name='西游记')[0] # data = models.Movie.objects.filter(id=1)[0] data = models.Movie.objects.filter(pk=1)[0] # pk指的就是当前字段的主键名 print(data) print(data.name)
5、update()
更新数据,返回值得到的是受影响的行数
data = models.Movie.objects.filter(pk=2).update(name='师徒四人', price='1547.21') print(data)
6、delete()
删除数据,返回值得到的是受影响的表和行数
data = models.Movie.objects.filter(pk=1).delete() print(data)
7、first()
取第一个元素,返回值得到数据对象
data = models.Movie.objects.filter().first() print(data)
8、last()
取最后一个元素,返回值得到数据对象
data = models.Movie.objects.filter().last() print(data)
9、get()
取值,返回值直接获取对象本身,不推荐使用,当查询条件不存在直接报错
data = models.Movie.objects.get(pk=2) # pk=2存在 print(data)
data = models.Movie.objects.get(pk=1) # pk=1不存在 print(data)
10、values()
取值,返回值得到queryset对象 [{}, {}, {}, {}, ...] 列表套字典
# 取值以字典的形式获取指定字段的数据 data = models.Movie.objects.values('name', 'publish_time') print(data)
11、values_list()
取值,返回值得到queryset对象 [(), (), (), (), ...] 列表套元组
# 取值以元组的形式获取指定字段的数据 data = models.Movie.objects.values_list('name', 'publish_time') print(data)
12、order_by()
排序,返回值得到queryset对象
data = models.Movie.objects.order_by('price') # 默认是升序 data2 = models.Movie.objects.order_by('-price') # 加个-就是倒序 print(data) print(data2)
13、count()
计数,返回值是数据的条数
data = models.Movie.objects.count() print(data)
14、exclude()
排除什么什么之外,返回值得到queryset对象
data = models.Movie.objects.exclude(pk=2) print(data)
15、exists()
判断是否存在,返回的是布尔值
data = models.Movie.objects.filter(pk=2).exists() # pk=2存在 print(data) data = models.Movie.objects.filter(pk=1).exists() # pk=1不存在 print(data)
16、reverse()
反转但不是倒序,返回值得到queryset对象
data = models.Movie.objects.order_by('price') # 默认是升序 data2 = models.Movie.objects.order_by('price').reverse() # 反转,但不是倒序 print(data) print(data2)
17、distinct()
去重,去重的前提是 数据完全一模一样的(包括主键id),返回的是布尔值
data = models.Movie.objects.all().distinct() # 无法去重,因为主键id不一样 print(data) models.Movie.objects.create(name='黑猫警长', price='1000.11', publish_time='2020-01-01') # 添加一条name、price字段都一样的数据(id不一样) print(models.Movie.objects.filter(name='黑猫警长')) # 查看黑猫警长的数据 data2 = models.Movie.objects.values('name', 'price').distinct() # 可以选择性去重,结果中不包括主键id print(data2)
二、双下划线查询(单表查询)
1、大于、小于、大于等于、小于等于
# 查询电影价格大于500的电影 data = models.Movie.objects.filter(price__gt=500) print(data) # 查询电影价格小于500的电影 data = models.Movie.objects.filter(price__lt=500) print(data) # 查询电影价格大于等于500的电影 data = models.Movie.objects.filter(price__gte=500) print(data) # 查询电影价格小于等于500的电影 data = models.Movie.objects.filter(price__gte=500) print(data)
2、and关系(什么和什么)
# 查询电影价格为100、200和300的电影 data = models.Movie.objects.filter(price__in=[100, 200, 300]) print(data)
3、在什么什么区间内
# 查询电影价格在200~500之间的电影 data = models.Movie.objects.filter(price__range=(200, 500)) print(data)
4、数据中含有什么字符
# 查询电影名中有字母x的电影 # data = models.Movie.objects.filter(name__contains='x') # contains默认是区分大小写的 data = models.Movie.objects.filter(name__icontains='X') # icontains不区分大小写 print(data)
5、查询时间字段中的年、月、日对应的数据
# 查询2020年出版的电影(不管月日) data = models.Movie.objects.filter(publish_time__year='2020') print(data) # 查询12月出版的电影(不管年日) data = models.Movie.objects.filter(publish_time__month='12') print(data) # 查询8号出版的电影(不管年月) data = models.Movie.objects.filter(publish_time__day='8') print(data)
三、多表查询
1、图书管理系统表创建并手动录入数据
class Book(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=8, decimal_places=2) publish_time = models.DateField(auto_now_add=True) # 该字段新增数据自动添加,无需考虑 # 出版社 一对多 外键字段在多的一方 publish = models.ForeignKey(to='Publish') # 作者 多对多 外键字段建在任意一端都可以 最好建立在查询频率高的一方 author = models.ManyToManyField(to='Author') # 库存 kucun = models.BigIntegerField(default=20000) # 售出数 sale = models.BigIntegerField(default=20000) def __str__(self): return self.name class Publish(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=64) def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() # 作者详情 一对一 外键字段在任意一方均可 最好建立在查询频率高的一方 author_detail = models.OneToOneField(to='AuthorDetail') def __str__(self): return self.name class AuthorDetail(models.Model): phone = models.BigIntegerField() addr = models.CharField(max_length=64)
图书管理系统表关系模型:
2、外键字段的增删改查
PS:外键字段在1.x版本中默认就是级联更新,级联删除;在2.x版本中需要手动指定,给ForeignKey增加属性:on_delete=models.SET_NULL 即可
1)一对多
增 create()
# 第一种:直接写实际的表字段 publish_id=数字 models.Book.objects.create(name='西游记', price='200.99', publish_id=2) # 第二种:通过虚拟字段添加 publish_obj = models.Publish.objects.get(pk=1) models.Book.objects.create(name='红楼梦', price='180.50', publish=publish_obj)
改 update()
# 第一种:直接改实际的表字段 publish_id=数字 models.Book.objects.filter(pk=1).update(publish_id=3) # 第二种:通过虚拟字段修改 publish_obj = models.Publish.objects.get(pk=3) models.Book.objects.filter(pk=1).update(publish=publish_obj)
2)多对多
增 add(),专门给第三张关系表添加数据,括号内既可以传数字,也可以传对象,并且都支持传多个
# 第一种:传数字 book_obj = models.Book.objects.filter(pk=1).first() book_obj.author.add(1) # 第二种:传对象 book_obj = models.Book.objects.filter(pk=1).first() author_obj1 = models.Author.objects.get(pk=2) author_obj2 = models.Author.objects.get(pk=3) book_obj.author.add(author_obj1, author_obj2)
移除 remove()
# 第一种:传数字 book_obj = models.Book.objects.filter(pk=1).first() book_obj.author.remove(1, 2) # 第二种:传对象 book_obj = models.Book.objects.filter(pk=1).first() author_obj = models.Author.objects.get(pk=3)
改 set(),括号内支持传数字和对象,但是需要是可迭代对象
# 第一种:传数字 book_obj = models.Book.objects.filter(pk=2).first() book_obj.author.set([1, 2]) # 第二种:传对象 book_obj = models.Book.objects.filter(pk=2).first() author_obj = models.Author.objects.get(pk=3) book_obj.author.set((author_obj, ))
清空 clear(),清空第三章关系表中的对象数据,不需要任何参数
book_obj = models.Book.objects.filter(pk=2).first() book_obj.author.clear()
四、多表查询之跨表查询
跨表查询的方式:
1. 子查询
将一张表的查询结果当做另外一张表的查询条件。正常解决问题的思路是分步操作
2. 联表查询
inner join
left join
right join
union
1、正反向查询的概念
定义:跨表查询的时候,外键字段是否在当前数据对象中,如果在,查询另外一张关系表,叫正向查询;如果不在,查询另外一张表,叫反向查询
口诀:正向查询按外键字段,反向查询按表名小写_set
2、基于对象的跨表查询(子查询)
例题:
① 查询书籍pk为1的出版社名称、地址
book_obj = models.Book.objects.filter(pk=1).first() print(book_obj.publish.name) print(book_obj.publish.addr)
② 查询书籍pk为2的所有作者姓名
book_obj = models.Book.objects.filter(pk=2).first() print(book_obj.author.all()) # 拿到所有书籍主键值为2的作者对象 for author_obj in book_obj.author.all(): print(author_obj.name)
③ 查询作者pk为1的电话号码
author_obj = models.Author.objects.filter(pk=2).first() print(author_obj.author_detail.phone)
PS:①~③为正向查询,当外键字段对应的数据可以有多个的时候需要加.all(),否则点外键字段即可获取到对应的数据对象
④ 查询出版社名称为东方出版社的书籍
publish_obj = models.Publish.objects.filter(name='东方出版社').first() print(publish_obj.book_set.all()) # 得到东方出版社出版过的所有书的对象,可使用for循环将对应的书名取出
⑤ 查询作者为tom的书
author_obj = models.Author.objects.filter(name='tom').first() print(author_obj.book_set.all()) # 得到东方出版社出版过的所有书的对象,可使用for循环将对应的书名取出
⑥ 查询手机号为110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first().author.name print(author_detail_obj)
PS:④~⑥为反向查询,看表名小写是否需要加_set.all(),一对多和多对多的时候需要加,一对一不需要
3、基于双下划线跨表查询(联表查询)
例题:
① 查询书籍pk为1的出版社名称
# 正向 book = models.Book.objects.filter(pk=1).values('publish__name') # 写外键字段,就以为着你已经在外键字段管理的那种表中了 print(book) # 反向 book = models.Publish.objects.filter(book__pk=1).first().name # 拿出版过pk为1的书籍对应的出版社 print(book)
② 查询书籍pk为1的作者姓名和年龄
# 正向 data = models.Book.objects.filter(pk=1).values('author__name', 'author__age') print(data) # 反向 data = models.Author.objects.filter(book__pk=1).values('name', 'age') print(data)
③ 查询作者为jack的年龄和手机号
# 正向 data = models.Author.objects.filter(name='jack').values('age', 'author_detail__phone') print(data) # 反向 data = models.AuthorDetail.objects.filter(author__name='jack').values('author__age', 'phone') print(data)
④ 查询书籍pk为1的作者的手机号
# 正向 data = models.Book.objects.filter(pk=1).values('author__author_detail__phone') print(data) # 反向 data = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone') print(data)
PS:只要表中有关系,就可以通过正向的外键字段或者反向的表名小写,进行连续的跨表操作
五、聚合函数
求最大值、最小值、平均值、计数、和,返回值得到字典
例题:
# 求最大值、最小值、平均值、计数、总和 from django.db.models import Max, Min, Avg, Count, Sum data = models.Movie.objects.aggregate(Max('price'), Min('price'), Avg('price'), Count('pk'), Sum('price')) print(data)
六、分组查询
关键字 annotate
例题:
① 统计每一本书的作者个数
data = models.Book.objects.annotate(author_num=Count('author__pk')).values('name', 'author_num') print(data)
② 统计出每个出版社卖的最便宜的书的价格
data = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price') print(data)
③ 统计不止一个作者的图书
data = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('name') print(data)
④ 查询各个作者出版的书的总价格
data = models.Author.objects.annotate(price_sum=Sum('book__price')).values('name', 'price_sum') print(data)
上面的例子都是按照表来分组,annotate在表后面就是按表来分组,annotate在value后面就是按照字段分组,那么如何按照表中的某一个指定的字段分组呢?
data = models.Book.objects.values('price').annotate() # 以价格分组 print(data)
七、F与Q查询
1、F查询
能够获取到数据库中某个字段对应的值,并将其拿出一个一个进行操作,操作之前需要导入一个模块
from django.db.models import F
例题:
① 查询库存数大于卖出数的书籍
res = models.Book.objects.filter(kucun__gt=F('sale')).values('name') print(res)
② 将所有书的价格提高100块钱
2、Q查询
能够改变查询的条件关系 and or not,操作之前需要导入一个模块
from django.db.models import Q
例题:
查询书的名字是西游记或者价格是500的书籍
""" data = models.Book.objects.filter(Q(price=500), Q(name='西游记')).values('name') # 逗号是and关系 data = models.Book.objects.filter(~Q(price=500) | Q(name='西游记')).values('name') # ~是not关系,取反 """ data = models.Book.objects.filter(Q(price=500) | Q(name='西游记')).values('name') # |是or关系 print(data)
3、Q查询的高阶用法
应用场景:网页的搜索功能,根据用户选择的内容和输入内容来搜索,比如用户输入 name 西游记 等
from django.db.models import Q q = Q() q.connector = 'or' # q对象默认也是and关系,可以通过connector改变or q.children.append(('name', '西游记')) q.children.append(('price', 500)) data = models.Book.objects.filter(q).values('name') print(data)
from django.db.models import Q column_name = input('输入查询的字段名:').strip() column_value = input('输入查询的字段值:').strip() q = Q() q.connector = 'or' # q对象默认也是and关系,可以通过connector改变为or q.children.append((column_name, column_value)) data = models.Book.objects.filter(q).values('name') print(data)
八、models中常用字段
1. AutoField(primary_key=True) # 主键字段 2. CharField(max_length=32) # varchar(32) 3. IntegerField() # int 4. BigIntegerField() # bigint 5. DecimalField() # decimal 6. EmailField() # varchart(254) 7. DateField() # date 8. DateTimeField() # datetime auto_now:每次编辑数据时候都会自动更新该字段时间 auto_now_add:创建时候自动更新,更新字段不会改变 9. BooleanField() # 给该字段传布尔值,会对应成数字0/1 应用场景:is_delete is_vip is_status 10. TextField() # 文本类型,存储大段文本 11. FileField() # 自渡船,路径保存在数据库,文件上传到指定目录,只存文件路径 upload_to = '指定文件路径' 给该字段传文件对象,文件会自动保存到upload_to指定的文件夹下,然后改字段存文件的路径
1、自定义char类型字段
from django.db.models import Field class DefCharField(Field): # 需要继承Field类 def __init__(self, max_length, *args, **kwargs): self.max_length = max_length # 拦截一个父类的方法 操作完之后 利用super调用父类的方法 super().__init__(max_length=max_length, *args, **kwargs) def db_type(self, connection): return 'char(%s)' % self.max_length
2、字段内的关键性参数
null
default
on_delete = models.CASCADE
3、choices参数
如果数据能够被列举出来供用户选择,那么可以考虑使用choices参数
class UserInfo(models.Model): name = models.CharField(max_length=64) gender_choice = ( (1, '男'), (2, '女'), (3, '其他'), ) gender = models.IntegerField(choices=gender_choice) # 该字段还是存数字, 并且可以匹配关系之外的数字 record_choices = (('checked', "已签到"), ('vacate', "请假"), ('late', "迟到"), ('noshow', "缺勤"), ('leave_early', "早退"), ) record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
① 查询pk为1的人的性别
user_obj = models.UserInfo.objects.get(pk=2) print(user_obj.gender) # 针对choices参数字段,取值的时候,get_字段名_display() print(user_obj.get_gender_display())
② 查询pk为5的人的性别
user_obj = models.UserInfo.objects.get(pk=5) print(user_obj.name, user_obj.gender, user_obj.get_gender_display())
九、数据库操作优化
only与defer,defer与only互为反对象
1、only()
作用:括号内传字段名,得到的结果是一个列表套数据对象,该对象内只含有指定的字段属性,对象.该属性是不会走数据库的,但是一旦对象.非括号内的字段,也能拿到数据,但是是重新走的数据库查询
data = models.Book.objects.only('name') # data对象内部只有name属性 print(data) for i in data: print(i.name) # 不走数据库 print(i.price) # 走数据库
2、defer()
作用:括号内传字段名,得到的结果是一个列表套数据对象,该对象内不含有该字段属性,对象一旦.非括号内的字段是不会走数据库的,但是对象.该属性,也能拿到数据,但是是重新走的数据库查询
data = models.Book.objects.defer('name') # data对象内部含有除了name属性之外的所有属性 print(data) for i in data: print(i.price) # 不走数据库 print(i.name) # 走数据库
3、select_related()
作用:select_related() 括号内只能传外键字段,不能是多对多字段,只能是一对多、一对一。内部是连表操作,先将关系表全部连接起来,之后在一次性查询出来,封装到对象中,数据对象之后再获取任意表中的数据的时候都不需要再走数据库了,因为全部封装成了对象的属性。select_related('外键字段1__外键字段2__外键字段3...') 将所有含有外键字段的表都连起来
data = models.Book.objects.select_related('publish') print(data) for i in data: print(i.publish.name) print(i.publish.addr)
4、prefetch_related()
作用:prefetch_related() 内部是子查询,但是给我们的感觉是连表操作,内部通过子查询将外键关联表中的数据也全部封装到对象中,之后对象.当前表或者外键关联表中的字段都不需要走数据库
data = models.Book.objects.prefetch_related('publish') print(data) for i in data: print(i.publish.name)
5、select_related和prefetch_related优缺点比较
select_related是连表操作,好处在于只走一次sql查询
耗时耗在 “连接表的操作”
prefetch_related是子查询,走两次sql查询
耗时耗在 “查询次数”
十、django orm开启事务操作
1、事务的四大特性(ACID)
① 原子性
② 一致性
③ 隔离性
④ 持久性
2、orm如何开启事务
from django.db import transaction with transaction.atomic(): # 在with代码块中执行的orm语句中同属于一个事务 pass # 代码块运行结束,事务就结束了
3、数据库的三大范式
自查...