ORM进阶操作
聚合查询
在ORM中也有相应的方法与MySQL中的聚合函数作用一致。
一共有五个聚合函数:
方法 | 作用 |
---|---|
Max(字段) | 返回字段中数据最大值 |
Min(字段) | 返回字段中数据最小值 |
Sum(字段) | 返回字段中数据总和 |
Count(字段) | 返回字段中数据个数 |
Avg(字段) | 返回字段中数据平均值 |
在使用以上五个聚合函数是需要先导入才能使用:
from django.db.models import Max,Min,Sum,Count,Avg
使用:统计Student表中score字段各项数据
res = models.Student.objects.aggregate(
Max('score'),
Min('score'),
Sum('score'),
Avg('score'),
Count('score')
)
分组查询
ORM分组查询使用方法annotate()。
现有如下表:
- 作者(编号,姓名,性别)
- 书本(编号,书名,价格,作者编号)
以字段为单位分组
要求:统计作者中各个性别的人数。
res = models.Author.objects.values('sex').annotate(sex_count=Count('pk')).values('sex','sex_count')
print(res)
以表为单位做分组
要求:统计每个作者出的书本数量。
res = models.Author.objects.annotate(book_count=Count('book__pk')).values('name','book_count')
print(res)
上述代码表示对作者表进行分组,并对书本编号进行统计,annotate()中的book_count是自己起的别名。
要求:统计每个作者出的最便宜的价格。
res = models.Author.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)
F与Q查询
F方法
如果我们想要让两个字段进行比较筛选,或者是让字段数据在原来的基础上继续修改,这时就需要用到F()方法了。
导入:
from django.db.models import F
查询学生中数学成绩(math)高于英语(english)的学生:
res = models.Student.objects.filter(math__gt=F('english'))
把所有书本的价格提升10元:
models.Book.objects.update(price=F('price')+10)
把所有书本的后面加上"(爆款)"(字符串拼接需要额外两个方法):
from django.db.models import Value
from django.db.models.functions import Concat
models.Book.objects.update(name=Concat(F('name'), Value('(爆款)')))
Q方法
filter()方法可以填写多个条件,每个条件逗号隔开,条件之间默认是and关系,无法直接修改。
如果想要让多个条件之间有更多的关系(比如or、not),需要用到Q方法。
使用Q方法后,,(逗号)隔开代表and,|(管道符)隔开代表or,~(波浪号)隔开代表not。
导入:
from django.db.models import Q
查询书本价格(price)大于50或者数量(quantity)大于50的书本
res = models.Book.objects.filter(
Q(price__gt=50) | Q(quantity__gt=50)
)
查询书本名称(name)不包含'J'的书本:
res = models.Book.objects.filter(
~Q(name__contains='J')
)
进阶
我们都知道,filter()内部不能使用字符串传参,比如filter('price'=50)这种,也就是说我们无法让用户自己输入字段名和字段值筛选数据,但是使用Q方法就可以做到。
q_obj = Q() # 获取Q对象
field = input('请输入字段名:')
value = input('请输入字段值:')
q_obj.children.append((field,value)) # 添加条件
res = models.Book.objects.filter(q_obj) # 筛选
print(res)
还可以设置条件之间的关系:
q_obj = Q()
q_obj.connector = 'or' # 用于表示条件之间的关系,默认为and
q_obj.children.append(('price__gt',50))
q_obj.children.append(('quantity__gt',50))
res = models.Book.objects.filter(q_obj)
print(res.query)
ORM查询优化
1.ORM查询默认都是惰性查询(能不消耗数据库资源就不消耗)
编写ORM语句并不会直接使用SQL语句,只有后续的代码用到了才会执行。
2.ORM查询默认自带分页功能(尽量减轻单次查询数据的压力)
每次执行SQL都会限制查询数据数量。
除了values()可以查询指定字段外,还有only()、defer()、select_related()、prefetch_related()。
only()与defer()
only()
only()返回对象结果集,获取only()中指定的字段不会再次执行SQL语句,但是如果获取没有指定的字段,会去执行SQL语句获取结果。
res = models.Book.objects.only('name','price')
for i in res:
print(i.quantity) # 获取没有指定的字段
defer()
defer()与only()刚好相反,获取defer()中指定的字段会再次执行SQL语句获取结果,但是如果获取没有指定的字段,不会去执行SQL语句。
res = models.Book.objects.defer('name','price')
for i in res:
print(i.name) # 获取指定的字段
select_related()与prefetch_related()
select_related()
select_related()括号内只能传一对一和一对多字段,不能传多对多字段或者其他字段。
相当于连表操作,使用join一次性连接之后的大表中所有的数据全部封装到数据对象中,后续使用对象跨表查询都不会执行SQL语句。
res = models.Book.objects.select_related('author')
for i in res:
print(i.name)
print(i.author.name)
prefetch_related()
prefetch_related()括号内只能传一对一、一对多、多对多字段,不能传其他字段。
相当于子查询,会分开操作查询语句,将多次查询之后的结果封装到数据对象中,后续使用对象跨表查询都不会执行SQL语句。
res = models.Book.objects.prefetch_related('author')
for i in res:
print(i.name)
print(i.author.name)
ORM常见字段
常见字段 | 描述 |
---|---|
AutoField() | 自动增长int类型 |
CharField(max_length=) | varchar类型,必须提供max_length参数指定长度 |
IntergerField() | int类型 |
DecimalField(max_digits=, decimal_places=) | decimal类型,总位数max_digits,小数位数decimal_places |
DateField() | date类型,提供auto_now和auto_now_add参数 |
DateTimeField() | datetime类型,提供auto_now和auto_now_add参数 |
BigIntergerField() | bigint类型 |
BooleanField() | 布尔值,存0和1 |
TextField() | 存储大段文本 |
FileField() | 传文件时会自动保存到指定位置并只存文件路径 |
EmailField() | varchar(254) |
ForeignKey(to=) | 一对多外键关系,to指定有关系的表 |
OneToOneField(to=) | 一对一外键关系,to指定有关系的表 |
ManyToManyField(to=) | 多对多外键关系,to指定有关系的表 |
自定义字段类型
自定义一个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
字段重要参数
重要参数 | 描述 |
---|---|
primary_key | 是否为主键 |
null | 是否可以为空 |
unique | 是否唯一 |
db_index | 是否设置索引 |
default | 设置默认值 |
max_length | 设置长度 |
verbose_name | 给字段起别名 |
choices | 给定一些选择,有对应关系就拿,没有还是本身 |
日期字段参数 | |
auto_now | 是否每次更新数据时都要更新该字段 |
auto_now_add | 是否添加数据时将当前时间添加到该字段 |
Decimal字段参数 | |
max_digits | 设置Decimal字段总位数 |
decimal_places | 设置Decimal字段小数位数 |
外键字段 | |
to | 设置要关联的表 |
to_fileld | 设置要关联的表的字段 |
on_delete | 设置当删除关联表中的数据时,当前表与其关联的行的行为 |
db_constraint | 是否在数据库中创建外键约束,默认为True |
choices参数
设置
class User(models.Model):
gender_choice = (
(1, '男性'),
(2, '女性')
)
gender = models.IntegerField(choices=gender_choice)
取值的时候使用对象.get_xxx_display()来获取choices后面具体的值,当choices中没有该数据对应的时候获取的仍是数字自己
User表
id | gender |
---|---|
1 | 1 |
2 | 4 |
user_obj = models.Userinfo.objects.get(pk=1)
print(user_obj.get_gender_display()) # 男
user_obj = models.Userinfo.objects.get(pk=2)
print(user_obj.get_gender_display()) # 4
on_delete参数
on_delete有如下选择:
on_delete值 | 描述 |
---|---|
models.CASCADE | 删除关联数据,与之关联也删除 |
models.DO_NOTHING | 删除关联数据,引发错误IntegrityError |
models.PROTECT | 删除关联数据,引发错误ProtectedError |
models.SET_NULL | 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) |
models.SET_DEFAULT | 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) |
models.SET | 删除关联数据,与之关联的值设置为指定值或是可执行对象的返回值 |
事务操作
使用:
from django.db import transaction
with transaction.atomic():
pass
with上下文管理开始执行代表事务开始,中间报错自动回滚,结束with语句自动提交事务。
ORM执行原生SQL
方式一:
from django.db import connection, connections
cursor = connection.cursor()
cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
cursor.fetchone()
方式二:
models.UserInfo.objects.extra(
select={'newid':'select count(1) from app01_usertype where id>%s'},
select_params=[1,],
where = ['age>%s'],
params=[18,],
order_by=['-age'],
tables=['app01_usertype']
)
多对多三种创建方式
全自动
orm自动创建第三张表,但是无法扩展第三张表的字段。
class Student(models.Model):
courses = models.ManyToManyField(to='Course')
全手动
优势在于第三张表完全自定义扩展性高,劣势在于无法使用外键方法和正反向查询。
class Student(models.Model):
title = models.CharField(max_length=32)
class Course(models.Model):
name = models.CharField(max_length=32)
class Student2Course(models.Model):
student = models.ForeignKey(to='Student')
course = models.ForeignKey(to='Course')
半自动
正反向查询还可以使用,并且第三张表可以扩展,唯一的缺陷是不能用add\set\remove\clear四个方法。
class Student(models.Model):
name = models.CharField(max_length=32)
courses = models.ManyToManyField(
to='Course',
through='Student2Course', # 指定表
through_fields=('student', 'course') # 指定字段,注意顺序
)
class Course(models.Model):
name = models.CharField(max_length=32)
class Student2Course(models.Model):
student = models.ForeignKey(to='Student')
course = models.ForeignKey(to='Course')
多对多建在任意一方都可以,如果建在Course表,字段顺序互换即可。