正反向查询进阶操作、聚合查询、分组查询、F与Q查询、ORM查询优化、事物操作、模型层常见字段、ORM常见字段参数、多对多三种创建方式
一、正反向查询进阶操作
'''正反向查询进阶操作'''
# 1.查询主键为1的书籍对应的出版社名称及书名
# 外键在书籍表,反向查询
res = models.Publish.objects.filter(book__pk=1).values('name','book__title')
print(res) # <QuerySet [{'name': '清华出版社', 'book__title': '小鞠带你环游世界'}]>
# 2.查询主键为3的书籍对应的作者姓名以及书名
# 主键在书籍表,反向查询
res = models.Author.objects.filter(book__pk=3).values('name','book__title')
print(res) # <QuerySet [{'name': 'xj', 'book__title': '小鞠的白日梦中级'}, {'name': 'zxr', 'book__title': '小鞠的白日梦中级'}]>
# 3.查询xj的作者的电话号码和地址
# 主键在作者表,反向查询
res = models.AuthorDetail.objects.filter(author__name='xj').values('phone','addr')
print(res) # <QuerySet [{'phone': 18856082140, 'addr': '安徽池州'}]>
# 4.查询清华出版社出版的书籍名称和价格
# 主键在书籍表,正向查询
res = models.Book.objects.filter(publish__name='清华出版社').values('title','price')
print(res) # <QuerySet [{'title': '小鞠带你环游世界', 'price': Decimal('88.88')}, {'title': '小鞠的白日梦初级', 'price': Decimal('56.66')}, {'title': '小鞠的白日梦中级', 'price': Decimal('56.66')}, {'title': '小鞠的白日梦终级', 'price': Decimal('56.66')}]>
# 5.查询xj写过的书的名称和日期
# 主键在书籍表,正向查询
res = models.Book.objects.filter(authors__name='xj').values('title','publish_time')
print(res) # <QuerySet [{'title': '小鞠的白日梦中级', 'publish_time': datetime.datetime(2022, 9, 5, 13, 15, 28, 483591, tzinfo=<UTC>)}]>
# 6.查询电话号码是18856082140的作者姓名和年龄
# 主键在作者表,正向查询
res = models.Author.objects.filter(author_detail__phone=18856082140).values('name','age')
print(res) # <QuerySet [{'name': 'xj', 'age': 28}]>
# 7.查询主键为1的书籍对应的作者电话号码
# 主键在书籍表,作者详情表连接作者表,作者表连接着书籍表,反向查询
res = models.AuthorDetail.objects.filter(author__book__pk=1).values('author__name','phone')
print(res) # <QuerySet [{'author__name': 'xj', 'phone': 18856082140}]>
res1 = models.Author.objects.filter(book__pk=1).values('name','author_detail__phone')
print(res1) # <QuerySet [{'name': 'xj', 'author_detail__phone': 18856082140}]>
二、聚合查询(aggregate)
聚合函数:max()、min()、sum()、avg()、count()
'''
1.没有分组之前如果单纯的使用聚合函数,需要关键字aggregate
2.聚合函数返回值的数据类型是字典
3.返回的字典中,键的名称默认是:属性名__聚合函数名,值为聚合值
4.聚合函数的查询的结果起别名(必须是变量名):aggregate(别名 = 聚合函数名('属性名'))
'''
### 聚合函数
from django.db.models import Max, Min, Sum, Avg, Count # 导入的模块
res = models.Book.objects.aggregate(m_p=Max('price')) # 没有分组之前如果单纯的使用聚合函数,需要关键字aggregate
print(res)
res1 = models.Book.objects.aggregate(Max('price'))
print(res1)
res2 = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
print(res2)
三、分组查询(annotate)
分组特性:默认只能够直接获取分组的字段
'''分组有一个特性,默认只能够直接获取分组的字段,其它字段需要修改mysql内的配置'''
# 查看mysql内的配置
show variables like '%mode%';
# 将sql_mode中only_full_group_by配置移除即可
set global sql_mode = 'STRICT_TRANS_TABLES';
代码展示
# 1.统计每一本的作者个数
# 由书到作者,是正向概念,字段名
'''按照整条数据分组:models.Book.objects.annotate() 按照一条条书籍记录分组'''
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
print(res) # <QuerySet [{'title': '小鞠带你环游世界', 'author_num': 1}, {'title': '小鞠的白日梦初级', 'author_num': 1}, {'title': '小鞠的白日梦中级', 'author_num': 2}, {'title': '小鞠的白日梦终级', 'author_num': 1}, {'title': '初级梦想之身体健康', 'author_num': 1}, {'title': '中级梦想之金钱自由', 'author_num': 1}, {'title': '终级梦想之没有烦恼', 'author_num': 1}, {'title': '快乐的终极实现', 'author_num': 1}, {'title': '健康的终极实现', 'author_num': 1}, {'title': '金钱的终极实现', 'author_num': 1}]>
'''按照表中某个字段分组:models.Book.objects.values('title').annotate() 按照annotate之前values括号中指定的字段分组'''
res1 = models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id','book_num')
print(res1) # <QuerySet [{'publish_id': 1, 'book_num': 4}, {'publish_id': 2, 'book_num': 3}, {'publish_id': 3, 'book_num': 3}]>
# 2.统计每个出版社卖的最便宜的书籍
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
print(res) # <QuerySet [{'name': '清华出版社', 'min_price': Decimal('56.66')}, {'name': '浙江出版社', 'min_price': Decimal('96.66')}, {'name': '人民出版社', 'min_price': Decimal('199.99')}]>
# 3.统计不止一个作者的书籍
# 先根据书籍id分组,统计书籍的作者数,然后筛选作者数大于1的书籍,书籍到作者正向查询
# filter在annotate前是where在annotate后面是having in
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': 2}, {'title': '终级梦想之没有烦恼', 'author_num': 2}, {'title': '快乐的终极实现', 'author_num': 2}, {'title': '健康的终极实现', 'author_num': 2}, {'title': '金钱的终极实现', 'author_num': 2}]>
# 4.查询各个作者出的数的总价格
# 根据作者分组,作者到书是反向查询
res = models.Author.objects.annotate(book_sum_price=Sum('book__price')).values_list('name','book_sum_price')
print(res) # <QuerySet [('xj', Decimal('498.85')), ('zxr', Decimal('906.61')), ('wyn', Decimal('496.64'))]>
小结
1.返回值:
分组后,用 values 取值,则返回值是QuerySet,数据类型里面为字典;
分组后,用 values_list 取值,则返回值是Queryset,数据类型里面为元组套字典
2.annotate与聚合函数
1)values/values_list放在annotate前面,values/values_list是声明已什么字段分组,annotate执行分组
2)values/values_list放在annotate后面,values/values_list表示要查询哪些字段,annotate执行分组
3)annotate里面的聚合函数起别名后在annotate后面的values/values_list里面写别名可查询聚合的结果
3.filter:
filter在annotate前是where在annotate后面是having in
四、F与Q查询
数据准备
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish_time = models.DateTimeField(auto_now=True)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
storage_num = models.IntegerField(verbose_name='库存数',null=True)
sale_num = models.IntegerField(verbose_name='售出数',default=1000)
'''
表中已有数据添加新的字段,新的字段的值没有给出会报错
1)提供一个默认值
2)退出,在models.py里修改
当表中已有数据的情况下,添加新的字段需要指定参数
设置字段允许为空
设置字段默认值
在终端中直接给出默认值
'''
查询练习
'''F查询:查询条件不是自定义的而是来自于表中其它的字段'''
# 查询字段和查询条件全部来自于表中的字段需要使用 F查询
from django.db.models import F
# 1.查询库存数大于卖出数的书籍
res = models.Book.objects.filter(storage_num__gt=F('sale_num'))
print(res) # <QuerySet [<Book: 书籍对象:小鞠带你环游世界>, <Book: 书籍对象:小鞠的白日梦初级>, <Book: 书籍对象:快乐的终极实现>, <Book: 书籍对象:金钱的终极实现>]>
# 2.将每本书的价格上涨10块钱
models.Book.objects.update(price=F('price') + 10)
from django.db.models.functions import Concat
from django.db.models import Value
# 3.将书籍加上爆款
models.Book.objects.filter(pk=10).update(title=Concat(F('title'), Value('爆款')))
'''Q查询: 可以改变filter括号内多个条件之间的逻辑运算符,还可以将查询条件的字段改为字符串形式'''
1)Q查询:可以改变filter括号内多个条件之间的逻辑运算符
from django.db.models import Q
# res = models.Book.objects.filter(pk=1,publish_id=3) # ,默认是and关系
# print(res) # <QuerySet []>
res = models.Book.objects.filter(Q(pk=1),Q(publish_id=3)) # ,还是and关系
# print(res)
print(res.query) # SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_time`, `app01_book`.`publish_id`, `app01_book`.`storage_num`, `app01_book`.`sale_num` FROM `app01_book` WHERE (`app01_book`.`id` = 1 AND `app01_book`.`publish_id` = 3)
res1 = models.Book.objects.filter(Q(pk=1) | Q(publish_id=3)) # | 是or关系
# print(res1) # <QuerySet [<Book: 书籍对象:小鞠带你环游世界>, <Book: 书籍对象:快乐的终极实现>, <Book: 书籍对象:健康的终极实现>, <Book: 书籍对象:金钱的终极实现爆款>]>
print(res1.query) # SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_time`, `app01_book`.`publish_id`, `app01_book`.`storage_num`, `app01_book`.`sale_num` FROM `app01_book` WHERE (`app01_book`.`id` = 1 OR `app01_book`.`publish_id` = 3)
res2 = models.Book.objects.filter(~Q(pk=1) | Q(publish_id=3)) # ~ 是not关系
# print(res2) # <QuerySet [<Book: 书籍对象:小鞠的白日梦初级>, <Book: 书籍对象:小鞠的白日梦中级>, <Book: 书籍对象:小鞠的白日梦终级>, <Book: 书籍对象:初级梦想之身体健康>, <Book: 书籍对象:中级梦想之金钱自由>, <Book: 书籍对象:终级梦想之没有烦恼>, <Book: 书籍对象:快乐的终极实现>, <Book: 书籍对象:健康的终极实现>, <Book: 书籍对象:金钱的终极实现爆款>]>
print(res2.query)
2)q查询:可以将查询条件的字段改为字符串形式
q_obj = Q()
q_obj.children.append(('pk', 1))
q_obj.children.append(('publish_id', 3))
res = models.Book.objects.filter(q_obj)
print(res) # <QuerySet []>
q_obj1 = Q()
q_obj1.connector = 'or' # Q对象默认的多个条件也是and关系,可以修改为or
q_obj1.children.append(('pk',1))
q_obj1.children.append(('publish_id', 3))
res1 = models.Book.objects.filter(q_obj1)
print(res1) # <QuerySet [<Book: 书籍对象:小鞠带你环游世界>, <Book: 书籍对象:快乐的终极实现>, <Book: 书籍对象:健康的终极实现>, <Book: 书籍对象:金钱的终极实现爆款>]>
五、ORM查询优化
'''惰性查询:如果只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别出来,不执行SQL语句'''
res = models.Book.objects.all() # settings.py内的日志没有打印SQL语句,不执行SQL语句
print(res) # 需要用到orm语句的结果,执行SQL语句
only
only会将括号内填写的字段封装成一个个数据对象,对象在点击的时候不会再走数据库查询,但是对象也可以点击括号内没有的字段 只不过每次都会走数据库查询
# res = models.Book.objects.all()
# for obj in res:
# print(obj.title) # 将书籍一个个循环打印出
# res = models.Book.objects.values('title', 'price')
# for i in res:
# print(i.title) # 报错,拿到的是子弹不是对象
# print(i.get('title'))
res = models.Book.objects.only('title', 'price') # 括号内查询的字段可以有多个
print(res) # 查询一次,打印一条sql查询语句
for obj in res:
print(obj.title)
print(obj.price) # 有几个对象,就查询几次,打印几条sql查询语句
print(obj.publish_time) # 不是括号内的字段,查询两次
'''
1)only会把括号内字段对应的值,封装到查询返回的对象中,通过对象.括号内的字段,不需要再走数据库查询,直接返回结果;
2)如果对象.不是括号内的字段,就会频繁的走数据库查询;
'''
defer
defer与only刚好相反 ,数据对象点击括号内出现的字段 每次都会走数据库查询,数据对象点击括号内没有的字典 不会走数据库查询
res = models.Book.objects.defer('title', 'price')
for obj in res:
print(obj.title)
print(obj.price)
print(obj.publish_time)
'''
1)和only相反,defer会将括号内的字段封装到查询返回的对象中,通过对象.括号内的字段,每次都需要走数据库查询,然后返回结果;
2)如果对象.不是括号内的字段,就不会走数据库查询
'''
select_related括号内只能接收外键字段(一对多 一对一) 自动连表 得出的数据对象在点击表中数据的时候都不会再走数据库查询
res = models.Book.objects.select_related('publish')
# print(res) # <QuerySet [<Book: 书籍对象:小鞠带你环游世界>, <Book: 书籍对象:小鞠的白日梦初级>, <Book: 书籍对象:小鞠的白日梦中级>, <Book: 书籍对象:小鞠的白日梦终级>, <Book: 书籍对象:初级梦想之身体健康>, <Book: 书籍对象:中级梦想之金钱自由>, <Book: 书籍对象:终级梦想之没有烦恼>, <Book: 书籍对象:快乐的终极实现>, <Book: 书籍对象:健康的终极实现>, <Book: 书籍对象:金钱的终极实现爆款>]>
for obj in res:
print(obj.publish.name)
'''
1)select_related括号内放外键字段,并且外键字段的类型只能是一对一和一对多,不能是多对多;
2)内部自动做联表操作,会将括号内外键字段所关联的表与当前表自动拼接成一张表,然后将表中的数据一个个查询出来封装成一个个的对象。 这样做 就不会重复的走数据库,减轻数据库的压力;
3)select_related括号内可以放多个外键字段,用逗号隔开,会将多个外键字段关联的表拼接成一张大表,后续对象通过正反向查询跨表 内部不会再走数据库查询
'''
prefetch_related底层其实是子查询 将查询之后的结果也一次性封装到数据对象中 用户在使用的时候是感觉不出来的
res = models.Book.objects.prefetch_related('publish')
for obj in res:
print(obj.publish.name)
'''
1)prefetch_related内部是子查询,会自动按照步骤查询多张表,然后将查询的结果封装到对象中,说到底还是联表操作;
2)每放一个外键字段,就会多走一条SQL语句,多查询一张表;
3)括号内支持多个外键字段,并且没有类型限制,将多次查询之后的结果封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询
'''
六、事物操作
事务的定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
with 语句:
from django.db import transaction
try:
with transaction.atomic():
pass
except Exception:
pass
eg:
from django.db import transaction
def viewfunc(request):
# 这部分代码不在事务中,会被Django自动提交
...
with transaction.atomic():
# 这部分代码会在事务中执行
# 创建回滚点
save_id = transaction.savepoint()
#一旦异常,则回滚代码
transaction.savepoint_rollback(save_id)
...
补充:
装饰器方法:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 这些代码会在一个事务中执行
七、模型层常见字段
'''
属性名 = models.字段类型,定义属性时需要指定字段类型, 通过字段类型的参数指定选项
属性名:
不允许使用python的保留关键字
不允许使用mysql的保留关键字
不允许使用连续的下划线,因为Django的查询语法就是连续的下划线
'''
AutoField:自动增长的IntegerField, 不指定时Django会自动创建属性名为id的自动增长属性
CharField(max_length=20):字符串,参数max_length表示最大字符个数
IntegerField():整数
BigIntegerField():长整型(有符号的)
DateField():auto_now=False, auto_now_add=False ;
参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false;
参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false。
DateTimeField:日期时间,参数同DateField,日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DecimalField(max_digits=None, decimal_places=None):可以指定精度的十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数
EmailField():字符串类型,Django Admin以及ModelForm中提供验证机制
BooleanField():传布尔值存数字0或1,布尔字段,值为True或False
TextField():存储大段文本
FileField():存储文件数据 自动找指定位置存储 字段存具体路径
字符串,路径保存在数据库,文件上传到指定目录
参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ForeignKey()
OneToOneField()
ManyToManyField()
orm支持自定义字段
# django中没有对应的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
info = MyCharField(max_length=32)
八、ORM常见字段参数
常见参数
max_length:设置最大的长度
verboses_name:注释
auto_now:配置上auto_now=True,每次更新数据记录的时候会更新该字段。
auto_now_add:配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
null:用于表示某个字段可以为空。
default:为该字段设置默认值。
max_digits:一般同时作用于 DecimalField 字段类型
decimal_places:一般同时作用于 DecimalField 字段类型
(DecimalField(max_digits=8, decimal_places=2)
unique=True:该字段在此表中必须是唯一的 。
db_index=True:代表着为此字段设置索引。
重要参数
to:设置要关联的表。
to_field:设置要关联的字段。
related_name:给外键字段名起别名
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相似
choices
'''当字段数据的可能性是可以完全列举出来的时候,应该考虑使用该参数'''
class UserInfo(models.Model):
username = models.CharField(max_length=32)
gender_choice = (
(1, '男'),
(2, '女'),
(3, '其他')
)
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()) # 可查找提前写好的对应关系(获取到数字对应的关系),若没有对应关系则按照真实数据展示
'''只要是choices参数的字段 如果你想要获取对应信息,固定写法 get_字段名_display()
可查找提前写好的对应关系(获取到数字对应的关系),若没有对应关系则按照真实数据展示
'''
九、多对多三种创建方式
1.自动创建
authors = models.ManyToManyField(to='Author')
优点:第三张表自动创建
缺点:第三张表扩展性差
2.手动创建
class Book(models.Model):
pass
class Author(models.Model):
pass
class Book2Author(models.Model):
book_id = models.ForeignKey(to="Book")
author_id = models.ForeignKey(to="Author")
优点:第三张表扩展性强
缺点:无法使用正反向查询以及多对多四个方法
3.半自动创建
class Book(models.Model):
authors = models.ManyToManyField(to='Author',
through='Book2Author'
through_fields=('book_id','author_id')
)
# class Author(models.Model):
# '''多对多建在任意一方都可以 如果建在作者表 字段顺序互换即可'''
# books = models.ManyToManyField(to='Author',
# through='Book2Author', # 指定表
# through_fields=('author','book')
# )
class Author(models.Model):
pass
class Book2Author(models.Model):
book_id = models.ForeignKey(to="Book")
author_id = models.ForeignKey(to="Author")
优点:扩展性强并且支持正反向查询
缺点:无法使用多对多四个方法