django之模型层
模型层
模型层前期准备
使用django ORM要注意
-
django自带的sqlite3数据可对时间字段不敏感,有时候会展示错乱,所以我们习惯切换成常见的数据库比如MySQL。
-
django ORM并不会自动帮我们创建库,所以需要提前准备好''djangoday01''
-
id字段是自动添加的,如果想自定义主键,只需要在其中一个字段指定primary_key = True,如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
-
Django支持MySQL5.5及更高版本。
django测试某个功能层
默认不允许单独测试某个py文件,如果想要测试某个py文件,一般事测试models.py文件
测试环境1:pycharm提供的python console(临时保存,不推荐使用)
测试环境2:自己搭建(自带的test或者自己创建的py文件)
1.拷贝manage.py前四行代码
2.自己再添加两行
import django
django.setup()
代码演示:
测试tests.py
# manage.py中拷贝前四行代码
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject6.settings')
# 手写两行
import django
django.setup()
# 导入app01文件夹内models文件并 测试models文件内User运行
from app01 import models
models.User.objects.all()
models.py
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)
"""
DateField : 年月日
DateTimeField : 年月日 时分秒
两个重要参数
auto_now : 每次操作数据的时候 该字段会自动将当前时间更新
auto_now_add : 在创建数据的时候会自动将当前创建时间记录下来 之后只要不人为的修改 那么就一直不变
"""
def __str__(self):
return '用户对象:%s' % self.name
切换MySQL数据库
1.提前终端创建好库djangoday01
2.将DATABASES的配置更改
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangoday01',
'USER':'root',
'PASSWORD':'123',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}
3.连接MySQL库
4.makemigrations
5.migrate
如何查看django ORM 底层原理?
django ORM本质还是SQL语句。
1.如果有QuerySet对象,那么可以直接点query查看SQL语句
res =models.User.objects.filter(name='kimi')
print(res.query)
SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`name` = kimi
结论:有些不是QuerySet对象,就不能通过点query的形式点出来,就只能使用通过的方法
2.如果想查看所有ORM底层的SQL语句,也可以直接在配置文件添加日志记录
res1 = models.User.objects.create(name='kimi',age=16)
print(res.query) # 会报错
settings最后>>>拷贝代码放在settings
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常见关键字
基础的增删改查
方法 | 返回值 |
---|---|
create(字段名=数据) | 刚创建的数据记录对象 |
filter(筛选条件) | QuerySet列表对象 |
filter().update(修改内容) | 受影响的行数 |
filter().delete() | 受影响的行数即各表受影响的行数 |
常用的关键字
create | 描述 |
---|---|
filter | 创建数据并直接获取当前创建的数据对象 |
first/last | 根据条件筛选数据 结果是QuerySet [数据对象1,数据对象2] |
update | 拿queryset里面第一个元素/拿queryset里面最后一个元素 |
delete | 删除数据(批量删除) |
all | 查询所有数据 结果是QuerySet [数据对象1,数据对象2] |
values | 根据指定字段获取数据 结果是QuerySet [{}},{},{},{}] |
values_list | 根据指定字段获取数据 结果是QuerySet [(),(),(),()] |
distinct | 去重 数据一定要一模一样才可以 如果有主键肯定不行 |
order_by | 根据指定条件排序 默认是升序 字段前面加负号就是降序 |
get | 根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用 |
exclude | 取反操作 |
reverse | 颠倒顺序(被操作的对象必须是已经排过序的才可以) |
count | 统计结果集中数据的个数 |
exists | 判断结果集中是否含有数据 如果有则返回True 没有则返回False |
1.create 创建数据并直接获取当前创建的数据对象
res1 = models.User.objects.create(name='kimi',age=16)
print(res1)
res2 = models.User.objects.create(name='rose', age=18)
print(res2)
res3 = models.User.objects.create(name='jason', age=20)
print(res3)
res4 = models.User.objects.create(name='kiki', age=27)
print(res4)
2.filter() 根据条件筛选数据 结果是QuerySet [数据对象1,数据对象2]
res5 =models.User.objects.filter()
res6 =models.User.objects.filter(name='kimi')
res7 =models.User.objects.filter(name='kimi',age=16 )
print(res5)
print(res6) # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>]>
print(res7) # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>]>
3.first()/last() QuerySet支持索引取值但是只支持正数 并且orm不建议你使用索引
res8 =models.User.objects.filter()[1]
# res9=models.User.objects.filter(pk=100)[0] # 数据不存在索引取值会报错
res10 =models.User.objects.filter(pk=100).first() # 数据不存在不会报错而是返回None
res11 =models.User.objects.filter(pk=100).last() # 数据不存在不会报错而是返回None
print(res8) # 用户对象:kimi
# print(res9)
print(res10) # None
print(res11) # None
4.update() 更新数据(批量更新)
models.User.objects.filter().update() # 批量更新
models.User.objects.filter(id=1).update(9) # 单个删除
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 [{}},{},{},{}]
values()res=models.User.objects.all().values('name') #结果是QuerySet [{'name': 'kimi'}},{'name': 'rose'},{},{}]
res=models.User.objects.filter().values() #结果是QuerySet [[{'id': 2, 'name': 'kimi', 'age': 16,.....}]
res=models.User.objects.values() # <QuerySet [{'id': 2, 'name': 'kimi', 'age': 16,
8.values_list() 根据指定字段获取数据 结果是QuerySet [(),(),(),()]
res=models.User.objects.all().values_list('name','age') # <QuerySet [('kimi', 16), ('rose', 18), ('jason', 20), ('kiki', 27),...]
9.distinct() 去重 数据一定要一模一样才可以 如果有主键肯定不行
res = models.User.objects.values('name','age').distinct()
10.order_by() 根据指定条件排序 默认是升序 字段前面加负号就是降序
res = models.User.objects.all().order_by('age')
11.get() 根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用
res = models.User.objects.get(pk=1)
print(res) # 用户对象:kimi
#res = models.User.objects.get(pk=100, name='kimi')
# print(res) # 报错
12.exclude() 取反操作
res = models.User.objects.exclude(pk=2)
print(res) #<QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>
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() 统计结果集中数据的个数
res1 =models.User.objects.all().count()
print(res1) # 4
15.exists() 判断结果集中是否含有数据 如果有则返回True 没有则返回False
# res=models.User.objects.all().exisrt() 报错
res1 = models.User.objects.filter(pk=100).exists()
print(res1) # False
基础方法总结
1、返回QuerySet对象的方法有(大多通过模型类.objects.方法调用)
QuerySet对象形似存储了一个个记录对象的列表,但拥有一些特殊的属性,如query。
方法 | 含义 | 补充 |
---|---|---|
all() | 拿到表中所有记录 | |
filter() | 拿到表中所有符合条件的记录 | 括号中需要写筛选条件 |
exclude() | 拿到表中所有不符合条件的记录 | 括号中需要写筛选条件 |
order_by() | 按照某个字段进行排序 | 括号中需要写字段名称 |
reverse() | 将排序好的字段反转排序 | 只能对排过序的QuerySet对象操作 |
distinct() | 对QuerySet对象进行去重 | 一般搭配value(*field)使用,传入筛选过字段的记录集合QuerySet对象 |
raw(sql) | 执行原生的sql语句 | 注意实际的表名与模型表的类名区别 |
2.特殊的QuerySet
方法 | 含义 | 返回值 |
---|---|---|
values() | 筛选字段 | <QuerySet[{},{},{}]>内部是字典 |
values_list() | 筛选字段 | <QuerySet[(),(),()]>内部是元组 |
3.返回具体对象的
方法 | 含义 | 返回值 |
---|---|---|
create(**kwargs) | 向表中插入记录表 | 刚创建的记录对象 |
QuerySet对象.first() | 拿到结果的第一个记录 | 如果没有返回None |
QuerySet对象.last() | 拿到结果的最后一个记录 | 如果没有返回None |
QuerySet对象.get(筛选条件) | 拿到结果中符合条件的记录 | 如果没有符合条件则报错 |
4.返回布尔值的方法有
exists()
5.返回数字的方法有count()
count()
ORM执行SQL语言
有时候ORM的操作可能偏低,我们可以自己编写SQL语句。
方式一
res=models.User.objects.raw('select * from app01_user')
print(res) #<RawQuerySet: select * from app01_user;>
方式二
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursorfetchall())
终端打印
# (('kimi',), ('rose',), ('jason',), ('kiki',), ('ab',), ('ab',), ('ab',))
神奇的双下划线
结果对象还是query对象就可以无限制的点queryset对象的方法。
queryset.filter().values().filter().values_list().filter()....
数据准备
django中将字段后加上__条件的方式让关键字参数拥有除等号外的其他含义。
数据查询(双下划线)
__gt | 大于 |
---|---|
__lt | 小于 |
__gte | 大于等于 |
__lte | 小于等于 |
__in | 类似于成员运算,在...里面 |
__range | 在什么范围之内 |
__contains | 是否含有,区分大小写 ,模糊查询 |
__icontains | 是否含有,不区分大小写 ,模糊查询 |
__year | 查询年份 |
__day | 查询日期天数 |
__second/minute | 查看秒/分 |
双下划线小测试
1.查询年龄大于18的用户数据
res = models.User.objects.filter(age__gte=18)
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:jerry>]>
2.查询年龄小于38的用户数据
res = models.User.objects.filter(age__lte=38)
print(res) # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>
3.大于等于38的用户数据
res = models.User.objects.filter(age__gte=18)
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:jerry>]>
4.小于等于38的用户数据
res = models.User.objects.filter(age__lte=38)
print(res) # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>
5.查询年龄是18或者20或者38的数据
res = models.User.objects.filter(age__in=(18,20,38))
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>
6.查询年龄在18到38范围之间的用户数据
res = models.User.objects.filter(age__range=(18,38))
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>
7.查询名字中含有字母j的用户数据
1.区分大小写
res = models.User.objects.filter(name__contains='j')
print(res) # <QuerySet [<User: 用户对象:jason>, <User: 用户对象:jerry>]>
2.不区分大小写
res = models.User.objects.filter(name__icontains='j')
print(res) # <QuerySet [<User: 用户对象:jason>, <User: 用户对象:jerry>, <User: 用户对象:Jeny>]>
8.查询注册年份是2022的数据
res = models.User.objects.filter(register_time__year=2022)
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:Jeny>]>
多表操作
ORM外键字段的创建
跟MySQL外键关系一样判断规律
1.一对多 外键字段建在多的一方
2.多对多 外键字段统一建在第三张关系表
3.一对一 建在任何一方都可以,但是建议建在查询频率较高的表中
注意:目前关系的判断可以采用表与表之间换位思考原则
基础表的准备
-
创建基础表(书籍表、出版社表、作者表、作者详情)
-
确定外键关系
一对一 ORM与MySQL一致 外键字段建在查询较高的一方
一对多 ORM与MySQL一致 外键建在多的一方
多对多 ORM比MySQL有更多的变化
1.外键字段可以直接建在某张表中(查询频率较高的)
内部会自动帮你创建第三张关系表
2.自己创建第三张关系表并创建外键字段
后续讲解
- ORM创建
针对一对多和一对一同步到表中之后自动_id的后缀,如book中的外键字段publish,会自动变成publish_id。
1.一对多关系
publish= models.ForeignKey(to='Publish',on_delete=models.CASCADE)
在多的表中建立外键字段,会在表中产生一个实际的字段(自动加_id后缀)
2.一对一
author_detail = models.OneToField(to='AuthorDetail',on_delete=models.CASCADE)
在查询频率较高的表中建立外键字段,会在表中产生一个实际的字段(自动加入_id后缀)
针对多对多,不会在表中有展示,而是自动创建第三张表
1.多对多
authors=models.ManyToManyField(to='Author')
在查询频率较高的表中建立外键字段(ORM自动创建的,也可自己创建),不会在表中产生实际的字段,而是告诉ORM创建第三张关系表。
模型表创建一对一、一对多和多对多的实例
需要注意的是:
1.创建一对多关系
和sql语句一样,外键建立到多的那张表上,不同的是,我们可以不讲究关联表和被关联表的建立顺序。字段类为ForeignKey
在django2.x版本以上,建立一对多关系时需要指定on_delete参数为CASCADE,不加会报错,不过也不一定就是CASCADE,可能为其他实参,这里不展开。
建立外键时,系统会自动加上_id后缀作为字段名。
2.创建多对多关系
sql中是将两张表建立好后,将外键字段创建在第三张表中,而django为我们省去了这一步骤,我们可以在多对多关系双方的一个模型表中直接建立一个虚拟外键,ManyToManyField
在底层,sql依旧创建了第三张表来存储两表的多对多关系,但是在orm操作中我们就可以将模型表中的外键当做实实在在的联系,因为在查询时,我们感受不到第三张的表的存在。
多对多关系的外键没有on_delete关键字参数。
3.创建一对多关系
一对一的字段类为OneToOneField,建议建立在查询频率高的一方。
建立一对一关系时需要指定on_delete参数,否则报错。
多对多三种创建方法的补充
注意:多对多关系这种虚拟外键才有add、set、clear、remove,一对一和一对多的表是无法使用的
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.ForeigKey(to='Author')
others=models.CharField(max_length=32)
join_time = models.DataField(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')# 外键在哪个表就把book表放前面
)
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
外键字段的相关操作
数据的创建
基本数据:提前将Publish,author以及authordetail三个表的数据信息录入,Book以及关系的绑定在下面详细介绍
一对多和一对一实际外键字段的绑定
1.外键关联的实际字段
针对一对多,插入数据可以直接填写表中的实际字段
models.BooK.objects.create(title='茶花女',price='888.66',publish_id=1)
models.BooK.objects.create(title='红楼梦',price='666.56',publish_id=1)
models.BooK.objects.create(title='西游记',price='555.34',publish_id=2)
models.BooK.objects.create(title='三国演义',price='222.98',publish_id=3)
2.外键的关联对象
针对一对多,插入数据也可以填写表中的类中字段名
publish_obj = models.Publish.objects.filter(pk=1).first()
models.BooK.objects.create(title='水浒传',price=999.05,publish=publish_obj)
3.一对一与一对多插入数据的方式是一致的
关于多对多关系外键字段的绑定
多对多外键属于实际不在模型表中的虚拟字段,多对多关系则需要django提供给我们的方法来实现增删改关系。拿到设立多对多外键的模型表的对象,用它点出外键属性,可以进行add、set、remove方法,这些方法都是这条记录对象的操作。
数据的增加add
语法:book_obj.authors.add()
# 对象.外键.add()
add可以通过关联的id或者关联的对象进行绑定关系
book_obj = models.BooK.objects.filter(pk=1).first()
1.书与作者一对一绑定
book_obj.authors.add(1) # 在第三张关系表中给当前书籍绑定作者
2.书与作者一对多绑定
book_obj.authors.add(2,3)
3.作者对象与书对象的绑定
book_obj = models.BooK.objects.filter(pk=4).first()
author_obj1 =models.Author.objects.filter(pk=2).first()
author_obj2 =models.Author.objects.filter(pk=3).first()
# book_obj.authors.add(author_obj1) # 可以添加一个作者对象
book_obj.authors.add(author_obj1,author_obj2) # 也可同时添加两个作者对象
总结:add(1) add(1,2) add(obj1) add(obj1,obj2)
数据的修改add和set**
语法:book_obj.authors.set()
# 对象.外键.set()
set可以通过关联的id或者关联的对象进行修改绑定关系
4.绑定错误,如何修改使用set修改关系
""" 通过id修改的"""
book_obj = models.BooK.objects.filter(pk=4).first()
book_obj.authors.set((1,3)) # set括号里面只能填写一个可跌倒对象()/[]或者对象
""" 原本id=4的书籍绑定的是作者2和作者3,通过set修改数据信息后绑定的是作者1和作者3"""
book_obj.authors.set([2,4])
"""通过对象修改的"""
book_obj = models.BooK.objects.filter(pk=2).first()
book_obj.authors.add(1,2,4)
""" id=2的书绑定了作者1,作者2和作者4"""
book_obj = models.BooK.objects.filter(pk=2).first()
author_obj1=models.Author.objects.filter(pk=1).first()
author_obj2=models.Author.objects.filter(pk=2).first()
author_obj4=models.Author.objects.filter(pk=4).first()
book_obj.authors.set((author_obj1,author_obj2))
""" id=2的书由绑定的作者1,作者2和作者4修改为作者1和作者4"""
book_obj.authors.set((author_obj1,author_obj2,author_obj4))
""" 通过修改,id=2的书还是绑定了作者1,作者2和作者4"""
总结set((1,)) set((1,2)) set((obj1,)) set((obj1,obj2))
数据的删除remove
语法:book_obj.authors.remove()
# 对象.外键.remove()
remove可以通过关联的id或者关联的对象进行移除绑定关系
5.数据的删除
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()
""" 通过id去删除"""
book_obj.authors.remove(2) # 作者2
#book_obj.authors.remove(1,3)
""" 通过作者对象去删除"""
book_obj.authors.remove(author_obj1) # 作者1
#book_obj.authors.remove(author_obj1,author_obj2)
总结:remove(1) remove(1,2) remove(obj1) remove(obj1,obj2)
add()\remove() 多个位置参数(数字 对象)
set() 可迭代对象(元组 列表) 数字 对象
clear() 情况当前数据对象的关系 ,不需要传参数
数据的清空 clear
语法:book_obj.authors.clear()
# 对象.外键.clear()
clear() 直接清空与book的id为1关联的所有作者
""" 清空主键为1的绑定关系"""
book_obj = models.BooK.objects.filter(pk=1).first()
book_obj.authors.clear()
ORM跨表查询
MySQL跨表查询的思路
1.子查询
分步操作:将一条SQL语句用括号括起来当做另外一条SQL语句的条件
2.连表操作
先整合多张表之后基于单表查询即可
inner join 内连接
left join 左连接
right join 右连接
ORM跨表查询****的思路
正反向查询的概念(重要)
1.正向查询
由外键字段所在的表数据查询关联的表数据 正向
2.反向查询
没有外键字段的表数据查询关联的表数据 反向
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()) # <QuerySet [<Author: Author object (1)>, <Author: Author object (3)>]>
print(book_obj.authors.all().values('name')) # <QuerySet [{'name': 'kiki'}, {'name': 'rose'}]>
res =book_obj.authors.all().values('name')
for i in res:
# print(i)
print(i.get('name'))
3.查询kimi的电话号码
author_obj = models.Author.objects.filter(name='kimi').first()
print(author_obj.author_detail.phone) # 120
反向查询
4.查询人民出版社出版过的书籍
publish_obj = models.Publish.objects.filter(name='人民出版社').first()
print(publish_obj.book_set) # app01.BooK.None
print(publish_obj.book_set.all()) # <QuerySet [<BooK: BooK object (1)>, <BooK: BooK object (2)>, <BooK: BooK object (5)>]>
print(publish_obj.book_set.all().values('title')) # <QuerySet [{'title': '茶花女'}, {'title': '红楼梦'}, {'title': '水浒传'}]>
5.查询kimi写过的书籍
author_obj = models.Author.objects.filter(name='kimi').first()
print(author_obj.book_set) # app01.BooK.None
print(author_obj.book_set.all().values('title')) # <QuerySet [{'title': '红楼梦'}]>
6.查询电话号码是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first()
print(author_detail_obj.author) # Author object (1)
print(author_detail_obj.author.name) # kiki
总结方法
正向查询:数据对象可以点出它有的外键字段的名称属性
反向查询:数据对象可以点出关联它的表名小写_set的名称属性
基于上下划线的跨表查询
正向查询
1.查询主键为1的书籍对应的出版社名称
res = models.BooK.objects.filter(pk=1).values('publish__name','title')
print(res) # <QuerySet [{'publish__name': '人民出版社', 'title': '茶花女'}]>
2.查询主键为4的书籍对应的作者姓名
res1 = models.BooK.objects.filter(pk=4).values('authors__name','title')
print(res1) # <QuerySet [{'authors__name': 'kiki', 'title': '三国演义'}, {'authors__name': 'rose', 'title': '
3.查询kimi的电话号码
res2 = models.Author.objects.filter(name='kimi').values('author_detail__phone','name')
print(res2) # <QuerySet [{'author_detail__phone': 120, 'name': 'kimi'}]>
反向查询
4.查询人民出版社出版过的书籍和价格
res3= models.Publish.objects.filter(name='人民出版社').values('book__title','book__price')
print(res3) # <QuerySet [{'book__title': '茶花女', 'book__price': Decimal('888.66')}, {'book__title': '红楼梦', 'book__price': Decimal('666.56')}, {'book__title': '水浒传', 'book__price': Decimal('999.05')}]>
5.查询kimi写过的书籍
res4 = models.Author.objects.filter(name='kimi').values('book__title','name')
print(res4) # <QuerySet [{'book__title': '红楼梦', 'name': 'kimi'}]>
6.查询电话号码是110的作者姓名
res5 = models.AuthorDetail.objects.filter(phone=110).values('author__name','phone')
print(res5) # <QuerySet [{'author__name': 'kiki', 'phone': 110}]>
进阶操作
两个表
""" 前提是前面的条件不能使用,只能使用后面的条件"""
1.查询主键为1的书籍对应的出版社名称
res=models.Publish.objects.filter(book__pk=1).values('name')
print(res) #<QuerySet [{'name': '人民出版社'}]>
2.查询主键为4的书籍对应的作者姓名
res1 = models.Author.objects.filter(book__pk=4).values('name')
print(res1) #<QuerySet [{'name': 'kiki'}, {'name': 'rose'}]>
3.查询kimi的电话号码
res2 = models.AuthorDetail.objects.filter(author__name='kimi').values('phone')
print(res2) # <QuerySet [{'phone': 120}]>
4.查询人民出版社出版过的书籍和价格
res3=models.BooK.objects.filter(publish__name='人民出版社').values('title','price')
print(res3) # <QuerySet [{'title': '茶花女', 'price': Decimal('888.66')}, {'title': '红楼梦', 'price': Decimal('666.56')}, {'title': '水浒传', 'price': Decimal('999.05')}]>
5.查询kimi写过的书籍
res4 = models.BooK.objects.filter(authors__name='kimi').values('title')
print(res4) # <QuerySet [{'title': '红楼梦'}]>
6 查询电话号码是110的作者姓名
res5 = models.Author.objects.filter(author_detail__phone=110).values('name')
print(res5) # <QuerySet [{'name': 'kiki'}]>
三个表
1.查询主键为4的书籍对应的作者的电话号码
res = models.BooK.objects.filter(pk=4).values('authors__author_detail__phone')
print(res) # <QuerySet [{'authors__author_detail__phone': 110}, {'authors__author_detail__phone': 119}]>
"""1.拿到主键为4的book对象
2.书与作者是多对多关系,从书到作者是正向查询,使用外键authors,进入了author表
3.author表与作者详情表是一对多的关系,从作者表到作者详情表是正向查询,使用外键author_detail,进入作者详情表
4.在作者详情表使用表中phone获取号码 """
res1 =models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res1) # <QuerySet [{'author_detail__phone': 110}, {'author_detail__phone': 119}]>
"""1.拿到author作者对象
2.书与作者是多对多关系,从作者到书是反向查询,使用表名小写book,知道主键为4,可以拿到主键为4的书名
3.author表与作者详情表是一对多的关系,从作者表到作者详情表是正向查询,使用外键author_detail,进入作者详情表
4.在作者详情表使用表中phone获取号码 """
res2=models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res2) # <QuerySet [{'phone': 110}, {'phone': 119}]>
"""1.拿到AuthorDetail作者详情表对象,作者与作者详情表有关系,作者与书有关系,
2.作者与作者详情表是一对一关系,从作者详情表到作者是反向查询,使用表名小写author,知道主键为4,可以拿到主键为4的书名
3.在作者详情表使用表中phone获取号码 ,"""
聚合查询
聚合函数:Max Min Sum Count Avg
函数名 | 描述 |
---|---|
Max | 大于 |
Min | 小于 |
Sum | 求和 |
Count | 统计某个数据 |
Avg | 平均值 |
如果我们在ORM中使用聚合函数,ORM支持单独使用聚合函数,步骤如下:
1.引入模块
from django.db.models import Max,Min,Sum,Count,Avg
2.使用关键字aggregate
res = models.Book.objects.aggregate(Max('xx'),Min('xx'),最小价格=Sum('xx'),拼averge=Avg('xxx')) # 建议取名要英文,不要用中文
eg:
from django.db.models import Max,Min,Sum,Count,Avg
res= models.BooK.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
print(res) # {'price__max': Decimal('999.05'), 'price__min': Decimal('222.98'), 'price__sum': Decimal('3332.59'), 'pk__count': 5, 'price__avg': Decimal('666.518000')}
分组查询
引入
如果执行ORM分组查询报错,并且又关键sql_mode / strict mode ,那么就去移除sql_mode中的only_full_group_by
- 统计每一本的作者个数
res = models.BooK.objects.annotate(author_num=Count('authors__pk')).values('title','author_num')
print(res) # <QuerySet [{'title': '茶花女', 'author_num': 1}, {'title': '红楼梦', 'author_num': 3}, {'title': '西游记', 'author_num': 0}, {'title': '三国演义', 'author_num': 2}, {'title': '水浒传', 'author_num': 0}]>
- 统计出每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
print(res) # <QuerySet [{'name': '人民出版社', 'min_price': Decimal('666.56')}, {'name': '商务印书馆', 'min_price': Decimal('555.34')}, {'name': '上海译文出版社', 'min_price': Decimal('222.98')}, {'name': '国家图书馆出版社', 'min_price': None}]>
- 统计不止一个作者的图书
1.先统计每本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk'))
2.筛选出作者个数大于1的数据
res = models.BooK.objects.annotate(author_num =Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')
print(res) # <QuerySet [{'title': '红楼梦', 'author_num': 3}, {'title': '三国演义', 'author_num': 2}]>
- 查询每个作者出的书的总价格
res=models.Author.objects.annotate(book_sum=Sum('book__price')).values('name','book_sum')
print(res) # <QuerySet [{'name': 'rose', 'book_sum': Decimal('1111.64')}, {'name': 'kiki', 'book_sum': Decimal('889.54')}, {'name': 'kimi', 'book_sum': Decimal('666.56')}, {'name': 'jerry', 'book_sum': Decimal('666.56')}]>
上述分组都是按照表来分组,我们也可以按照表中的字段名来分组
1.按照表分组
models.表名.objects.annotate()
2.按照表中字段名来分组
models.表名.objects.values('字段名').annotate()
eg:
res= models.Book.objects.values('publish_id').annotate(count_pk=Count('pk')).values('publish_id','count_pk')
print(res)
注意:values在annotate前就是按照values()括号里面字段名来分组;values()在annotate后就是按照前面的表名分组,values就是拿值的
F与Q查询
注:字段中填写了两个参数,一个是null,即允许为空(默认情况下为非空),一个是default默认值,即在记录中这个字段如果没填写则自动填充的值。如果不设置这两个参数,那么在添加这两个字段的时候,因为表中已经有数据了,所以原本记录的这两个字段即要求非空又没有默认值就无法迁移。然后,涉及到我们的数据迁移,需要执行那两条命令makemigrations migrate。
数据添加,首先在Book表中添加两个字段库存数和卖出数。但是由于我们前面就已经数据迁移过了,那么如何处理呢?
"""后续添加两个字段,必须要加入默认值"""
inventory_number=models.IntegerField(verbose_name='库存数',default=1000)
number_sold = models.IntegerField(verbose_name='售出数',null=True)
将售出数添加后方便后续操作,如下展示
F查询
当查询条件不是很明确的,也需要从数据库中获取,就需要使用F查询。
简单理解:
- 两个字段进行比较的筛选条件(库存数大于卖出数),
- 在原来的数值字段增加数值(888),
- 在原来的字段名后面加字(+爆款)
在上述条件等,我们只借助ORM操作,是实现不了的,我们需要在ORM中就需要借助F方法。
1.查询库存数大于卖出数的书籍
from django.db.models import F
res = models.BooK.objects.filter(inventory_number__gt=F('number_sold')).values('title')
print(res) # <QuerySet [{'title': '红楼梦'}, {'title': '西游记'}]>
2.将所有书的价格涨800
from django.db.models import F
res=models.BooK.objects.update(price=F('price')+800)
print(res) # 5
3.将所有书的名称后面追加爆款
from django.db.models.functions import Concat
from django.db.models import Value
models.BooK.objects.update(title=Concat(F('title'),Value('爆款')))
Q查询
在ORM操作中,筛选条件中存在或、非的关系需要借助Q方法来实现。
符号 | 描述 |
---|---|
,(逗号) | and的关系 |
| | or的关系 |
~ | not的关系 |
1.查询主键是1或者价格大于2000的书籍
1.常用的查询方法
res = models.BooK.objects.filter(pk=1,price__gt=2000).values('title') # 逗号默认是and关系
print(res) # <QuerySet [{'title': '茶花女爆款'}]>
2.逗号是and
from django.db.models import Q
res = models.BooK.objects.filter(Q(pk=1),Q(price__gt=2000)).values('title') # 逗号是and
print(res) # <QuerySet [{'title': '茶花女爆款'}]>
3.|是or
res1 = models.BooK.objects.filter(Q(pk=1) | Q(price__gt=2000)).values('title') # |是or
print(res1) # <QuerySet [{'title': '茶花女爆款'}, {'title': '红楼梦爆款'}, {'title': '西游记爆款'}, {'title': '水浒传爆款'}]>
4.~是not
res2 = models.BooK.objects.filter(~Q(pk=1 ) | Q(price__gt=2000)).values('title') # ~是not
print(res2) # <QuerySet [{'title': '茶花女爆款'}, {'title': '红楼梦爆款'}, {'title': '西游记爆款'}, {'title': '三国演义爆款'}, {'title': '水浒传爆款'}]>
Q方法使用总结:
- 两个条件是或关系Q(条件1) | Q(条件2)
- 两个条件是非关系~Q(条件)
Q查询进阶操作
from django.db.models import Q
q_obj = Q() # 1.产生一个Q对象
q_obj.connector ='' # 默认是多个条件的连接时and
q_obj.connector ='or' # 可以修改为or
q_obj.children.append(('pk',1)) # 2.添加查询条件
q_obj.children.append(('price__gt',2000)) # 支持添加多个
res = models.Book.objects.filter(q_obj) # 查询支持直接填写Q对象
查询条件成为'字符串',也就意味着,可以让用户交互,进行筛选条件的选择与添加。
ORM查询优化
-
ORM的查询默认都是惰性查询
-
ORM的查询自带分页处理
将来如果自己做SQL,最好要加上LIMIT分页
-
only与defer
# res = models.Book.objects.all() # 获取所有的[数据对象1,],不走sql语句
只想拿某些字段的话,使用value,但是value拿到的是[{},{},{}]。只有only可以实现
1.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查询
总结:only会将括号内填写的字段封装到数据对象中,后续获取不走SQL,但是获取括号内没有的字段数据则需要走SQL
2.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查询
总结:defer会将括号内填写的字段封装到数据对象中,后续获取有字段需要走SQL,但是获取括号内没有的字段数据则不需要走SQL
4.select_related与prefetch_related
括号内都是只支持一对一和一对多外键字段,多对多外键字段是不支持的。
1.select_related 连表操作
# res = models.Book.objects.all()
# for obj in res:
# print(obj.publish.name) # 每次查询都需要走SQL
""" 通过点book外的表,每次查询都会走SQL,如果不想走SQL,可以将两个表连起来后再查询不走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查询
""" select_related括号内填写一对多、一对一字段 自动连表然后继续数据封装"""
2.prefetch_related 子查询
res = models.Book.objects.prefetch_related('publish') # 子查询
for obj in res:
print(obj.publish.name)
""" prefetch_related括号内填写一对多、一对一字段 基于子查询然后封装数据 """
ORM事务操作
引入事务
1.事务的四大特性
原子性、一致性、隔离性、持久性
2.相关SQL关键字
start transaction;
rollback;
commit;
savapoint;
3.相关重要概念
脏读、幻读、不可重复读、MVCC多版本控制
django orm提供了至少三种开启事务的方式
方式一:全局开启事务
配置文件数据库(DATABASE)相关添加键值对 全局有效
"ATOMIC_REQUESTS":True每次请求所涉及到的orm操作同属于一个事务
如果过程中报错了,就会往回滚
方式二:装饰器 局部有效
from django.db import transaction # 引入事务模块
@transaction.atomic # 原子装饰器
def index():pass
eg:
from django.db import transaction
@transaction.atomic
def index():
model.book.objects.create(title='简爱',price='345',publish_id=1)
hhshhsldlllds # 报错>>>就会过滚
return HttpResponse('添加成功')
return 123 #在事务里面,这也算成功
装饰器针对的是视图函数,当视图函数需要设计ORM操作,如果函数从上往下执行,遇到报错会自动回滚到视图函数开始的状态。
方式三:with 上下文管理 局部有效
from django.db import transaction
def reg():
with transaction.atomic():
pass # 这下面看作是同一个事务处理,遇到报错就会回滚
ORM常用字段类型
1.AutoField(primary_key) # 字段才用,可以让它自动创建
2.CharField(max_length) # 对应varchar字段,存储有限的字符
3.IntegerField # 整型
4.BigIntergerField #整型(比如手机号11位)
5.DecimalField(max_digits,decimal_places) # 小数字段
6.DateField(auto_now,aoto_now_add) # 日期(年月日)
7.DateTimeField(auto_now auto_now_add) #日期(年月日时分秒)
8.Booleanfiels:传布尔值自动存0(False)或者1(True)
9.TextField:存储大段文本
10.EmailField:存储邮箱格式数据
11.FileField:传文件对象,自动保存到提前配置好的路径下并存储该路径信息
12.ForeignKeyField(to='',on_delete) 实际外键字段,建立一对一关系
13.OneToOneField(to='',on_delete) 实际外键字段,建立一对多关系
14.ManyToManyField(to='') 虚拟外键字段,建立多对多关系
需要说明的是,这些orm字段并非和sql字段一一对应,有些是封装了一些逻辑功能在字段的创建、存储过程中的。
ORM还支持用户自定义字段类型
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常用字段参数
1.primary_key 主键字段
2.verbose_name 字段注释
3.max_length 字段长度
4.max_digits 小数总共多少位
5.decimal_places 小数点后面的位数
6.auto_now 每次操作数据自动更新事件
7.auto_now_add 首次创建自动更新事件后续不自动更新
在时间相关字段的独有参数,设置为True则会自动执行相关功能。
8.null 允许字段为空
9.default 字段默认值
10.unique 唯一值
11.db_index 给字段添加索引
12.choices 当某个字段的可能性能够被列举完全的情况下使用
13.to/to_field/on_delete
eg:性别、学历、工作状态...
class User(models.Model):
name=models.CharField(max_length=32)
info= MyCharField(max_length=64)
# 提前列举好对应关系
gender_choice=(
(1,'男性'),
(2,'女性'),
(3,'其他'),
)
gender = models.IntergerField(choices=gender_choice,null=True)
user_obj = User.objects.filter(pk=1).first() # 拿到一个对象
user_obj.gender # 直接点显示存储的真实数据
user_obj.get_gender_display() # 通过这个方法拿显示转义后的选项
外键相关参数
to 关联表
to_field 关联字段(不写默认关联数据主键)
on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。(只针对一对多和一对一关系)
on_delete()里面相关参数
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相似
ps:主表一般是指在一对一、一对多中一的那一方,当对其进行删除时,另外一张表的所有元素都应该受到影响
练习题建议总结:
- 如果要更改模型表中的书籍on_delete()里面的参数
比如把on_delete=models.CASCADE更改为on_delete=models.SET_NULL,除了更改参数外,要提前给null=True,因为你在添加book书籍时候,没用绑定外键关联的表时,自动设置为null
2.在建立模型表时,on_delete属性设置成CASCADE,那你删除出版社就直接把所有的书籍删除
3.一对一、一对多的关系都是实际字段,如果想单方面解除关系,直接对实际的外键字段增删改就行了
4.所有上述的关于多对多关系的操作(add,set,remove,clear)都是基于第三张表是ORM自动创建的