7 Django 聚合查询 分组查询 F与Q查询 事务 ORM字段 查询优化的方法

Django

1 聚合查询

关键字 aggregate

from django.db.models import Max, Min, Sum, Count, Avg
from app01 import models

res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('pk'), Avg('price'))

2 分组查询

关键字 annotate

默认按照主键字段分组

from django.db.models import Max, Min, Sum, Count, Avg
from app01 import models

# 1. 统计每一本书的作者个数
# res = models.Book.objects.annotate()  # 按照书进行分组
res = models.Book.objects.annotate(author_num=Count('authors')).values('title', 'author_num')
# 书查作者,正向查询按字段。
# authors__id可以简写为authors,内部自动查询其主键。

# 2. 统计每个出版社卖的最便宜的书的价格
# res = models.Publish.objects.annotate()  # 按照出版社进行分组
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
# 出版社查书,反向查询按表名小写

# 3. 统计不止一个作者的图书
# 先按照图书分组,求每一本书对应的作者个数;
# 过滤出作者数量大于1的图书。
res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title', 'author_num')
# 只要orm语句获得的结果是一个queryset对象,那么它就可以继续调用queryset对象的方法。

# 4. 查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')

按照指定的字段分组
values

# 按照书的价格分组
models.Book.objects.values('price').annotate()

注意,如果如果出现分组查询报错的情况,需要修改数据库严格模式。
MySQL中,严格模式 ONLY_FULL_GROUP_BY
分组后默认只能获取分组的依据字段,其它字段无法直接获取。

3 F查询

F查询,用于直接获取/操作表中指定字段的数据。

  1. 查询出库数大于入库数的书籍
from django.db.models import F

res = models.Book.objects.filter(output__gt=F('input'))
  1. 将所有书籍的价格提升500块
from django.db.models import F

models.Book.objects.update(price=F('price') + 500)
  1. 将所有书的名称后面加上‘爆款’两个字

操作字符类型的数据时,F查询不能直接进行字符串拼接。

from django.db.models.functions import Concat
from django.db.models import Value

models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

4 Q查询

4.1 基本使用

Q包裹的条件之间的连接关系支持与、或、非。

查询出库数大于100或者价格小于600的书籍

from django.db.models import Q

# and ,
res_and = models.Book.objects.filter(Q(output__gt=100), Q(price__lt=600))

# or |
res_or = models.Book.objects.filter(Q(output__gt=100) | Q(price__lt=600))

# not ~
res_not = models.Book.objects.filter(~Q(output__gt=100))

# 组合使用
res_not = models.Book.objects.filter(~Q(output__gt=100) | Q(price__lt=600))

4.2 Q的高阶用法

前面设置查询条件时,条件等号左边是变量名的形式,
Q支持查询条件的左边的值以及条件连接关系是字符串的形式,即可以动态指定查询条件。

from django.db.models import Q

q = Q()  # 生成Q对象
q.connector = 'or'  # and/or,连接关系不指定默认是and。
q.children.append(('output__gt', 100))  # 注意,参数接收可迭代对象,可以是元组或列表。
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)

5 事务

事务的四个特性:ACID
A:原子性,不可分割的最小单位;
C:一致性,事务前后处于一致状态;
I:隔离性,事务与事务之间互不干扰;
D:持久性,事务确认后永久生效。

事务回滚:rollback
事务确认:commit

Django中如何开启事务?

from django.db import transaction

try:
    with transaction.atomic():
        # with代码块内的所有orm操作都属于同一个事务
        ...
except Exception as e:
    print(e)
print('执行其它操作。')

6 字段

6.1 常用字段

  1. AutoField 主键字段
    参数:
    primary_key=True (必需参数)
  2. CharField
    对应的类型是varchar
    参数:
    max_length 字符长度
  3. IntegerField
    对应的类型是int
  4. BigIntegerField
    对应的类型是bigint
  5. DecimalField
    参数:
    max_digits:总位数
    decimal_places:小数位
  6. EmailField
    对应的是varchar(254)
  7. DateFieldDateTimeField
    参数:
    auto_now:每次修改数据都会更新该时间字段;
    auto_now_add:创建数据时自动记录创建时间,之后一般不会修改。
  8. BooleanField
    对应的是布尔类型。
    字段传入布尔值(False/True),数据库中存储0/1 。
  9. TextField
    用于存储大量文本内容,没有字数限制。
  10. FileField
    用于上传文件,
    为该字段传一个文件对象,将文件保存到参数upload_to指定的目录下,将文件的存储路径保存到数据库中。
    参数:
    upload_to = “” 上传文件的保存目录;
    storage = None 存储组件,默认。

其它参考:https://www.cnblogs.com/Dominic-Ji/p/9203990.html

6.2 常用字段参数

verbose_name、
字段注释

null
用于表示某个字段可以为空。

unique
如果设置unique=True,该字段在此表中必须唯一。
一对一中,ForeignKey(unique=True) 等价于 OneToOneField(),即一对一关系有两种指定方式。

db_index
如果设置db_index=True,表示为该字段创建索引。

default
为该字段设置默认值。

6.3 外键字段ForeignKey

外键字段通常用于表示与其它表的关联关系,一般把ForeignKey字段设置在’一对多’中’多’的一方。
ForeignKey既可以用于与其它表做关联,也可以用于和自身做关联。
参数:
to
设置关联的表

to_field
设置关联的表的字段,默认关联主键字段。

on_delete
删除关联表中的数据时,当前表与其关联的数据的行为。
django 2.X 及以上版本,需要指定外键字段的级联更新和级联删除。
on_delete = models.CASCADE
删除关联数据,与之关联也删除

db_constraint
是否在数据库中创建外键约束,默认为True。

6.4 自定义字段

models.py

# 自定义字段
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        # 最后调用父类的init方法
        # max_length一定要以关键字的形式传入
        super().__init__(max_length=max_length, *args, **kwargs)  

    def db_type(self, connection):
        # 返回真正的数据类型及约束条件
        return 'char(%s)' % self.max_length
# 自定义字段使用
class Book(models.Model):
	mycharfield = MyCharField(max_length=32, null=True)

7 数据库查询优化

7.1 only 与 defer

ORM的特点:惰性查询
如果仅仅是ORM语句,在后面没有用到该语句查询出来的结果,ORM会自动识别并不执行这条语句。

res = models.Book.objects.all()
# 这条ORM查询语句不会执行,因为后面没有使用res。
res = models.Book.objects.all()
print(res)  # 这里使用了res,执行ORM查询语句。

获取书籍表中所有数的名字

res = models.Book.objects.values('title')
for each_book in res:
    print(each_book.get('title'))
7.1.1 only

only方法返回QuerySet对象,类似于列表套对象,且对象中只有only的参数指定的属性,如果后面访问only未指定的属性,则会重新检索数据库。

需求:得到的数据是一个数据对象,然后点title就直接获取书名,并且没有其它字段。

res = models.Book.objects.only('title')
print(res)  # , ]>
for each_book in res:
    print(each_book.title)  # 对于only的括号内指定的字段,循环中不会再次查询数据库;
    print(each_book.price)  # 对于only的括号内没有指定的字段,每一次循环都会重新查询一次数据库。
res = models.Book.objects.all()
for each_book in res:
	# all 循环中不会查询数据库
	print(each_book.title)
	print(each_book.price)
7.1.2 defer

defer与only相反
defer括号内指定的字段不在查询出来的对象内,每一次查询defer指定的字段都需要重新查询数据库,
如果查询的是defer括号内没有指定的字段,不需要重新查询数据库。

res = models.Book.objects.defer('title')
print(res)  # , ]>
for each_book in res:
    print(each_book.title)  # 对于defer的括号内指定的字段,每一次循环都会重新查询一次数据库;
    print(each_book.price)  # 对于defer的括号内没有指定的字段,循环中不会再次查询数据库。
7.1.3 总结

访问only括号内指定的字段不需要重新检索数据库,访问其它字段则需要重新检索数据库。
def与only正好相反。

only与defer的选择,取决于字段个数。

7.2 select_related 与 prefetch_related

select_related 与 prefetch_related与跨表操作有关。

7.2.1 select_related 联表操作
res = models.Book.objects.all()
for each_book in res:
    print(each_book.publish.name)  # 每循环一次就查询一次数据库

res = models.Book.objects.select_related('authors')  # INNER JOIN
for each_book in res:
    print(each_book.publish.name)  # 不需要再次查询数据库

select_related在内部先将book表与publish表连起来进行查询,
然后将联表查询的结果全部封装成对象。
此时无论从对象中获取book表的数据还是publish表的数据,都不需要再次查询数据库。

注意:select_related括号内只能放外键字段,
因此只支持一对多和一对一,不支持多对多。

可以一次连接多张表,将多张表的检索结果封装到对象中。

models.表名.objects.select_related('外键字段1__外键字段2__外键字段3')  
7.2.2 prefetch_related 子查询
res = models.Book.objects.prefetch_related('publish')  # 子查询
for each_book in res:
    print(each_book.publish.name)

prefetch_related方法在内部其实就是子查询,将子查询得到的所有结果全部封装到对象中,好像也是一次性搞定的,使用感觉上与select_related差别不大。

7.2.3 总结

select_related 联表操作 内部只需查询一次;
select_related的参数只接受外键字段,因此只支持一对多和一对一,不支持多对多。

prefetch_related 子查询 内部先查询子表,将子表的查询结果当作另一张表的条件再次进行查询,总共查询两次。

select_related与prefetch_related选择
取决于联表耗时长还是再次检索数据库的耗时长。

你可能感兴趣的:(Django学习,mysql,python,orm)