聚合查询,分组查询,F查询与Q查询,ORM查询优化,ORM字段类型和choice参数,ORM事务,ORM执行原生SQL,多对多创建第三张表
数据库:
#书籍
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 一对多
publish = models.ForeignKey(to='Publish')
# 多对多
authors = models.ManyToManyField(to='Author') # 自动创建书籍和作者的第三张关系表
#出版
class Publish(models.Model):
title = models.CharField(max_length=32)
#作者
class Author(models.Model):
name = models.CharField(max_length=32)
# 一对一
author_detail = models.OneToOneField(to='AuthorDetail')
#作者信息
class AuthorDetail(models.Model):
addr = models.CharField(max_length=32)
phone = models.BigIntegerField()
聚合查询(aggregate)
聚合查询返回值的数据类型是字典。
返回的字典中:键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。
MySQL聚合函数:max\min\sum\count\avg
如果要自定义返回字典的键的名称,可以起别名:
aggregate(别名 = 聚合函数名("属性名称"))
使用时需要导入对应模块
from django.db.models import Max, Min, Sum, Avg, Count
查找书籍中最便宜书籍的价格
from django.db.models import Max, Min, Sum, Avg, Count
res = models.Book.objects.aggregate(price_min = Min('price'))
print(res) # {'price_min': Decimal('11111.00')}
'''没有分组也可以使用聚合函数 默认整体就是一组'''
分组查询(annotate)
MySQL分组操作:group by
ORM执行分组操作 如果报错 可能需要去修改sql_mode 移除only_full_group_by
返回值:
分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。
annotate 里面放聚合函数。
values 或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字
段分组,annotate 执行分组。
values 或者 values_list 放在annotate后面: annotate 表示直接以当前表的pk执行分组,
values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在
values 或者 values_list 里写其别名。
1.统计每本书的作者个数
from django.db.models import Min,Min,Avg,Sum,Count
res = models.Book.objects.annotate(munb = Count('authors__pk')).values('title','munb')
print(res)
# <QuerySet [{'title': 'Python编程', 'munb': 2}, {'title': 'JAVA', 'munb': 1}, {'title': 'JS', 'munb': 2}]>
2.统计每个出版社卖的最便宜的书的价格
from django.db.models import Min
res = models.Publish.objects.annotate(book_min = Min('book__price')).values('title','book_min')
print(res)
# <QuerySet [{'title': '上海出版社', 'book_min': Decimal('100000.00')}, {'title': '浙江出版社', 'book_min': Decimal('11111.00')}]>
3.统计不止一个作者的图书
from django.db.models import Count
res = models.Book.objects.annotate(count = Count('authors__id')).filter(count__gt=1).values('title')
print(res) # <QuerySet [{'title': 'Python编程'}, {'title': 'JS'}]>
4.统计每个出版社出版的书籍个数
from django.db.models import Count
res = models.Publish.objects.annotate(count = Count('book__id')).values('title','count')
print(res)
# <QuerySet [{'title': '上海出版社', 'count': 2}, {'title': '浙江出版社', 'count': 1}]>
当表中已有数据的情况下,为表重新添加字段
为书籍表添加2个字段,销量和库存字段
#书籍
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
#销量
sales = models.IntegerField(verbose_name='销量')
#库存
inventory = models.IntegerField(verbose_name='库存')
# 一对多
publish = models.ForeignKey(to='Publish')
# 多对多
authors = models.ManyToManyField(to='Author') # 自动创建书籍和作者的第三张关系表
修改了模型代码后,一定要进行数据迁移。
执行迁移命令后会发现不成功

它给了两个选项:
1)立即提供一次性默认值(在所有现有行上都设置为空值)
2)退出,让我在Models.py中添加一个默认值。
解决方法:
当表中已经有数据的情况下 添加额外的字段 需要指定默认值或者可以为null
方式1
IntegerField(verbose_name='销量',default=1000)
方式2
IntegerField(verbose_name='销量',null=True)
方式3
在迁移命令提示中直接给默认值
F查询和Q查询
F查询
如果想要对同一张表中的两个字段的值做比较,就需要用到 F()。
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
用法:
F("字段名称")
F 动态获取对象字段的值,可以进行运算。
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F() 函数。
首先需要导入模块
from django.db.models import F
1.查询库存大于销量的书籍
from django.db.models import F
res = models.Book.objects.filter(inventory__gt=F('sales'))
print(res) # <QuerySet [<Book: Book object>, <Book: Book object>]>
print(res[0].title,res[1].title,end=' ') # Python编程 JS
2.将所有书的价格提升1000块
from django.db.models import F
res = models.Book.objects.update(price = F('price') + 1000)
print(res) # 3 返回受影响的行数
在已有的char数据类型的值后面添加内容
'''如果要修改char字段咋办(千万不能用上面对数值类型的操作!!!) 需要使用下列两个方法'''
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
Q查询
用法:
Q(条件判断)
之前构造的过滤器里的多个条件的关系都是 and,如果需要执行更复杂的查询(例如 or 语句),就可以使用 Q 。
Q 对象可以使用 & | ~ (与 或 非)操作符进行组合。
优先级从高到低:~ & |。
可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成
and ),但是 Q 对象必须位于所有关键字参数的前面。
使用时需要导入模块
from django.db.models import Q
1.查询价格销量大于900或者名称以P开头的书籍的名称和价格。
from django.db.models import Q
res = models.Book.objects.filter(Q(sales__gt=900) | Q(title__startswith='P')).values('title','price')
print(res)
# <QuerySet [{'title': 'Python编程', 'price': Decimal('102000.00')}, {'title': 'JAVA', 'price': Decimal('125456.00')}]>
Q对象的第二种用法
from django.db.models import Q
q = Q() # 创建一个Q对象q
q.connector = 'OR' # 默认是and 可以改为or
# 传入条件进行查询:
q.children.append(('price__lt', 100000))
q.children.append(('sales__gt', 600))
res = models.Book.objects.filter(q)
# 等于models.Book.objects.filter(Q(price__lt=100000) | Q(sales__gt=600))
print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
ORM查询优化
# 惰性查询:如果只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别出来,直接不执行。
# 举例:
res = models.Book.objects.all() # 这时orm是不会走数据库的
print(res) # 只有当要用到的上述orm语句的结果时,才回去数据库查询。
only
res = models.Book.objects.only('title') # 括号内查询的字段可以有多个
print(res) # 查询一次,打印一条sql查询语句
for i in res:
print(i.title) # 查询一次,打印一条sql查询语句
print(i.price) # 有几个对象,就查询几次,打印几条sql查询语句
only会把括号内字段对应的值,封装到查询返回的对象中,通过对象点括号字段,不需要再走数据库
查询,直接返回结果,一旦你点了不是括号内的字段 就会频繁的去走数据库查询
'''
only英语的意思就是只有,那我们可以理解为通过only查询出来的对象内只会有小括号内的字
段的值,如果想要获取其他字段的值,就还得在查一次数据库或者说是在执行一次SQL语句。
'''
defer
res = models.Book.objects.defer('title')
# print(res)
for i in res:
print(i.price) # 只会执行一次SQL语句
print(i.title) # 每查询一次执行一次SQL语句
和 only相反,defer会将括号内的字段排除之外将其他字段对应的值, 直接封装到返回给你的对象
中, 点其他字段 不需要再走数据库查询,一旦你点了括号内的字段就会有多少值,就会查询几次
'''
defer刚好和only相反,defer小括号里写了啥,它反倒不给你,它会给你除了括号内的所有
字段值。当你获取小括号内的字段值时,会去查询,获取不在小括号内的字段值时反倒还不用去查
询。
'''
select_related
res = models.Book.objects.select_related('publish')
res = models.Book.objects.select_related('publish')
print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
print(res[0].title) # Python编程
print(res[0].publish.title) # 上海出版社
select_related括号内放外键字段,并且外键字段的类型只能是一对一和一对多,不能是多对多,
内部自动做联表操作,会将括号内外键字段所关联的表与当前表自动拼接成一张表,然后将表中的
数据一个个查询出来封装成一个个的对象。 这样做 就不会重复的走数据库,减轻数据库的压力。
select_related括号内可以放多个外键字段,用逗号隔开,会将多个外键字段关联的表拼接成一张大表
后续对象通过正反向查询跨表 内部不会再走数据库查询
prefetch_related
res = models.Book.objects.prefetch_related('publish','authors')
for i in res:
print(i.publish)
prefetch_related内部是子查询,会自动按照步骤查询多张表,然后将查询的结果封装到对象中,这样给用户的感觉还是联表操作。
括号内支持传多个外键字段,并且没有类型限制。
每放一个外键字段,就会多走一条sql语句,多查询一张表
"""
将多次查询之后的结果封装到数据对象中 后续对象通过正反向查询跨表 内部不会再走数据库查询
"""
ORM常见字段
属性名 = models.字段类型,定义属性时需要指定字段类型, 通过字段类型的参数指定选项
属性名:
不允许使用python的保留关键字
不允许使用mysql的保留关键字
不允许使用连续的下划线,因为Django的查询语法就是连续的下划线
AutoField:自动增长的IntegerField, 不指定时Django会自动创建属性名为id的自动增长属性
BooleanField:布尔字段,值为True或False
NullBooleanField:支持Null、True、False三种值
CharField(max_length=20):字符串
参数max_length表示最大字符个数
TextFiled:大文本字段,一般超过4000个字符时使用
IntegerField:整数
DecimalField(max_digits=None, decimal_places=None):可以指定精度的十进制浮点数
参数max_digits表示总位数
参数decimal_places表示小数位数
FloatField():浮点数
DateField[auto_now=False, auto_now_add=False]):日期
参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它
总是使用当前日期,默认为false
参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当
前日期,默认为false
参数auto_now_add和auto_now是相互排斥的,组合将会发生错误
TimeField:参数和DateField一样
DateTimeField:日期时间,参数同DateField
FileField:上传文件字段,以二进制的形式
ImageField:继承于FileField,对上传的内容进行校验,确保是有效的图片
自定义字段类型
class FixedCharField(models.Field):
"""
自定义的char类型的字段类 固定长度
"""
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):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
使用:
class Class(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=25)
# 使用上面自定义的char类型的字段
cname = FixedCharField(max_length=25)
choice参数
用户表
性别
学历
工作经验
是否结婚
是否生子
客户来源
...
针对某个可以列举完全的可能性字段,我们应该如何存储
只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
# 性别
gender_choices = (
(1,'男'),
(2,'女'),
(3,'其他'),
)
gender = models.IntegerField(choices=gender_choices)
# 分数
score_choices = (
('A','优秀'),
('B','良好'),
('C','及格'),
('D','不合格'),
)
# 保证字段类型跟列举出来的元祖第一个数据类型一致即可
score = models.CharField(choices=score_choices,null=True)
添加数据:
models.User.objects.create(username='111',age=18,gender=1)
models.User.objects.create(username='222',age=85,gender=2)
models.User.objects.create(username='333',age=40,gender=3)
models.User.objects.create(username='444',age=45,gender=4)
'''
添加数据时,如果添加了没有列举出来的数字也能存(范围是按照字段类型决定)
'''
取值:
只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.get_gender_display())
'''
有对应关系就拿对应的内容 没有还是本身
'''
ORM事务
在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法
1.装饰器方法:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 这些代码会在一个事务中执行
...
2.with 语句:
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
try:
with transaction.atomic():
pass
except Exception:
pass
ORM执行原生SQL
(1)
from django.db import connection, connections
cursor = connection.cursor()
cursor = connections['default'].cursor()
cursor.execute("原生SQL语句",[1]) # []中的内容会按位置传值给%s
# 查询结果
row = cursor.fetchone()
'''类似于pycharm操作'''
(2)
models.UserInfo.objects.extra(
select={'newid':'select count(1) from app01_usertype where id>%s'},
select_params=[1,], # 按位置传值给select中的%s
where = ['age>%s'],
params=[18,], # 按位置传值给where中的%s
order_by=['-age'],# 指定按这个字段降序,升序把"-"号去掉即可
tables=['app01_usertype'] # 指定表
)
多对多三种创建方式
# 全自动(常见)
orm自动创建第三张表 但是无法扩展第三张表的字段
authors = models.ManyToManyField(to='Author')
# 全手动(使用频率最低)
优势在于第三张表完全自定义扩展性高 劣势在于无法使用外键方法和正反向
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_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
# 半自动(常见)
正反向还可以使用 并且第三张表可以扩展 唯一的缺陷是不能用
add\set\remove\clear四个方法
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(
to='Author',
through='Book2Author', # 指定表
through_fields=('book','author') # 指定字段
)
class Author(models.Model):
name = models.CharField(max_length=32)
'''多对多建在任意一方都可以 如果建在作者表 字段顺序互换即可'''
books = models.ManyToManyField(
to='Author',
through='Book2Author', # 指定表
through_fields=('author','book') # 指定字段
)
#第三张表
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
本文作者:春游去动物园
本文链接:https://www.cnblogs.com/chunyouqudongwuyuan/p/16285499.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步