数据库操作
# 数据库操作
一旦创建 数据模型后,Django 自动给予你一套数据库抽象 API,允许你创建(create
),检索(retrieve
),更新(update
)和删除(delete
)对象。
为了方便调试我们通过下面的命令进入交互式python命令行:
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)
2
# 创建对象
创建一个模型对象,可以直接通过关键字参数实例化,然后调用save()
方法将其存入数据库。
from crm.models import Student
s = Student(name='心蓝', age=18)
s.save()
2
3
这在幕后执行了 INSERT
SQL 语句。
django在你显示的调用save()
才会操作数据库。save()
方法没有返回值。
还有以一种创建对象并一步到位的保存方法是create
。
# 使用create方法,create方法直接写入数据库
st = Student.objects.create(name='赵六')
2
还可以使用get_or_create
方法,先查询,没有才创建,但是如果找到了多个会引发MultipleObjectsReturned
错误。
Student.objects.get_or_create(name='xinlan')
(<Student: Student object (1)>, False)
2
返回值是一个元组,第一个元素是模型对象,如果是查询返回的,第二个元素为False,如果是新创建则为True。
# 批量创建
批量创建对象可以利用上面的方法通过for循环来实现,但是会执行多条sql语句,不够高效。
bulk_create(objs, batch_size=None, ignore_conflicts=False)
这个方法可以有效的将提供的对象插入数据表(一般来说,不管多少个对象,只需进行一次查询),并以列表形式返回创建的对象,顺序与提供的相同:
Students.objects.bulk_create([
Student(name='张三'),
Student(name='李四')
])
2
3
4
但是需要注意:
- 模型的
save()
方法不会被调用,pre_save
和post_save
信号将不会被发送。 - 在多表继承的模式下,它不能与子模型一起工作
- 如果模型的主键是一个
AutoField
,主键属性只能在某些数据库(目前是PostgreSQL,MariaDB 10.5+,和 SQLite 3.35+)上检索到。在其他数据库中,它将不会被设置。 - 对于多对多的关系,它行不通。
batch_size
参数控制在一次查询中创建多少对象。默认情况是在一个批次中创建所有对象,但 SQLite 除外,默认情况是每个查询最多使用 999 个变量。
在支持它的数据库上(除了Oracle),将 ignore_conflicts
参数设置为 True
告诉数据库忽略插入任何不合格的约束条件的行,如重复的唯一值。启用该参数会禁用在每个模型实例上设置主键(如果数据库正常支持的话)。
# 更新对象
要将修改保存至数据库中已有的某个对象,使用save()
方法。
有一个已经存入数据库中的Student
实例s
,修改其年龄,并在数据库中更新其记录:
s.age = 19
s.save()
2
在幕后,执行了update
语句。
# 指定要更新的字段
直接调用save()
方法更新时,在sql中会更新所有字段。
可以在调用save()
方法时传递给参数update_fields
一个字段名列表,那么只有列表中命名的字段才会被更新,而不是所有的字段都被更新,可以轻微提升性能优势。
update_fields
参数可以是任何包含字符串的可迭代对象。一个空的update_fields
可迭代对象将跳过保存。值为None
将对所有字段进行更新。
s.name = '心蓝'
s.save(update_fields=['name']) # 在update语句上只会更新name字段
2
# 一次更新多个对象
有时候,你想统一设置查询集中的所有对象的某个字段,你可以通过update()
方法。例如:
Student.object.all().update(sex=1)
方法 update()
立刻被运行,并返回匹配查询调节的行数(若某些行早已是新值,则可能不等于实际匹配的行数)。
要认识到 update()
方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的save()
方法,或发射pre_save
或post_save
信号,或使用auto_now
字段选项。
若想保存查询集中的每项,并确保调用了每个实例的save()
方式,你并不需要任何特殊的函数来处理问题。迭代它们,调用它们的save()
方法:
for item in my_queryset:
item.save()
2
# 检索对象
# 管理器
要从数据库检索对象,要通过模型类的Manager
构建一个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."
2
3
4
5
6
7
# QuerySet
一个QuerySet
代表来自数据库中对象的一个集合,它可以迭代,支持切片,不支持负索引,可以通过list
将其转换成列表。在SQL
的层面上,QuerySet
对应SELECT
语句。
# 检索全部对象
从数据库中检索对象最简单的方式就是检索全部。只需要在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
2
3
# 过滤
all()
返回的QuerySet
包含了数据表中所有的对象。通过下面的两个方法可以对QuerySet
进行过滤。
filter(**kwargs)
返回一个新的QuerySet
,包含的对象满足给定查询参数。
exclude(**kwargs)
返回一个新的QuerySet
,包含的对象不满足给定查询参数。
例如查询所有叫心蓝的学生
Student.objects.filter(name="心蓝")
# 或者
Student.objects.all().filter(name='心蓝')
2
3
# 检索单个对象
filter()
总是返回一个QuerySet
,即便只有一个对象满足查询条件——这种请情况下,QuerySet
只包含一个元素。
如果知道只有一个对象满足查询条件,可以在管理器上使用get()
方法,它会直接返回这个对象:
s = Student.objects.get(pk=1) # 如果不存在会抛出异常
除了get
方法之外,还有一些快捷方法获取单个对象。
first()
Student.objects.first() # 获取第一个对象
last
Student.objects.last() # 获取最后一个对象
# 排序
order_by(*fields)
,会根据给定字段排序,默认QuerySet按照模型类中的Meta类中定义的来排序。管理器和QuerySet上都可以调用。
Student.objects.all().order_by('name') # 根据名字升序排序
Student.objects.order_by('-name') # 根据名字降序排序
2
# 切片
使用python列表的切片的语法来获取部分数据,它等价于SQL中LIMIT与OFFSET子句。
Student.objects.all()[:5] # 获取前5条 等价于 LIMIT 5
Student.objects.all()[2:5] # 等价于 LIMIT 3 OFFSET 2
2
# 选择字段
values(fields)
返回一个QuerySet
,这个QuerySet
返回一个字典列表,而不是数据对象。参数fields
指定了select
语句中我们想要限制查询的字段。返回的字典中只会包含我们指定的字段。如果不指定,则包含所有字段。
Student.objects.values('name')
<QuerySet [{'name': '张柏芝'}, {'name': '刘德华'}, {'name': '心蓝'}]>
2
3
only(fields)
返回一个QuerySet
,这个QuerySet
返回一个对象列表。参数fields
指定了select
语句中我们想要限制查询的字段。注意,only
一定会包含主键字段。
Student.objects.only('name')
<QuerySet [<Student: 张柏芝>, <Student: 刘德华>, <Student: 心蓝>]>
2
defer(fields)
返回一个QuerySet
,这个QuerySet
返回一个对象列表。参数fields
指定了select
语句中我们想要排除的查询的字段。注意,defer
一定会包含主键字段。
Student.objects.defer('c_time')
only
和defer
返回的对象还是可以正常访问没有包含在select
语句中的字段,只是会再次查询数据库。
# 条件查询
在filter
,exclude
,get
中可以接收参数实现各种比较条件的查询。
- exact
准确匹配.如果给的值是None,它会被解释成SQL NULL 看下面的案例
Student.objects.get(id__exact=14)
Student.objects.get(qq__exact=None)
2
id__exact=14
等价于id = 14
,默认情况不带exact
- iexact 不分大小写的匹配。
Blog.objects.get(name__iexact='beatles blog')
Blog.objects.get(name__iexact=None)
2
- in
在一个给定的可迭代对象中,通常是一个列表,元组,或queryset。虽然不是经常用但是字符串也可以。
Entry.objects.filter(id__in=[1, 3, 4])
Entry.objects.filter(headline__in='abc')
2
- gt
大于
Entry.objects.filter(id__gt=4)
- gte 大于等于
- lt 小于
- lte 小于等于
- range
范围区间
Student.objects.filter(age__range=(18, 20))
更多字段参考官方文档 (opens new window)
# 条件组合
- 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))
2
3
4
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))
2
3
SQL 等价于:
SELECT ... WHERE x=1 OR y=2
# 聚合查询
count 统计数量
# 统计所有学生的数量 Students.objects.count() # 统计所有男生的数量 Students.objects.filter(sex=1).count()
1
2
3
4Avg 平均值
# 计算同学们的年龄平均值 from django.db.models import Avg Students.objects.aggregate(age_avg=Avg('age'))
1
2
3Max 最大值
# 找到最大年龄的学生 from django.db.models import Max Students.objects.aggregate(Max('age'))
1
2
3Min 最小值
# 找到最小年龄的学生 from django.db.models import Min Students.objects.aggregate(Min('age'))
1
2
3Sum 求和
# 计算缴费总金额 from django.db.models import Sum Enroll.objects.aggregate(Sum('pay'))
1
2
3
# 分组
分组,聚合,需要结合values,annotate和聚合方法看下面的案例
# 查询男生女生多少人
Student.objects.values('sex').annotate(Count('sex')) # annotate 默认按照主键分组
2
3
更多聚合参考官方文档 (opens new window)
# 删除对象
通常,删除方法被命名为 delete()
。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。例子:
s = Student.objects.get(pk=1)
s.delete()
2
# 关联对象操作
# 多对一
# 正向
一个模型如果有一个外键字段,通过这个模型对外键进制操作叫做正向
# 更新,修改
# 给学生设置渠道属性
# 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()
2
3
4
5
6
7
8
9
10
11
==注意,主表表数据要入库(也即是Channel对象要save()之后)之后,引用表才能创建==
ForeignKey字段的更新,和普通字段没有什么区别。
# 删除
如果一个外键字段有null=True
的设置(即,它允许空值),您可以指定None
来删除关系。例如:
s1.channel = None
s1.save()
2
# 查
# 查询所有百度渠道的学生
Student.objects.filter(channel__name='百度') # 跨表查询
channel = Channel.objects.get(name='百度')
channel.student_set.all() # 反向查询
2
3
4
外键字段对象的属性可以通过两个下划线来获取。
# 反向
一个模型如果被另外一个模型外键关联,通过这个模型对关联它的模型进行操作叫做反向。
如果一个模型有一个ForeignKey,那么这个外键模型的实例将可以访问一个返回第一个模型的所有实例的管理器。默认情况下,这个管理器名为foo_set
,其中foo
是源模型名,小写。这个管理器返回queryset,可以按照前面检索对象一节中描述的那样对其进行过滤和操作。
==通过在定义字段的时候设置参数 related_name 可以替代上面的管理器名==
# 增
# 1.通过主表创建从表数据
new_student = ch.student_set.create(name='韩梅梅', age=16, sex=0)
# 2.增加多条数据
ch.student_set.add(s1,s2,s3)
# 会将s1,s2,s3全部加入关联对象集合
2
3
4
5
# 删除
# 1. 从相关对象中移除指定的模型对象
# 清除某个渠道中的某些学生
ch.student_set.remove(s1, s2, ...)
# 2. 从相关对象中删除所有的 对象
# 清除某个渠道中的所有学生
ch.student_set.clear()
2
3
4
5
6
# 改
# 替换对象集
ch.student_set.set([s1, s2])
# 如果clear 可以调用 先clear再添加,如果过不行就直接添加
2
3
# 查
# 1. 查询所有
ch.student_set.all()
# 2. 条件查询
ch.student_set.filter(name='心蓝')
# 和objects一样的使用
2
3
4
5
# 多对多
多对多两端都可以获得另一端的自动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
c1.students.add(s1,s2,s3)
# 学生s1报名的课程有哪些
s1.course_set.all()
# 课程c1有哪些学生报名
2
3
4
5
6
7
8
和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])
2
当多对多的关系指定了中间表的时候,还可以通过中间表对象像普通的多对一关系一样操作。看下面的案例:
# 创建一个报名记录,s1学员报名了c1课程
e = Enroll()
e.course = c1
e.student = s1
e.save()
2
3
4
5
你可能会问,既然这样,那指定中间表还要多对多字段干什么?因为查询非常方便,看下面的案例:
# 查询报名某课程的学生
c1.students.all()
# 查询某学生报名的课程
s1.course_set.all()
2
3
4
使用ManyToManyField
之后,这两个表可以有api直接访问。
# 一对一
一对一关系非常类似于多对一关系。如果模型上定义了一对一字段,那么该模型的实例将可以通过模型的一个简单属性访问相关对象。例如:
# 给某学生添加学生详情
s1 = Student.objects.first()
sd = StudentDetail.objects.create(student=s1,city='北京',salary=50000)
<Student: yoyo>
2
3
4
不同之处在于反向
查询。一对一关系中的相关模型可以通过相关模型的小写属性名访问一个Manager
对象,但是这个Manager
只是一个对象,而不是一个对象集合:
s1.studentdetail
<StudentDetail: yoyo>
2
如果没有给这个关系分配对象,Django将抛出一个DoesNotExist异常。
# 跨表查询
Django提供了一种强大而直观的方法,可以在查询中“跟踪”关系,在幕后自动处理为SQL连接。要跨越关系,只需使用跨模型的相关字段的字段名,以双下划线分隔,直到您到达您想要的字段为止。
例如,查询男生都报名了什么课程
Course.objects.filter(students__sex=1).distinct()
这个关系要多深就可以有多深。
它也向后工作。要引用反向
关系,只需使用模型的小写名称。
这个例子查询所有报名了python
课程的学员:
Student.objects.filter(course__name__contains='python')
我们再看一个例子:
查询所有报名了python的百度渠道的学员
Student.objects.filter(course__name__contains='python', channel__name='百度')
# 执行原生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
# 2. 执行原生查询
直接调用原生数据驱动执行SQL。
from django.db import connection
def my_custom_sql():
with connection.cursor() as cursor:
cursor.execute("select * from crm_student where age= %s", [18])
row = cursor.fetchone()
return row
2
3
4
5
6
7
8
本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。
