第七篇:Django之模型层
第七篇:Django之模型层
一、单表查询(增删改查)
模型层(ORM语法):是直接跟数据库打交道的,十分重要,学习的时候可以对比SQL语句,进行对比学习。ORM语法最后本质上都是要转化成SQL语法的。
django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错,所以我们使用mysql数据库进行数据存储和处理。
1、测试脚本
当只是要测试django中的某一个py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。脚本代码无论是在应用下的tests.py文件下,还是自己单独开设py文件都可以。
测试环境配置代码如下,如此一来,我们便可以在在test.py中进行测试,不必像之前那样大费周章。
# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
import django
django.setup()
# 注意,之后测试的代码必须写在下面,到了这里,配置环境才准备完毕
...
2、单表测试操作
我们先使用navicat创建一个数据库day64,然后配置django中的数据库数据,之后在models.py文件中书写如下代码。
from django.db import models
# Create your models here.
# 用户表
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
register_time = models.DateField() # 年月日:在这里我们自定义,而不采用自动传数值的方式
"""
DateField : 年月日
DateTimeField : 年月日时分秒
两个重要参数
auto_now:每次操作数据的时候 该字段会自动将当前时间更新
auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
"""
# 当打印数据对象时,调用该方法,打印对象的name字段
def __str__(self):
return '对象:{}'.format(self.name)
然后使用python3 manage.py makemigrations
命令和python3 manage.py migrate
命令,创建表。
- 增加
"""第一种方式"""
res = models.User.objects.create(name='yangyi', age=18, register_time='2021-6-29') # create方法会返回一个对象,为创建的数据对象
print(res) # 对象:yangyi
"""第二种方法"""
import datetime
current_time = datetime.datetime.now()
# 可以直接传递一个事件对象,即便格式看起来并不匹配
user_obj = models.User(name='leichao', age=24, register_time=current_time)
user_obj.save()
print(current_time) # 2021-06-29 13:44:47.769389
"""时间数据
数据库 ----直接取到---> 前端页面 ---需要使用过滤器---> {{ user_obj.register_time|date:'Y-m-d h:i:s' }}
2021-08-22 11:35:58.404072 Aug. 22, 2021, 11:35 a.m. 2021-08-22 11:35:58
"""
我们发现我们在数据库中创建了一条数据。
我们使用第二种方法创建的数据。
-
删除
补充:pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段,用了pk之后, 你就不需要指代当前表的主键字段到底叫什么了。【可能有的主键值叫 uid、pid、sid、...】
"""第一种方式"""
res = models.User.objects.filter(pk=2).delete() # 批量删除
print(res) # 返回删除的数据的个数 (1, {'app01.User': 1}),并不是被删除的数据对象
"""第二种方式"""
user_obj = models.User.objects.filter(pk=1).first() # 指名道姓,拿到确定的数据对象
user_obj.delete()
"""补充,细品"""
print(models.User.objects.filter(pk=1))
# 拿到一个QuerySet:查询集,格式类似于列表【可通过索引取值 .first()】,但是比列表多一些方法
<QuerySet [<User: 对象:yangyi>]>
-
修改
我们重新建立四条数据,用于测试。
"""第一种方法"""
models.User.objects.filter(pk=4).update(name='dsg') # 批量更新
"""第二种方法"""
user_obj = models.User.objects.filter(pk=4).first() # 指名道姓
user_obj.name = 'nb'
user_obj.save()
"""补充"""
get方法返回的直接就是当前的数据对象,但是该方法不推荐使用,一旦数据不存在该方法会直接报错。
而filter则不会报错,而filter得到一个查询集。
二、常见的十几种查询方法
1、操作测试
"""纠正一下以前的问题,User只是一个类,User.objects是一个表"""
# 1、all() 查询所有数据
print(models.User.objects)
app01.User.objects
# 可以理解为先拿到类产生的对象,之后使用对象的方法,来获得其中的数据对象。
print(models.User.objects.all())
<QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>
# 2、filter() 带有过滤条件的查询【得到查询集】
# 3、get() 直接拿数据对象,但是条件不存在直接报错
# 4、first() 拿queryset查询集里面第一个元素
# 5、last() 拿queryset查询集里面最后一个元素
# 6、values() 可以指定获取的数据字段【列表套字典】【可以直接更在objects后面,也可以跟在查询集后面】
res = models.User.objects.values('name', 'age')
print(res) # <QuerySet [{'name': 'anan', 'age': 18}, {'name': 'nb', 'age': 24}, {'name': 'yangxin', 'age': 24}, {'name': 'gangchuan', 'age': 23}]>
print(res.first().get('name')) # anan 查询集的操作方式和列表、字典相同。
# 7、values_list() 【列表套元祖】
res = models.User.objects.values_list('name', 'age')
print(res) # <QuerySet [('anan', 18), ('nb', 24), ('yangxin', 24), ('gangchuan', 23)]>
"""
补充:只有queryset对象才能够点击query查看内部的sql语句
res = models.User.objects.values('name', 'age')
print(res.query) # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`
"""
# 8、distinct() 去重
res = models.User.objects.all().distinct()
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>, <User: 对象:nb>]>
"""
我们发现并没有去重,去重一定要是一模一样的数据,如果带有主键那么肯定不一样,往后的查询中一定不要忽略主键。
"""
res = models.User.objects.values('name', 'age').distinct()
print(res) # <QuerySet [{'name': 'anan', 'age': 18}, {'name': 'nb', 'age': 24}, {'name': 'yangxin', 'age': 24}, {'name': 'gangchuan', 'age': 23}]> 去重并不会对原数据库中的数据产生影响,只是查询时去重而已。
# 9.order_by() 排序
res = models.User.objects.order_by('age') # 默认升序
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>]>
res = models.User.objects.order_by('-age') # 降序
print(res) # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:anan>]>
# 10.reverse() 反转的前提是,数据已经排过序
res = models.User.objects.order_by('age').reverse()
print(res) # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:anan>]>
res = models.User.objects.all().reverse() # 如此没有效果
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>, <User: 对象:nb>]>
# 11.count() 统计当前数据的个数
res = models.User.objects.count()
print(res) # 5
# 12.exclude() 排除在外
res = models.User.objects.exclude(name='yangxin')
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:nb>]>
# 13.exists() 是否存在【基本用不到因为数据本身就自带隐式布尔值】
res = models.User.objects.filter(pk=10).exists()
print(res) # False
参考数据库如图所示。
2、查看内部sql语句的方式
"""方式一""" # queryset对象才能够点击query查看内部的sql语句
res = models.User.objects.values('name', 'age')
print(res.query) # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`
"""方式二""" # 所有的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',
},
}
}
# 查询方式:直接打印要测试的语句即可。
res = models.User.objects.values('name', 'age')
print(res.query) # 第一种
print(res) # 第二种
三、神奇的双下划线查询
参考数据库。
# 1、年龄大于23岁的数据
res = models.User.objects.filter(age__gt=23)
print(res) # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>]>
# 2、年龄小于23岁的数据
res = models.User.objects.filter(age__lt=23)
print(res) # <QuerySet [<User: 对象:anan>]>
# 3、年龄大于等于23岁的数据
res = models.User.objects.filter(age__gte=23)
print(res) # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>
# 4、年龄小于等于23岁的数据
res = models.User.objects.filter(age__lte=23)
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
# 5、年龄是18或23的数据
res = models.User.objects.filter(age__in=[18, 23])
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
# 6、年龄在15和23之间的数据
res = models.User.objects.filter(age__range=[15, 23]) # 首尾都包括
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
# 7、查询名字中有g的数据【模糊查询】
res = models.User.objects.filter(name__contains='g') # 区分大小写
print(res) # <QuerySet [<User: 对象:yangxin>, <User: 对象:gangchuan>]>
res = models.User.objects.filter(name__icontains='g') # 如此忽略大小写
# 8、名字中以y开头的数据
res = models.User.objects.filter(name__startswith='y')
print(res) # <QuerySet [<User: 对象:yangxin>]>
# 9、名字中以n结尾的数据
res = models.User.objects.filter(name__endswith='n')
print(res) # <QuerySet [<User: 对象:anan>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>
# 10、查询注册时间是5月的数据
res = models.User.objects.filter(register_time__month=5)
print(res) # <QuerySet [<User: 对象:yangxin>, <User: 对象:gangchuan>]>
"""register_time__year、register_time__month、register_time__day、register_time__week_day等等"""
四、外键字段的增删改查
1、一对多外键增删改查
我们先简单建立几张表,分别为图书表、出版社表、作者表、作者详情表和图书作者对象表。建表代码如下。
"""用户表"""
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
register_time = models.DateField() # 年月日:在这里我们自定义,而不采用自动传数值的方式
"""
DateField : 年月日
DateTimeField : 年月日时分秒
两个重要参数
auto_now:每次操作数据的时候 该字段会自动将当前时间更新
auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
"""
# 当打印数据对象时,调用该方法,打印对象的name字段
def __str__(self):
return '对象:{}'.format(self.name)
"""图书表"""
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
publish_date = models.DateField(auto_now_add=True)
# 外键:多对一 出版社
publish = models.ForeignKey(to='Publish')
# 外键:多对多 作者
author = models.ManyToManyField(to='Author')
"""出版社表"""
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
# varchar(254) 该字段类型不是给models看的,而是给校验性组件看的
email = models.EmailField()
"""作者表"""
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
# 外键:一对一 作者详情
author_detail = models.OneToOneField(to='AuthorDetail')
"""作者详情表"""
class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 电话号码用BigIntegerField或者直接用CharField
addr = models.CharField(max_length=64)
由此创建了五张表。
我们先给出版社表、作者详情表、作者表中添加数据【出版社表没有外键,一对一关系较为简单】。简单添加效果如下。
下面,我们执行下述操作。
- 增加
"""第一种方式"""
# 直接写实际字段 id
models.Book.objects.create(title='三国演唱',price=899.23, publish_id=1)
models.Book.objects.create(title='水许传',price=444.23,publish_id=2)
models.Book.objects.create(title='东游记',price=333.66,publish_id=1)
"""第二种方式"""
# 虚拟字段 直接传入出版社数据对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='绿楼梦', price=123.3, publish=publish_obj)
- 删除
# 级联删除
models.Publish.objects.filter(pk=2).delete() # 删除主键为2的第二个出版社
publish_obj = models.Publish.objects.filter(pk=2).first()
publish_obj.delete()
- 修改
# 把book表中主键为1的数据对象的出版社id改为2
"""第一种""" # id修改 【批量更新】
models.Book.objects.filter(pk=1).update(publish_id=2) # 可以直接修改
"""第二种""" # 对象修改
publish_obj = models.Publish.objects.filter(pk=1).first() # 拿到出版社对象
models.Book.objects.filter(pk=1).update(publish=publish_obj) # 直接将拿到的出版社对象赋值给book对象的字段
2、多对多外键增删改查
多对多,增删改查,其实就是在操作第三张表。
- 增加
# 如何给多对多关系的第三章表添加数据?【此时第三张关系表为虚拟表,并没有办法直接操作...】
"""
add给第三张关系表添加数据,括号内既可以传数字也可以传对象,并且都支持多个
"""
"""1、可以直接根据主键添加""" # 给主键为1的书添加作者关系
book_obj = models.Book.objects.filter(pk=1).first() # 拿到主键值为1的书的数据对象
print(book_obj.author) # app01.Author.None,就类似于已经拿到了第三章关系表了
book_obj.authors.add(1) # 书籍id为1的书籍绑定一个主键为1的作者
book_obj.authors.add(2,3) # 可以添加多个作者
"""2、也可以直接添加作者对象"""
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj1,author_obj2)
- 删除
"""remove:括号内既可以传数字也可以传对象,并且都支持多个"""
"""第一种"""
book_obj.authors.remove(2) # 拿到书的对象,给书移除作者
book_obj.authors.remove(1,3)
"""第二种"""
author_obj = models.Author.objects.filter(pk=2).first() # 也可以拿到要被移除的作者的数据对象,然后直接传入数据对象移除即可。
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj,author_obj1)
- 修改
"""set:括号内必须传一个可迭代对象,该对象内既可以数字也可以对象,并且都支持多个"""
"""第一种""" # 直接将关联的作者重置,可以这么理解
book_obj.authors.set([1,2])
book_obj.authors.set([3])
# 如果 book_obj.author.set(3) 则报错:TypeError: 'int' object is not iterable
"""第二种"""
author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1]) # 括号内
- 清空
"""clear:括号内不要加任何参数"""
# 括号内不要加任何参数【删除有book_obj关联的所有作者关系】
book_obj.authors.clear()
五、跨多表查询
1、正反向概念
正向:假设外键字段在我手上,那么我查你就是正向。
反向:假设外键字段如果不在手上,那么我查你就是反向。
比如:
正向:book【外键】 ----------> publish
反向:publish ----------> book【外键】
# 一对一和多对多正反向的判断也是如此
"""可以简单总结一下"""
# 正向查询按字段 如果多个 + all()
# 反向查询按表名小写 + _set 如果多个 + all() 如果只有一个 不用加 _set.all()
2、子查询(基于对象的跨表查询)
- 正向查询
# 1.查询主键为1的书籍所对应的出版社对象
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish) # Publish object 得到出版社对象
# 2.查询主键为2的书籍所关联的的作者对象
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.author) # app01.Author.None
print(book_obj.author.all()) # <QuerySet [<Author: Author object>, <Author: Author object>]>
print(book_obj.author.all().first()) # Author object【查询集这样取值】
# 3.查询作者yangyi的电话号码
author_obj = models.Author.objects.filter(name='yangyi').first()
print(author_obj.author_detail) # AuthorDetail object【如果只有一个,直接拿到对象】
print(author_obj.author_detail.phone) # 通过数据对象取电话号码
总结:
在书写orm语句的时候跟写sql语句一样的,不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点。
"""正向什么时候需要加.all()?"""
# 当你的结果可能有多个的时候就需要加.all()
# 如果是一个则直接拿到数据对象
如: book_obj.publish # 多对一,拿出来一个
book_obj.authors.all() # 多对多,可能拿出来多个,也可能拿出来一个
author_obj.author_detail # 一对一,直接拿出对象
- 反向查询
# 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>, <Book: Book object>]>
# 5.查询作者yangyi写过的所有书籍数据对象
author_obj = models.Author.objects.filter(name='yangyi').first()
print(author_obj.book_set) # app01.Book.None
print(author_obj.book_set.all()) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
# 6.查询手机号是11110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=11110).first()
print(author_detail_obj.author) # Author object【只有一个结果不用加_set.all()】
print(author_detail_obj.author.name) # yangyi
"""
基于对象
反向查询的时候
当你的查询结果可以有多个的时候 就必须加_set.all()
当你的结果只有一个的时候 不需要加_set.all()
"""
总结:
反向查询的时候,当你的查询结果可以有多个的时候,就必须加_set.all()。
但当查询的结果只有一个的时候,不需要加_set.all()。
3、联表查询(基于双下划线的跨表查询)
"""1.查询yangyi的手机号和作者姓名"""
# 正向
res = models.Author.objects.filter(name='yangyi').values('author_detail__phone','name')
print(res) # <QuerySet [{'author_detail__phone': 11110, 'name': 'yangyi'}]>
# 反向
res = models.AuthorDetail.objects.filter(author__name='yangyi') # 拿作者姓名是yangyi的作者详情
print(res) # <QuerySet [<AuthorDetail: AuthorDetail object>]>
res = models.AuthorDetail.objects.filter(author__name='yangyi').values('phone','author__name')
print(res) # <QuerySet [{'phone': 11110, 'author__name': 'yangyi'}]>
"""2.查询主键为1的书籍对象所对应的出版社名称和书的名称"""
# 正向
res = models.Book.objects.filter(pk=1).values('title','publish__name')
print(res) # <QuerySet [{'title': '三国演唱', 'publish__name': '东方出版社'}]>
# 反向
res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res) # <QuerySet [{'name': '东方出版社', 'book__title': '三国演唱'}]>
"""3.查询主键为1的书籍所对应的的作者姓名"""
# 正向
res = models.Book.objects.filter(pk=1).values('author__name')
print(res) # <QuerySet [{'author__name': 'yangyi'}, {'author__name': 'leichao'}]>
# 反向
res = models.Author.objects.filter(book__id=1).values('name')
print(res) # <QuerySet [{'name': 'yangyi'}, {'name': 'leichao'}]>
"""4.查询主键是1的书籍所对应的作者的手机号"""
# book ---> author ---> authordetail
res = models.Book.objects.filter(pk=1).values('author__author_detail__phone')
print(res) # <QuerySet [{'author__author_detail__phone': 11110}, {'author__author_detail__phone': 22220}]>
【或者】 res = models.Author.objects.filter(book__id=1).values('author_detail__phone')
总结:
"""基于双下划线的跨表查询"""
# 正向查询
通过外键 + __ 就可以跳转到与外键相连的表
# 反向查询
被关联的表,通过有外键的表的表名小写 + __ 就可以反向跳转到具有外键的表
六、查询方式
1、聚合查询【aggregate】
聚合查询通常情况下都是配合分组查询一块使用的。
from django.db.models import Max, Min, Sum, Count, Avg
"""只要是跟数据库相关的模块,基本上都在django.db.models里面,如果上述没有那么应该在django.db里面"""
# 1、查看所有的书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
res = models.Book.objects.all().aggregate(Avg('price')) # 这种方式也可以,如果是所有的数据中查找,就无需加all()。也是在查询集中进行平均。
print(res) # {'price__avg': 453.0225}
# 2、统一使用
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('pk'), Avg('price'))
print(res) # {'price__max': Decimal('899.23'), 'price__min': Decimal('123.20'), 'price__sum': Decimal('1812.09'), 'pk__count': 4, 'price__avg': 453.0225}
2、分组查询【annotate】
回顾:MySQL分组查询都有哪些特点?
分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取了,因为是严格模式,ONLY_FULL_GROUP_BY
。
# 1.统计每一本书的作者个数
"""author_num是我们自己定义的字段,用来存储统计出来的每本书对应的作者个数"""
res = models.Book.objects.annotate(author_num=Count('author')).values('title', 'author_num')
print(res) # <QuerySet [{'title': '三国演唱', 'author_num': 2}, {'title': '东游记', 'author_num': 2}, {'title': '大学', 'author_num': 1}, {'title': '中庸', 'author_num': 0}]>
# 2.统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res) # <QuerySet [{'name': '东方出版社', 'min_price': Decimal('333.66')}, {'name': '北方出版社', 'min_price': Decimal('123.20')}]>
# 3.统计不止一个作者的图书
"""1、先按照图书分组,求每一本书对应的作者个数;2.过滤出不止一个作者的图书"""
res = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('title','author_num')
print(res) # <QuerySet [{'title': '三国演唱', 'author_num': 2}, {'title': '东游记', 'author_num': 2}]>
# 4.查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res) # <QuerySet [{'name': 'yangyi', 'sum_price': Decimal('1356.09')}, {'name': 'leichao', 'sum_price': Decimal('1232.89')}]>
"""
补充: 如果我想按照指定的字段分组该如何处理呢?
models.Book.objects.values('price').annotate()
如果出现分组查询报错的情况,取消mysql数据库的严格模式即可。
"""
3、F与Q查询
-
F查询
能够帮助你直接获取到表中某个字段对应的数据。
我们给书籍表添加两个字段,分别是库存数
stock
和卖出数sale
。
# 图书表
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish_date = models.DateField(auto_now_add=True)
# 库存数
stock = models.IntegerField(default=1000)
# 卖出数
sale = models.IntegerField(default=1000)
# 外键:多对一 出版社
publish = models.ForeignKey(to='Publish')
# 外键:多对多 作者
author = models.ManyToManyField(to='Author')
然后对数据进行简单修改,效果如下。
from django.db.models import F
# 1.查询卖出数大于库存数的书籍
res = models.Book.objects.filter(sale__gt=F('stock'))
print(res) # <QuerySet [<Book: 对象:大学>]>
# 2.将所有书籍的价格提升50块
models.Book.objects.update(price=F('price') + 50)
# 3.将所有书的名称后面加上爆款两个字
"""在操作字符类型的数据的时候,F不能够直接做到字符串的拼接,数据会全部消失"""
models.Book.objects.update(title=F('title') + '爆款') # 错误
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
效果如下。
- Q查询
# 1.1.查询卖出数大于400或者价格小于600的书籍
"""
filter括号内多个参数是and关系
# 查询卖出数大于400同时价格小于600的书籍
res = models.Book.objects.filter(sale__gt=400, price__lt=600)
print(res) # <QuerySet [<Book: 对象:东游记爆款>, <Book: 对象:中庸爆款>]>
"""
rom django.db.models import Q
# 如此一来,还是and关系 【,】
res = models.Book.objects.filter(Q(sale__gt=400), Q(price__lt=600))
print(res) # <QuerySet [<Book: 对象:东游记爆款>, <Book: 对象:中庸爆款>]>
# or 关系 【|】
res = models.Book.objects.filter(Q(sale__gt=400) | Q(price__lt=600))
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
# not 关系 【~】
res = models.Book.objects.filter(~Q(sale__gt=400) | ~Q(price__lt=600))
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:大学爆款>]>
Q的高阶用法
# 可以满足用户自定义查询【能够将查询条件的左边也变成字符串的形式】
q = Q()
q.connector = 'or'
q.children.append(('sale',400))
q.children.append(('price__lt',600))
res = models.Book.objects.filter(q) # 如果不修改q.connector,默认还是and关系
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
4、Django中如何开启事务
# 事务的四个特征
ACID
原子性:不可分割的最小单位
一致性:跟原子性是相辅相成
隔离性:事务之间互相不干扰
持久性:事务一旦确认永久生效
"""事物的回滚:rollback; 事务的确认:commit"""
"""如何开启事务?"""
from django.db import transaction
try:
with transaction.atomic():
# sql1
# sql2
...
# 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
print(e)
print('执行其他操作')
5、orm中常用字段及参数
# AutoField
主键字段 primary_key=True
# CharField varchar(可变字符串)
verbose_name 字段的注释
max_length 长度【必须指定】
# IntegerField int
# BigIntegerField bigint
# DecimalField decimal
max_digits=8 最长8位
decimal_places=2 小数2位
# EmailFiled varchar(254)
# DateField date
年月日
# DateTimeField datetime
年月日时分秒
"""
auto_now:每次修改数据的时候都会自动更新当前时间
auto_now_add:只在创建数据的时候记录创建时间,后续不会自动修改
"""
# BooleanField() - 布尔值类型
该字段传布尔值(False/True) 对应数据库里面存0/1
# TextField(Field) - 文本类型
该字段可以用来存大段内容(文章、博客...),并且没有字数限制
# FileField(Field) - 字符类型
upload_to = "/data/"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中。比如,数据库中保存 /data/a.txt
"""外键字段及参数"""
# unique=True
所以: ForeignKey(unique=True) <===> OneToOneField()
如果使用 ForeignKey(unique=True)创建一对一关系,orm会有一个提示信息,orm推荐你使用后者但是前者也能用。
# db_index=
如果db_index=True,则代表着为此字段设置索引【索引参考mysql索引内容】
# to_field=
设置要关联的表的字段,默认不写关联的就是另外一张的主键字段
# on_delete=True
设置级联更新,级联删除使用。
# on_update=True
django2.X及以上版本,需要你自己指定外键字段的级联更新级联删除
自定义字段【了解即可】:
django除了给你提供了很多字段类型之外,还支持你自定义字段,自定义方式如下。
"""自定义字段"""
class MyCharField(models.Field):
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length
# 调用父类的init方法
super().__init__(max_length=max_length,*args,**kwargs) # 一定要是关键字的形式传入
def db_type(self, connection):
"""
返回真正的数据类型及各种约束条件
:param connection:
:return:
"""
return 'char(%s)'%self.max_length
"""自定义字段的使用"""
myfield = MyCharField(max_length=16, null=True)
七、数据库查询优化
orm查询语句的特点:
惰性查询,如果你仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行。
1、only与defer
- only
"""获取书籍表中所有书的名字"""
res = models.Book.objects.values('title')
print(res) # <QuerySet [{'title': '三国演唱爆款'}, {'title': '东游记爆款'}, {'title': '大学爆款'}, {'title': '中庸爆款'}]>
for item in res: # for循环,取得所有书的名单
print(item.get('title'))
"""那么我想要实现获取到的是一个数据对象,然后点title就能够拿到书名,而且并且没有其他字段?"""
res = models.Book.objects.only('title')
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
for item in res:
print(item.title) # 取得书名
# 点击only括号内的字段,不会走数据库;击only括号内没有的字段,会重新走数据库查询而all不需要走
- defer
res = models.Book.objects.defer('title') # 和only刚好相反,对象除了没有title属性之外其他的都有
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
res = models.Book.objects.defer('title') # 和only刚好相反,对象除了没有title属性之外其他的都有
for item in res:
print(item.price) # 点defer中没有的,只需要一条sql查询语句
"""
defer与only刚好相反
defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据
而如果查询的是非括号内的字段,则不需要走数据库
"""
2、select_related与prefetch_related
select_related与prefetch_related ,跟跨表操作有关。
- select_related【联表查询】
res = models.Book.objects.all()
for item in res:
print(item.publish.name) # 每循环一次就要取数据库中查询一次
res = models.Book.objects.select_related('publish')
print(res) # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
"""
select_related内部直接先将book与publish连起来 然后一次性将大表里面的所有数据全部封装给查询出来的对象, 这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询。
select_related括号内只能放外键字段,只能一对多,一对一,多对多不行。
"""
-
prefetch_related 【子查询】
prefetch_related该方法内部其实就是子查询,将子查询查询出来的所有结果也给你封装到对象中,给人的感觉好像也是一次性搞定的【其实分两次】。
res = models.Book.objects.prefetch_related('publish')
# print(res)
for item in res:
print(item.publish.name)