模型层之F与Q查询, 事务及其他

08 模型层之F与Q查询, 事务及其他

一. F与Q查询

1. F查询

像之前我们所了解的一些过滤的例子和操作都是在针对字段值和某一个常量之间作比较,但是如果我们要针对两个字段值作比较的话就不行了,这就涉及到这个F查询了

"""
# 作用: 能够帮助你直接获取到表中某个字段对应的数据
# 使用:
    from django.db.models import F
    # 获取到某个字段对应的数据   
        F("字段__条件")
    # 查询字段对应的数据是数字类型可以直接加减运算:
        F('字段__条件') + 500
    # 字符类型需要借助Concat和Value方法
        from django.db.models.functions import Concat
        from django.db.models import Value
        Concat(F('字段__条件'), Value("str"))
        注意: 查询字段对应的数据是字符类型不能直接进行拼接. 否则操作的字段对应的所有数据将变成空白.
"""

from django.db.models import F

# 1. 查询卖出数大于库存数的书籍
res = models.Book.objects.filter(sale__gt=F('stock'))
print(res)  # <QuerySet [<Book: 论语>, <Book: 老子>]>


# 2. 将所有书籍的价格提升500块
res = models.Book.objects.update(price=F('price') + 500)
print(res)  # .update方法返回受影响的行数  7


# 3. 将所有书的名称后面加上爆款两个字
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
# models.Book.objects.update(title=F('title') + '爆款')  # 这样指定的字段title所有对应的数据全部变成空白.

2. Q查询

"""
# 作用: filter的字段筛选条件指定多个, 默认是and连接. 要实现or或者not需要借助Q查询
# 使用: 
    from django.db.models import Q
    Q(字段__条件=值)
    # 连接条件and的3种情况
        1. filter中指定多个参数逗号隔开: filter(参数1, 参数2)
        2. 查询指定多个逗号隔开: filter(Q(), Q())
        3. 使用&连接符: filter(Q() & Q())
    # 连接条件or
        filter(Q() | Q())
    # 连接条件not
        filter(~Q() | Q()) 
    # Q查询的高阶用法: 让左边指定的变量形式的查询条件可以是字符串
        q = Q()
        q.connecter = 'or'  # 指定连接符. 不指定默认and
        q.children.append(Q('字段__条件', 值))  
        res = models.XXX.objects.filter(q)
"""


from django.db.models import Q
# 1. 查询卖出数大于100 和 价格小于900的书籍 --> 连接条件 and
# res = models.Book.objects.filter(sale__gt=100, price__lt=900)
# res = models.Book.objects.filter(Q(sale__gt=100), Q(price__lt=900))
res = models.Book.objects.filter(Q(sale__gt=100) & Q(price__lt=900))
print(res)  # <QuerySet [<Book: 论语爆款>]>


# 2. 查询卖出数大于100或者价格小于600的书籍 --> 连接条件 or
res = models.Book.objects.filter(Q(sale__gt=100) | Q(price__lt=600))
print(res)  # <QuerySet [<Book: 红龙盟爆款>, <Book: 老子爆款>, <Book: 论语爆款>, <Book: 孟子爆款>, <Book: 老子爆款>, <Book: 三字经爆款>]>


# 3. 查询卖出数不大于100或者价格小于600的书籍 --> 连接条件 not
res = models.Book.objects.filter(~Q(sale__gt=100) | Q(price__lt=600))
print(res)   # <QuerySet [<Book: 夕阳西下爆款>]>


# 4. Q的高阶用法: 能够将查询条件的左边变量的形式变成字符串的形式
q = Q()               # 第一步: 实例化一个q对象
q.connector = 'or'    # 第二步: 定义连接条件
q.children.append(('sale__gt', 100))    # 第三步: 指定字符串形式的查询字段条件, 以及范围100
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)     # 第四步: 将q对象传入filter
print(res)

二. django中如何开启事务

'''
# 事务的四大特性: 
    # 简称: ACID
    # A: 原子性
        事务对数据的修改操作要么同时成功, 要么一个都别想成功(回滚)
    # C: 一致性
        事务的执行必然是从一个一致性的状态, 转变到另一个一致性的状态.
    # I: 隔离性
        对于并发的事务, 每个事务之间是互相隔离的, 互不影响的. 如果是争对同一份数据的修改操作, 那么将并发变成串行, 牺牲了效率, 但是保证了数据的安全.
    # D: 持久性:
        事务执行完毕对数据的修改操作是永久性的. 即便在数据库管理系统异常, 提交的结果对数据的修改也是永久的.
        
# 原生SQL中的事务操作步骤
    1. 开启 start transaction;
    2. 回滚 rollback;
    3. 确认 commit;
'''

from django.db import transaction
try:
    with transaction.atomic():
        # ORM执行SQL语句
        ...
        # 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
    print(e)
    
print('执行其他操作')

三. orm语句的特点: 惰性查询

"""
如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数
那么orm会自动识别 直接不执行

惰性: res = models.Book.objects.all()
执行: print(res)
"""

四. Django ORM执行原生SQL

'''
extra: 标识要做格外的操作
select={'publish_date': 'date_format(publish_date, "%%Y-%%m")'}: 执行原生的sql语句用到原生sql日期函数date_format
    publish_date: 定义一个key, 通过values后面可以拿到结果. 类始于annotate分组一样
'''
# 需求: 查询数据名称和出版时间(年-月)
res = models.Book.objects.extra(select={'publish_date': 'date_format(publish_date, "%%Y-%%m")'}).values('name', 'publish_date')
print(res)   # <QuerySet [{'publish_date': '2020-05', 'name': '西游记'}, {'publish_date': '2020-05', 'name': '水浒传'}]>

五. 数据库查询优化

1. only 和 defer

'''
使用: 直接在objects后面连用
返回: only 和 defer都返回QuerySet对象
区别: 
    only:  only括号内指定的字段,  在被查询的时候不会走数据库. 
    defer: defer括号内指定的字段, 在被查询的时候会走数据库
特殊: 如果仅仅使用all. 没有执行结果它是惰性的, 但是一旦执行过一次. 第二次拿到all的返回值. 是不需要重新走数据库    
'''

# 1. only
# res = models.Publish.objects.only('name')
res = models.Publish.objects.all()
print(res)      # <QuerySet [<Publish: 东方出版社>, <Publish: 北方出版社>]>
for i in res:
    print(i.name)  # 点击only括号内的字段不会走数据库

for i in res:
    print(i.addr)  # 点击only括号内没有的字段会重新走数据库. 如果是all的情况就不需要走.
"""
(0.001) SELECT `app01_publish`.`id`, `app01_publish`.`addr` FROM `app01_publish` WHERE `app01_publish`.`id` = 1; args=(1,)
(0.001) SELECT `app01_publish`.`id`, `app01_publish`.`addr` FROM `app01_publish` WHERE `app01_publish`.`id` = 2; args=(2,)
"""


# 2. defer
res = models.Publish.objects.defer('name')
print(res)  # <QuerySet [<Publish: 东方出版社>, <Publish: 北方出版社>]>


for i in res:
    print(i.name)  # 点defer括号内的字段会走数据库
'''
(0.000) SELECT `app01_publish`.`id`, `app01_publish`.`name` FROM `app01_publish` WHERE `app01_publish`.`id` = 1; args=(1,)
(0.000) SELECT `app01_publish`.`id`, `app01_publish`.`name` FROM `app01_publish` WHERE `app01_publish`.`id` = 2; args=(2,)
'''

for i in res:
    print(i.addr)  # 点defer括号内的字段不会走数据库
'''
使用: 直接在objects后面连用
返回: only 和 defer都返回QuerySet对象
区别: 
    select_related:   内部使用连表查询. 
        !!注意:!! 括号内只能放外键字段. 且只支持一对一, 一对多的表关系. 多对多不支持.
        内部通过1次性将2表查询出来封装成对象中, 下次查询这2表就无序走数据库了.
    prefetch_related: 内部使用子查询.  
        内部通过2次性将子查询结果查询出封装成对象中.下次查询这2表就无序走数据库了. (感觉视角: 感觉是一次性搞定的) 
'''


# 1. select_related 连表查询
'''
select_related内部直接先将Author与author_detail表连起来 然后一次性将大表里面的所有数据, 全部封装给查询出来的对象.
这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了
'''
res = models.Author.objects.select_related('author_detail')
for i in res:
    print(i.author_detail.phone)
'''
(0.001) SELECT `app01_author`.`id`, `app01_author`.`name`, `app01_author`.`age`, `app01_author`.`author_detail_id`, `app01_authordetail`.`id`, `app01_authordetail`.`phone`, `app01_authordetail`.`addr` FROM `app01_author` INNER JOIN `app01_authordetail` ON (`app01_author`.`author_detail_id` = `app01_authordetail`.`id`); args=()
'''

# 下次查询这2表就无序走数据库了.
for i in res:
    print(i.author_detail.phone) 

    

# prefetch_related 子查询 
'''
prefetch_related该方法内部其实就是子查询
将子查询查询出来的所有结果也给你封装到对象中
给你的感觉好像也是一次性搞定的
'''
res = models.Author.objects.prefetch_related('author_detail')
for i in res:
    print(i.author_detail.phone)  
'''
(0.000) SELECT `app01_author`.`id`, `app01_author`.`name`, `app01_author`.`age`, `app01_author`.`author_detail_id` FROM `app01_author`; args=()
(0.001) SELECT VERSION(); args=None
(0.000) SELECT `app01_authordetail`.`id`, `app01_authordetail`.`phone`, `app01_authordetail`.`addr` FROM `app01_authordetail` WHERE `app01_authordetail`.`id` IN (1, 2, 3); args=(1, 2, 3)
'''

# 下次查询这2表就无序走数据库了.
for i in res:
    print(i.author_detail.phone)

六. 总结

# F与Q查询
    # F查询:
        # 作用: 解决在指定条件不是一个确定的值的情况, 而来源于字段对应的数据.
        # 使用:
            from django.db.models import F
            # 基本使用
                models.Book.objects.filter(sole__gt=F('stock'))
            # 争对字段对应的数据是数字类型可以进行加减
                models.Book.objects.filter(sole__gt=F('stock')+20)
            # 注意:
                字段对应的数据是不能够直接进行字符串的拼接操作的. 要用如下的方法. 如果直接拼接, 那么操作的字段对应的所有数据将会为空值.
            # 争对字段对应的数据是字符串类型的拼接:
                from django.db.objects.function Concat
                from django.db.objects import Value
                models.Book.objects.filter(title=Concat(F('stock'), Value('字符串')))

    # Q查询:
        # 作用: filter多个参数默认and连接. 使用Q查询可以支持and, or, not连接条件语法
        # 使用:
            from django.db.models import Q
            # and连接语法: &
                models.Book.objects.filter(sole__gt=100,stock__lt=100)
                models.Book.objects.filter(Q(sole__gt=100), Q(stock__lt=100))
                models.Book.objects.filter(Q(sole__gt=100) & Q(stock__lt=100))
            # or连接语法:  |
            # not连接语法: ~

            # 高阶用法: 对指定条件左边是用的变量的形式sole__gt. 可以定制指定字符串的形式'sole__gt'
                q = Q()
                q.connector = 'or'   # 声明连接符. 不声明默认使用and连接
                q.children.append(('sole__gt', 100))
                q.children.append(('stock__lt', 100))
                models.Book.models.filter(q)


# 开启事务
    # 事务的四大特性: ACID 原子性 一致性 隔离性 永久性
    # MySQL中事务的操作:
        开启: start transaction;
        回滚: rollback;
        确认: commit;
    # Django中开启事务书写方式:
        from django.db import transaction
        try:
            with transaction.atomic():
            SQL语句...
        except Exception as e:
            print(e)

# ORM语句的惰性查询
    书写ORM语句并不会执行, 只有在用到的时候才执行.
    惰性: res = models.Book.objects.all()
    执行: print(res)

# only 和 defer
    # 返回值: QuerySet对象
    # only
        only括号内指定的字段. 在查询的时候不走数据库. 其它则走
    # defer
        defer括号内指定的字段. 在查询的时候走数据库. 其它不走.
    # 补充: all方法
        仅仅使用all方法拿到的返回值, 只在第一次执行的时候走数据库, 之后的操作都将不走.

# select_related 和 prefetch_related
    # 返回值: QuerySet对象
    # select_related  内部使用连表查询
        注意: 括号内参数必须执行外键字段
        支持范围: 仅仅支持一对多, 一对一关系表
        流程: 一次性将查询的2表拼接起来封装到对象中. 操作时就不需要重新到数据库中重复查找.

    # prefetch_related  内部使用子查询
        流程: 二次性将查询. 第一次查询的结果充当第二次查询的条件, 查询完毕以后封装到对象中. 操作时就不需要重新到数据库中重复查找
        触感: 感觉是一次性搞定的

七. 方法补充

1. in_bulk 根据主键ID进行查找

dic_obj = models.Book.objects.in_bulk([1, 2])
print(type(dic_obj))  # <class 'dict'>
for pk, book_obj in dic_obj.items():
    print(pk, book_obj.name)

'''
1 西游记
2 水浒传
'''
posted @ 2020-10-19 14:03  为了等  阅读(225)  评论(0编辑  收藏  举报