前言:
一旦创建 数据模型后,Django 自动给予你一套数据库抽象 API,允许你创建(create),检索(retrieve),更新(update)和删除(delete)对象。
为了方便调试我们通过下面的命令进入交互式python命令行:
pip install ipython
python manage.py shell
我们使用这个命令而不是简单的使用python,是因为 manage.py
会设置 DJANGO_SETTINGS_MODULE
环境变量,这个变量会让 Django 根据项目配置文件来设置 Python 包的导入路径。
queryset
对象的query
属性可以看到执行的sql
,但是只能对queryset
对象使用,所以insert
、update
就不能使用。
在日志等级debug=True的情况下,下面的代码可以打印出所有执行过的sql语句。
from django.db import connection
print(connection.queries)
一、增(创建对象)
1.1 单个创建
创建一个模型对象,可以直接通过关键字参数实例化,然后调用save()
方法将其存入数据库。
from crm.models import Student
s = Student(name='心蓝', age=18)
s.save()
这在幕后执行了 INSERT SQL
语句。
django在你显示的调用save()
才会操作数据库。save()
方法没有返回值。
还有以一种创建对象并一步到位的保存方法是create
。
# 使用create方法,create方法直接写入数据库
st = Student.objects.create(name='赵六')
还可以使用get_or_create
方法,先查询,没有才会创建,但是如果找到了多个会引发MultipleObjectsReturned
错误。
Student.objects.get_or_create(name='xinlan')
(<Student: Student object (1)>, False)
返回值是一个元组,第一个元素是模型对象,如果是查询返回的,第二个元素为False,如果是新创建则为True。
1.2 批量创建
批量创建对象可以利用上面的方法通过for
循环来实现,但是会执行多条sql
语句,不够高效。
bulk_create(objs, batch_size=None, ignore_conflicts=False)
这个方法可以有效的将提供的对象插入数据表(一般来说,不管多少个对象,只需进行一次查询),并以列表形式返回创建的对象,顺序与提供的相同:
Students.objects.bulk_create([
Student(name='张三'),
Student(name='李四')
])
但是需要注意:
- 模型的
save()
方法不会被调用,pre_save
和post_save
信号将不会被发送。 - 在多表继承的模式下,它不能与子模型一起工作
- 如果模型的主键是一个
AutoField
,主键属性只能在某些数据库(目前是PostgreSQL,MariaDB 10.5+,和 SQLite 3.35+)上检索到。在其他数据库中,它将不会被设置。 - 对于多对多的关系,它行不通。
batch_size
参数控制在一次查询中创建多少对象。默认情况是在一个批次中创建所有对象,但 SQLite 除外,默认情况是每个查询最多使用 999 个变量。
在支持它的数据库上(除了Oracle),将 ignore_conflicts
参数设置为 True
告诉数据库忽略插入任何不合格的约束条件的行,如重复的唯一值。启用该参数会禁用在每个模型实例上设置主键(如果数据库正常支持的话)。
二、改(更新对象)
2.1 全部字段进行更改
要将修改保存至数据库中已有的某个对象,使用save()
方法。
有一个已经存入数据库中的Student
实例s
,修改其年龄,并在数据库中更新其记录:
s.age = 19
s.save()
在幕后,执行了update
语句,直接调用save()
方法更新时,在sql中会更新所有字段。
2.2 指定字段进行更改
直接调用save()
方法更新时,在sql中会更新所有字段。
可以在调用save()
方法时传递给参数update_fields
一个字段名列表,那么只有列表中命名的字段才会被更新,而不是所有的字段都被更新,可以轻微提升性能优势。
update_fields
参数可以是任何包含字符串的可迭代对象。一个空的update_fields
可迭代对象将跳过保存。值为None
将对所有字段进行更新。
s.name = '心蓝'
s.save(update_fields=['name']) # 在update语句上只会更新name字段
2.3 一次更新多个对象
一次更新多个对象
有时候,你想统一设置查询集中的所有对象的某个字段,你可以通过update()
方法。例如:
Student.objects.all().update(sex=1)
方法 update()
立刻被运行,并返回匹配查询调节的行数(若某些行早已是新值,则可能不等于实际匹配的行数)。
要认识到 update()
方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的save()
方法,或发射pre_save或post_save
信号,或使用auto_now
字段选项。
若想保存查询集中的每项,并确保调用了每个实例的save()
方式,你并不需要任何特殊的函数来处理问题。可以迭代它们,调用它们的save()方法:
for item in my_queryset:
item.save()
三、查(检索对象)
3.1 管理器
要从数据库检索对象(数据),要通过模型类的Manager(默认:objects)
构建一个QuerySet
。
每个模型至少有一个Manager
,默认名称是objects
。像这样直接通过模型类使用它们:
>>> Student.objects
<django.db.models.manager.Manager at 0x1066850a0>
>>> s = Student(name='xinlan', age=18)
>>> s.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Student instances."
3.2 QuerySet
一个QuerySet
代表来自数据库中对象(数据)的一个集合,它可以迭代,支持切片,不支持负索引,可以通过list
将其转换成列表。在SQL的层面上,QuerySet对应SELECT语句。
3.2.1 检索全部对象
从数据库中检索对象最简单的方式就是检索全部。只需要在Manager
上调用all()
方法:
all_students = Student.objects.all()
方法all()
返回一个包含数据库表中所有对象的QuerySet
对象。
可以通过打印query
属性,查看当前QuerySet
对象将执行的查询语句。
>>> print(all_studnets.query)
SELECT "tb_student"."id", "tb_student"."name", "tb_student"."age", "tb_student"."sex", "tb_student"."phone", "tb_student"
."c_time", "tb_student"."channel_id" FROM "tb_student" ORDER BY "tb_student"."c_time" DESC
3.2.2 过滤
all()
返回的QuerySet
包含了数据表中所有的对象。通过下面的两个方法可以对QuerySet
进行过滤。
filter(**kwargs)
返回一个新的QuerySet
,包含的对象 满足 给定查询参数。
exclude(**kwargs)
返回一个新的QuerySet
,包含的对象 不满足 给定查询参数。
例如:查询所有叫心蓝的学生
Student.objects.filter(name="心蓝")
# 或者
Student.objects.all().filter(name='心蓝')
3.2.3 检索单个对象
filter()
总是返回一个QuerySet
,即便只有一个对象满足查询条件——这种请情况下,QuerySet只包含一个元素。
get()
该方法是根据特定的条件进行查询的
如果知道只有一个对象满足查询条件,可以在管理器上使用get()
方法,它会直接返回这个对象:
s = Student.objects.get(pk=1) # 如果不存在会抛出异常
first()
获取第一个对象(数据)
Student.objects.first() # 获取第一个对象
last()
获取最后一个对象(数据)
Student.objects.last() # 获取最后一个对象
3.2.4 排序
order_by(*fields)
,会根据给定字段排序,默认QuerySet
按照模型类中的Meta
类中定义的来排序。管理器和QuerySet
上都可以调用。
Student.objects.all().order_by('name') # 根据名字升序排序
Student.objects.order_by('-name') # 根据名字降序排序
3.2.5 切片(分页)
使用python列表的切片的语法来获取部分数据,它等价于SQL中LIMIT
与OFFSET
子句。
Student.objects.all()[:5] # 获取前5条 等价于 LIMIT 5
Student.objects.all()[2:5] # 等价于 LIMIT 3 OFFSET 2
3.2.6 选择字段
values()
values(*fields, **expressions)
返回一个QuerySet
,这个QuerySet
返回一个字典列表,而不是数据对象。参数fields指定了select语句中我们想要限制查询的字段。返回的字典中只会包含我们指定的字段。如果不指定,则包含所有字段。
Student.objects.values('name')
<QuerySet [{'name': '张柏芝'}, {'name': '刘德华'}, {'name': '心蓝'}]>
only(*fields)
返回一个QuerySet
,这个QuerySet
返回一个对象列表。参数fields
指定了select
语句中我们想要限制查询的字段。注意,only一定会包含主键字段。
Student.objects.only('name')
<QuerySet [<Student: 张柏芝>, <Student: 刘德华>, <Student: 心蓝>]>
defer(*fields)
返回一个QuerySet
,这个QuerySet
返回一个对象列表。参数fields
指定了select
语句中我们想要排除的查询的字段。注意,defer一定会包含主键字段。
Student.objects.defer('c_time')
only和defer返回的对象还是可以正常访问没有包含在select语句中的其他字段,只是会再次查询数据库。
3.2.7 条件查询
在filter
,exclude
,get
中可以接收参数实现各种比较条件的查询。
exact:精确匹配
在字段名后加_ _exact
注意:是两个下划线
准确匹配.如果给的值是None
,它会被解释成SQL NULL
看下面的案例
Student.objects.get(id__exact=14)
Student.objects.get(qq__exact=None)
id__exact=14等价于id = 14,默认情况不带exact
iexact
不分大小写的匹配。
Blog.objects.get(name__iexact='beatles blog')
Blog.objects.get(name__iexact=None)
in
在一个给定的可迭代对象中,通常是一个列表,元组,或queryset。虽然不是经常用但是字符串也可以。
Entry.objects.filter(id__in=[1, 3, 4])
Entry.objects.filter(headline__in='abc')
gt
大于
Entry.objects.filter(id__gt=4)
gte 大于等于
lt 小于
lte 小于等于
range
范围区间
Student.objects.filter(age__range=(18, 20))
更多字段参考官方文档
3.2.8 条件组合
AND
使用 SQL AND 操作符将两个 QuerySet 组合起来。
以下的都是相同的
Model.objects.filter(x=1) & Model.objects.filter(y=2)
Model.objects.filter(x=1, y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) & Q(y=2))
SQL 等价于:
SELECT ... WHERE x=1 AND y=2
OR
使用 SQL OR 操作符将两个 QuerySet 组合起来。
以下的都是相同的:
Model.objects.filter(x=1) | Model.objects.filter(y=2)
from django.db.models import Q
Model.objects.filter(Q(x=1) | Q(y=2))
SQL 等价于:
SELECT ... WHERE x=1 OR y=2
3.2.9 聚合查询
count 统计数量
# 统计所有学生的数量
Students.objects.count()
# 统计所有男生的数量
Students.objects.filter(sex=1).count()
Avg 平均值
# 计算同学们的年龄平均值
from django.db.models import Avg
Students.objects.aggregate(age_avg=Avg('age'))
# aggregate():返回对 QuerySet 计算的聚合值(平均值、总和等)的字典
Max 最大值
# 找到最大年龄的学生
from django.db.models import Max
Students.objects.aggregate(Max('age'))
Min 最小值
# 找到最小年龄的学生
from django.db.models import Min
Students.objects.aggregate(Min('age'))
Sum 求和
# 计算缴费总金额
from django.db.models import Sum
Enroll.objects.aggregate(Sum('pay'))
3.2.10 分组
分组,聚合,需要结合values
,annotate
和聚合方法
看下面的案例
# 查询男生女生多少人,因为是按照性别进行分组,所以.values('sex')
Student.objects.values('sex').annotate(Count('sex'))
# annotate 默认按照主键分组
Student.objects.values('sex').annotate(sex_=Count('sex'))
# sex_:给聚合函数计算出的值 所在的列,给一个列名
# 案例:
s = Student.objects.all().values('age', 'name').annotate(age_=Count('age'))
print(s)
# out:<QuerySet [{'age': 18, 'name': '心蓝', 'age_': 1}, {'age': 19, 'name': '张三', 'age_': 1}, {'age': 19, 'name': '李四', 'age_': 1}, {'age': 29, 'name': '李四', 'age_': 9}]>
print(connection.queries[-1])
# {'sql': 'SELECT `tb_student`.`age`, `tb_student`.`name`, COUNT(`tb_student`.`age`) AS `age_` FROM `tb_student` GROUP BY `tb_student`.`age`, `tb_student`.`name` ORDER BY NULL LIMIT 21', 'time': '0.000'}
更多聚合参考官方文档
四、删(删除)
通常,删除方法被命名为 delete()
。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。例子:
s = Student.objects.get(pk=1)
s.delete()
五、关联对象操作
5.1 多对一
5.1.1 正向
一个模型如果有一个外键字段,通过这个模型对外键进制操作叫做正向
改
# 给学生设置渠道属性
# 1 通过属性赋值的方式
ch = Channel('百度')
ch.save()
s1 = Student(name='心蓝', age=18)
s1.channel = ch
s1.save()
# 2 通过主键的方式
s2 = Student(name='小明', age=19)
s2.channel_id = ch.id
s2.save()
== 注意,主表表数据要入库(也即是Channel对象要save()之后)之后,引用表才能创建 ==
ForeignKey
字段的更新,和普通字段没有什么区别。
删
如果一个外键字段有null=True
的设置(即,它允许空值),您可以指定None
来删除关系。
也就是说,把一个学生对象的channel字段删除,如果想要删除一个外键,需要外键字段有null=True
的设置,例如:
s1.channel = None
s1.save()
查
跨表查询:表名__字段名
# 查询所有百度渠道的学生
Student.objects.filter(channel__name='百度') # 跨表查询
Student.objects.filter(channel__tilte='抖音')
-----
channel = Channel.objects.get(name='百度')
channel.student_set.all() # 反向查询
外键字段对象的属性
可以通过两个下划线
来获取
5.1.2 反向
一个模型(A:主表)如果被另外一个模型(B:从表)外键关联,通过这个模型(A)对关联它的模型(B)进行操作,叫做反向。
在这个模型对象上,有一个反向字段(以关联模型的小写
+_set
构成),所以在Channel
模型的对象上,有一个反向字段student_set
,这个字段是一个关系型管理器,可以操作Student
模型
如果一个模型有一个ForeignKey(B)
,那么这个外键所关联的模型(A)的实例将可以访问一个返回第一个模型(B)的所有实例的管理器。默认情况下,这个管理器名为foo_set
,其中foo
是源模型名(B),小写。这个管理器返回queryset
,可以按照前面检索对象一节中描述的那样对其进行过滤和操作。
==通过在定义字段的时候设置参数 related_name
可以替代上面的管理器名==
见 官方文档
https://docs.djangoproject.com/zh-hans/4.2/ref/models/relations/
增
注意:student表定义了一个外键chanel,则student表为从表,chanel为主表
# 1.通过主表创建从表数据
ch = Channel.objects.get(pk=1)
new_student = ch.student_set.create(name='韩梅梅', age=16, sex=0)
# 2.增加多条数据
ch.student_set.add(s1,s2,s3)
# 会将s1,s2,s3全部加入关联对象集合
删除
该删除的意思是,只是删除两张表的应用关系,不是将数据库中的数据进行删除
remove(*objs, bulk=True)
官方解释:从相关对象集中删除指定的模型对象
# 1. 从相关对象中移除指定的模型对象
# 清除某个渠道中的某些学生
ch.student_set.remove(s1, s2, ...)
# 2. 从相关对象中删除所有的 对象
# 清除某个渠道中的所有学生
ch.student_set.clear()
改
set(objs, bulk=True, clear=False, through_defaults=None)
替换相关对象集
# 替换对象集
ch.student_set.set([s1, s2])
# 如果clear 可以调用 先clear再添加,如果过不行就直接添加
查
# 1. 查询所有
ch.student_set.all()
# 2. 条件查询
ch.student_set.filter(name='心蓝')
# 和objects一样的使用
自定义 反向字段名
在外键字段中,使用参数realted_name
可以自定义反向字段名称。
channel = models.ForeignKey('Channel', ... related_name='students')
那么
ch1.students.all() # 那么就要通过students属性进行操作
5.2 多对多
前言
https://www.yuque.com/gululu-lhhoz/ln9o1q/so8o9yhn52kc2vwh
多对多两端都可以获得另一端的自动API访问。该API的工作原理类似上面的反向多对一关系,都是一个多对多的管理器对象。
定义ManyToManyField
字段的模型使用该字段本身的名称,反向
模型使用关系模型的名称小写加上_set。看下面的例子更容易理解:
# 先创建几个学生s1,s2,s3 几个课程 c1,c2,c3
# 学生s1报名课程c1,c2,c3
s1.course_set.add(c1,c2,c3)
# 学生s1,s2,s3报名课程c1,【course是定义ManyToMany字段的】
c1.students.add(s1,s2,s3)
# 学生s1报名的课程有哪些
s1.course_set.all()
# 课程c1有哪些学生报名,【students是Course模型中定义的多对多字段】
c1.students.all()
和ForeignKey
一样ManyToMany
也可以指定related_name
。在上面的案例中,如果定义在Course
中的ManyToManyField
指定了related_name='courses',那么每一个Student
对象都会有一个Courses
的属性替代course_set
。
和一对多关系不同的是,除了模型实例外,多对多关系上的add()
,set(),
和remove()
方法还接受主键值。
例如这些set()调用的工作方式是相同的:
c1.students.set([s1,s2,s3])
c1.students.set([s1.pk,s2.pk,s3,pk])
当多对多的关系指定了中间表的时候,还可以通过中间表对象像普通的多对一关系一样操作。看下面的案例:
# 创建一个报名记录,s1学员报名了c1课程
e = Enroll()
e.course = c1
e.student = s1
e.save()
你可能会问,既然这样,那指定中间表还要多对多字段干什么?因为查询非常方便,看下面的案例:
# 查询报名某课程的学生
c1.students.all()
# 查询某学生报名的课程
s1.course_set.all()
使用ManyToManyField
之后,这两个表可以有api直接访问。
增
增,有两种含义:
1、增,一方面指的是,创建关系对象
2、增,指的是创建关联对象的关系,也就是在第三种表中插入一条数据。
解释:创建一个对象的同时,也会在第三张表中,插入一条数据
1、创建关系对象
# 通过学生创建课程
s1.course_set.create(name='java自动化') # course_set字段名称与多对一的反向关系一致
# 通过课程创建学生
c1.students.create(name='大山')
2、创建关联关系
# 有学生s1,s2,s3
# 有课程c1,c2,c3
# 学生s1, 报名了 c1,c2
s1.course_set.add(c1,c2)
# 学生s2,s3 报名了课程 c2
c2.students.add(s2,s3)
删
同多对一,也有一个remove,clear
# 学生s1,删掉课程c1的报名
s1.course_set.remove(c1)
# 课程c2,删掉学生s2的包名
c2.students.remove(s2)
# 也有清空,使用cleare
# 学生s,删掉所有的包名
s.course_set.clear()
# 课程c,删除所有的包名
c.students.clear()
改
# 修改学生s1的报名名为 c1,c2,如果学生s2还有其他的包名,会被删掉
s1.course_set.set([c1,c2])
# 修改课程c1的包名为s1,s2,如果还有其他的学生报名了c1,也会被删掉
c1.students.set([s1,s2])
查
一般多对多,就是查询比较多
定义 多对多字段
都是 关系管理器,查询方法和默认管理器一致
# 查报名了某个课程的学生
c1.students.all()
# 查某个学生报名的课程
s1.course_set.all()
# 多对多字段都是关系管理器,查询方法和默认管理器一致
自定义 反向字段名
同 多对一
5.3 一对一
一对一
和多对一
很像。
一对一字段,指向的是关系模型的一个对象。
在被关联的模型对象(Student)上,默认有一个字段,以关联模型名(StudentDetail)的小写作为名称,即studentdetail
。
所以在Student对象上,有一个字段studentdetail
。这个字段指向一个StduentDetail
的对象。
如果Student
对象,还没有被关联,那么引用这个字段,会报RelatedObjectDoesNotExist
的异常。
在StudentDetail
对象上,有一个student
字段,它指向一个Student
的对象。
增
定义了一对一字段的模型,在创建数据的时候,必须传递一对一关系的对象。
# 给某个学生添加学生详情
sd = StudentDetail.objects.create(student=s1, city='长沙', salary=5000)
删
# 删除某个学生的学生详情
s1.studentdetail.delete()
改
# 修改某个学生的学生详情
s1.studentdetail.city = '北京'
s1.studentdetail.save()
# 修改某个学生详情对应的学生的信息
sd.student.name = 'xxx'
sd.student.save()
六、跨表查询
Django提供了一种强大而直观的方法,可以在查询中“跟踪”关系,在幕后自动处理为SQL连接。要跨越关系,只需使用跨模型
的相关字段
的字段名
,以双下划线分隔,直到您到达您想要的字段为止。
黑体字的理解:
举例:
q2 = Course.objects.filter(students__channel__title='百度')
Course
模型中,有个 多对多定义的字段(students),而这个字段,指向的是Student
模型,那么,在Student
模型中,有个 多对一 的字段(channel),而这个字段,指向的是Channel
模型,这个模型中有个字段为 title
,就这样,Course、Student、Channel 三个模型(表)连接完毕,最后,以title
字段进行查询
例如,查询男生都报名了什么课程
Course.objects.filter(students__sex=1).distinct()
这个关系要多深就可以有多深。
它也向后工作。要引用反向
关系,只需 使用模型的小写名称。
这个例子查询所有报名了python课程的学员:
# Course模型中定义了 多对多 的字段
# 要引用 反向 关系,只需 使用模型的小写名称。
# contains:包含,相当于sql中的 like 语句
Student.objects.filter(course__name__contains='python')
# 输出的sql:
# SELECT `tb_student`.`id`, `tb_student`.`name`, `tb_student`.`age`, `tb_student`.`sex`, `tb_student`.`phone`, `tb_student`.`c_time`, `tb_student`.`channel_id` FROM `tb_student` INNER JOIN `tb_entry` ON (`tb_student`.`id` = `tb_entry`.`student_id`) INNER JOIN `tb_course` ON (`tb_entry`.`course_id` = `tb_course`.`id`) WHERE `tb_course`.`name` LIKE BINARY %python%
我们再看一个例子:
查询所有报名了python的百度渠道的学员
# where语句中的and
Student.objects.filter(course__name__contains='python', channel__name='百度')
# 输出的sql
#SELECT `tb_student`.`id`, `tb_student`.`name`, `tb_student`.`age`, `tb_student`.`sex`, `tb_student`.`phone`, `tb_student`.`c_time`, `tb_student`.`channel_id` FROM `tb_student` INNER JOIN `tb_channel` ON (`tb_student`.`channel_id` = `tb_channel`.`id`) INNER JOIN `tb_entry` ON (`tb_student`.`id` = `tb_entry`.`student_id`) INNER JOIN `tb_course` ON (`tb_entry`.`course_id` = `tb_course`.`id`) WHERE (`tb_channel`.`title` = 百度 AND `tb_course`.`name` LIKE BINARY %python%)
通过关系字段加两个下划线进行跨表关系查询。
七、执行原生sql
1. 执行原生查询并返回模型实例
在管理器上调用raw()方法用于执行远程SQL查询,就会返回模型实例,语法格式如下:
Manager.raw(raw_query, params=(), translations=None)
该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet
实例。这个 RawQuerySet
能像普通的 QuerySet
一样被迭代获取对象实例。
for stu in Student.objects.raw('select * from crm_student'):
print(stu)
2. 执行原生查询
直接调用原生数据驱动执行SQL。
with connection.cursor() as cursor:
cursor.execute("select * from tb_student")
one = cursor.fetchone()
print(one)
two = cursor.fetchmany(2)
print(two)
a_ll = cursor.fetchall()
print(a_ll)