Django之模型层——ORM执行SQL语句
模型层(models.py)负责和数据库进行通信,如下图:
什么是模型?
- 模型是一个pyyhon类,它是同django.db.models.Mode派生出的子类。
- 一个模型类代表数据库中一张数据表。
- 模型类中每一个类属性都代表数据库中的一个字段。
- 模型是数据交互的接口,是表示操作数据库的方法和方式。
Django测试环境搭建
只需要测试Django项目中某个py文件的内容,可以不必通过前端,单独进行测试即可。
方式一(推荐):
- 使用脚本形式:任意创建一个 py 文件(或者直接在test.py中), 在该文件内书写固定的配置(可以去manage.py文件中去复制前四行)
# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行 import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings") import django django.setup() # 在这个代码块的下面就可以测试django里面的单个py文件了(注意: 导模块也要写在这下面)
方式二:
- 直接使用pycharm提供的Python console窗口
推荐使用方式一,Python Console测试报错进行修改之后,如果还是继续报错,有可能是之前的缓存影响。可以关闭一下此窗口,再重新打开输入测试语句再试一次。
ORM映射
# 在models.py文件准备表
from django.db import models class User(models.Model): name = models.CharField(max_length=32,verbose_name='用户名') age = models.IntegerField(verbose_name='年龄') register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True) """ auto_now:每次修改数据的时候都会自动更新当前时间 auto_now_add:只在数据创建的时候记录一次创建时间,后续不会自动更改 """
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() # 取queryset里的第一个数据,数据不存在不会报错而是返回None res = models.User.objects.filter().last() # 取queryset里的最后一个数据,数据不存在不会报错而是返回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的
方式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())
Django终端打印SQL语句
如果你想知道你对数据库进行操作时,Django内部到底是怎么执行它的sql语句时可以加下面的配置来查看
在Django项目的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', }, } }
配置好之后,再执行任何对数据库进行操作的语句时,会自动将Django执行的sql语句打印到pycharm终端上
补充:
除了配置外,还可以通过一点.query即可查看查询语句,具体操作如下:
参数介绍
参数 | 释义 |
__in | 是否在给定的数据集中 |
__gt | 大于 |
__lt | 小于 |
__gte | 大于等于 |
__lte | 小于等于 |
__range | 在…之间, 闭区间 |
__contains | 包含 |
__icontains | 包含(忽略大小写) |
__startswith | 以…开头 |
__endswith | 以…结尾 |
__year | 取出年份(仅限于时间类型) |
__month | 取出月份(仅限于时间类型) |
__day | 取出日期(仅限于时间类型) |
__hour | 取出小时(仅限于时间类型) |
__minute | 取出分钟(仅限于时间类型) |
__second | 取出秒(仅限于时间类型) |
__week_day | 一周的第几天(仅限于时间类型) |
示例
# 查询年龄大于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)
常用字段类型表
常用字段 | 描述 | 与MySQL字段对应关系 |
AutoField | 必须指定参数primary_key=True指定主键. 如果没有设置主键, 默认创建并以id名作为主键 | integer auto_increment |
IntegerField | 整型字段. 存储宽度4Bytes. 无符号: 0~2^32 有符号: -232/2~232-1 | int 或 integer |
BigIntegerField | 整型字段. 存储宽度8Bytes. 无符号: 0~2^64 有符号: -264/2~264-1 | bigint |
DecimalField | 浮点字段. 必须指定参数max_digits设置总长度. decimal_places设置小数位长度 | numeric(%(max_digits)s, %(decimal_places)s) |
EmailField | 字符字段. Django Admin以及ModelForm中提供验证机制 | |
CharField | 字符字段. 必须指定参数max_length参数设置字符存储个数. Django中的CharField对应的MySQL数据库中的varchar类型,没有设置对应char类型的字段,但是Django允许我们自定义新的字段. | varchar(%(max_length)s) |
DateField | 日期字段. 格式: 年-月-日. 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 | date |
DateTimeField | 日期字段. 格式: 年-月-日 时:分:秒 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 | datetime |
关系型字段
-
ForeignKey : 一对多
# 一对多,外键字段推荐建在一对多中多的一方 publish = models.ForeignKey(to='Publish)
-
ManyToManyField : 多对多
# 建议外键字段推荐建在查询频率较高的一方,且无需手动创建中间表,models会自动帮你创建一张虚拟表 authors = models.ManToManyField(to='authors')
-
OneToOneField : 一对一
# 建议外键字段建立在查询频率较高的一方 author_detail = models.OneToOneField(to='AuthorDetail')
ps : 建立一对多、一对一关系的外键关联表, 关联表中默认会在建立的外键字段之后拼接**"_id"**, 也就是 我们无需自己手动写个后缀
字段参数
个别字段才有的参数
CharField(max_length=100) # 字段长度为utf8编码的100个字符串 DateField(unique_for_date=True) # 这个字段的时间必须唯一 DecimalField(max_digits=4, decimal_places=2) # 前者表示整数和小数总共多少数,后者表示小数点的位数
auto_now和auto_now_add(面试)
- 提示: 一般作为
DateField
和DateTimeField
参数
auto_now=True # 对这条记录内容更新时的时间 auto_now_add=True # 创建这条记录时的时间
创建表(先创建基础表,再写外键字段)
准备工作
cmd创建一张表:create database day06;
settings里面修改配置文件:
1.创建基础表(书籍表、出版社表、作者表、作者详情)
# models.py
from django.db import models 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.DecimalField(auto_now_add=True,verbose_name='出版日期') class Publish(models.Model): """出版社表""" name = models.CharField(max_length=32,verbose_name='名称') address = models.CharField(max_length=64,verbose_name='地址') class Author(models.Model): """作者表""" name = models.CharField(max_length=32,verbose_name='姓名') age = models.IntegerField(verbose_name='年龄') class AuthorDetail(models.Model): """作者详情表""" phone = models.BigIntegerField(verbose_name='手机号') address = models.CharField(max_length=64,verbose_name='家庭住址')
2. 确定外键关系
一对多:ORM与MySQL一致,外键字段建在多的一方 多对多:ORM比MySQL有更多变化 1.外键字段可以直接建在某张表中(查询频率较高的) 内部会自动帮你创建第三张关系表 2.自己创建第三张关系表并创建外键字段 详情后续讲解 一对一:ORM与MySQL一致,外键字段建在查询较高的一方
分析:
- 书籍与出版社:一对多,外键字段建在多(书籍表)的一方
- 书籍与作者:多对多,外键字段建在查询频率较高(书籍表)的一方
- 作者与作者详情:一对一,外键字段建在查询频率较高(作者表)的一方
3. 添加外键字段
from django.db import models 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.DecimalField(auto_now_add=True,verbose_name='出版日期') # 创建书籍与出版社的一对多外键字段 """on_delete=models.CASECADE 和 on_update=models.CASECADE 设置级联更新级联删除. 等同于SQL语言中的ON DELETE CASCADE等约束 (提示: 该操作为Django1.X版本的默认操作, 2.X和3.X需要手动指定) """ publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE) # 创建书籍与作者的多对多外键字段 authors = models.ManyToManyField(to='Author') class Publish(models.Model): """出版社表""" name = models.CharField(max_length=32,verbose_name='名称') address = models.CharField(max_length=64,verbose_name='地址') class Author(models.Model): """作者表""" name = models.CharField(max_length=32,verbose_name='姓名') age = models.IntegerField(verbose_name='年龄')
# 创建作者与作者详情表之间一对一外键字段 """on_delete=models.CASECADE 和 on_update=models.CASECADE 设置级联更新级联删除. 等同于SQL语言中的ON DELETE CASCADE等约束 (提示: 该操作为Django1.X版本的默认操作, 2.X和3.X需要手动指定) """ author_detail = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE) class AuthorDetail(models.Model): """作者详情表""" phone = models.BigIntegerField(verbose_name='手机号') address = models.CharField(max_length=64,verbose_name='家庭住址')
数据库迁移
python38 manage.py makemigrations
python38 manage.py migrate
Django连接数据库
手动添加三张表的数据
通过ORM创建书籍表
import os def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoday06.settings') import django django.setup() from app01 import models """创建数据的两种方式""" # 方式1:外键字段绑定要关联的id,注意外键字段之后拼接"_id" models.Book.objects.create(title='三国演义',price=88.88,publish_id=1) models.Book.objects.create(title='红楼梦', price=66.66, publish_id=2) models.Book.objects.create(title='西游记', price=99.99, publish_id=1) # 方式2:先获取关联表中要绑定的对象,外键字段直接绑定该对象 publish_obj = models.Publish.objects.filter(pk=3).first() models.Book.objects.create(title='水浒传', price=77.77, publish=publish_obj) main()
针对多对多关系的增、删、改、查
增加绑定关系:
add() 括号里面可以是一个或多个位置参数,支持数字和对象
"""针对多对多关系绑定的两种方式""" # 方式1:直接添加关系表中的id值 book_obj1 = models.Book.objects.filter(pk=1).first() book_obj1.authors.add(1) # 在第三张关系表中给当前书籍绑定作者 book_obj1.authors.add(2,3) # 支持一次性绑定多个 # 方式2:先获取关联表中要绑定的数据对象,添加该对象 book_obj2 = models.Book.objects.filter(pk=3).first() author_obj1 = models.Author.objects.filter(pk=2).first() # 获取作者对象1 author_obj2 = models.Author.objects.filter(pk=3).first() # 获取作者对象2 book_obj2.authors.add(author_obj1,author_obj2) # 绑定上述两个作者对象
此操作可能会报错,查看id是否在数据修改的时候被删除,其他错误可以参考博客:https://www.cnblogs.com/chen-ao666/p/16985789.html
remove() 括号里面可以是一个或多个位置参数,支持数字和对象
"""删除绑定关系""" # 方式1: book_obj1.authors.remove(1) # 删除书籍对象book_obj1绑定的作者id值为1的关系 book_obj1.authors.remove(2,3) # 支持一次性删除多个 # 方式2: book_obj2.authors.remove(author_obj1,author_obj2) # 删除书籍对象book_obj2绑定的两个作者对象
修改绑定关系:
set() 注意括号里面为可迭代对象(元组 列表),支持数字和对象
# 方式1: book_obj1.authors.set((2,)) # 先删除id值不等于2的,如果没有2则添加 book_obj1.authors.set([2]) book_obj1.authors.set((3,4)) # 先删除id值不等于3或4的,如果没有3,4则添加 book_obj1.authors.set([3, 4]) # 方式2: book_obj2.authors.set((author_obj1,)) book_obj2.authors.set((author_obj1, author_obj2))
清空绑定关系:
clear()
book_obj1.authors.clear() # 清空book_obj1绑定的作者
复习MySQL跨表查询的思路
-
子查询
分步操作:将一条SQL语句用括号括起来当做另外一条SQL语句的条件
-
连表操作
先整合多张表之后基于单表查询即可
inner join 内连接
left join 左连接
right join 右连接
1.正向查询
- 正向查询按照外键字段
- 正向:外键字段在的一方查不在的一方,外键字段在谁那儿,谁查另外的人就是正向
- 如果结果可能有多个的时候就要加.all(),如果是一个则直接拿到数据对象
2.反向查询
- 反向查询按照表名小写
- 反向:没有外键字段,就是外键字段不在的一方查在的一方
- 如果结果可能有多个的话必须加
_set.all(),结果只有一个的时候不需要加
# 提示: 书籍与作者, 外键字段在书籍. 作者与作者详情, 外键字段在作者. 书籍与出版社外键字段在书籍.
ps:正反向的核心就看外键字段在不在当前数据所在的表中
# 1.查询主键为1的书籍对应的出版社名称 # 先根据条件获取数据对象 book_obj = models.Book.objects.filter(pk=1).first() # 再判断正反向的概念 由书查出版社 外键字段在书所在的表中 所以是正向查询 print(book_obj.publish.name) # 2.查询主键为3的书籍对应的作者姓名 # 先根据条件获取数据对象 book_obj = models.Book.objects.filter(pk=3).first() # 再判断正反向的概念 由书查作者 外键字段在书所在的表中 所以是正向查询 print(book_obj.authors) # app01.Author.None print(book_obj.authors.all()) print(book_obj.authors.all().values('name')) # 3.查询alex的电话号码 author_obj = models.Author.objects.filter(name='alex').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()) print(publish_obj.book_set.all().values('title')) # 5.查询alex写过的书籍 author_obj = models.Author.objects.filter(name='alex').first() print(author_obj.book_set) # app01.Book.None print(author_obj.book_set.all()) print(author_obj.book_set.all().values('title')) # 6.查询电话号码是110的作者姓名 author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first() print(author_detail_obj.author) print(author_detail_obj.author.name)
# 1.查询主键为1的书籍对应的出版社名称 res = models.Book.objects.filter(pk=1).values('publish__name') # 正向查询,按照主键publish查询,__name获取出版社名字 print(res) # 2.查询主键为3的书籍对应的作者姓名 res = models.Book.objects.filter(pk=3).values('authors__name') # 正向查询,按照主键author查询,__name获取作者姓名 print(res) # 3.查询alex的电话号码 res = models.Author.objects.filter(name='alex').values('author_detail__phone') # 正向查询,按主键author_detail print(res) # 4.查询北方出版社出版过的书籍名称和价格 res = models.Publish.objects.filter(name='北方出版社').values('book__title','book__price') # 反向查询,按照表名小写book查询,__XX获取相应的信息 print(res) # 5.查询alex写过的书籍 res = models.Author.objects.filter(name='alex').values('book__title') # 反向查询,按照表名book print(res) # 6.查询电话号码是110的作者姓名 res = models.AuthorDetail.objects.filter(phone=110).values('author__name') # 反向查询,按照表名author print(res)
# 1.查询主键为1的书籍对应的出版社名称 # 主键在书籍表中,从出版社查询为反向,按照表名小写book查询,条件写在filter()括号中,values()括号中直接填写需要的键值'name' res = models.Publish.objects.filter(book__pk=1).values('name') # 查询出版过主键为1的书籍的出版社名 print(res) # 2.查询主键为3的书籍对应的作者姓名 # 主键在书籍表中,从作者查询为反向,按照表名book查询,条件写在filter()括号中,values()括号中直接填写需要的键值'name' res = models.Author.objects.filter(book__pk=3).values('name') print(res) # 3.查询alex的电话号码 # 主键在作者表中,从电话号码查询为反向,按照表名author查询,条件写在filter()括号中,values()括号中直接填写需要的键值'phone' res = models.AuthorDetail.objects.filter(author__name='alex').values('phone') print(res) # 4.查询北方出版社出版过的书籍名称和价格 # 主键在书籍表中,从书籍查询为正向,按照外键名publish查询 res = models.Book.objects.filter(publish__name='北方出版社').values('title','price') print(res) # 5.查询alex写过的书籍 # 主键在书籍表中,从书籍查询为正向,按照外键名authors查询 res = models.Book.objects.filter(authors__name='alex').values('title') print(res) # 6.查询电话号码是110的作者姓名 # 主键在作者表中,从作者查询为正向,按照外键名author_detail查询 res = models.Author.objects.filter(author_detail__phone=110).values('name') print(res)
补充:
查询主键为3的书籍对应的作者的电话号码
# 1.从书籍表查询 # 书籍———>作者(正向按外键authors)————>详情(正向按外键author_detail) res = models.Book.objects.filter(pk=3).values('authors__author_detail__phone') print(res) # 2.从详情表查询 # 详情————>作者(反向按表名author)————>书籍(反向按表名book) res = models.AuthorDetail.objects.filter(author__book__pk=3).values('phone') print(res) # 3.从作者表查询 # 作者————>书籍(反向按表名book) # 根据书籍主键获取到作者之后,直接到详情表中获取电话号码(正向按外键author_detail) res = models.Author.objects.filter(book__pk=3).values('author_detail__phone') print(res)