心蓝的博客 心蓝的博客
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档

心蓝

人生苦短,我用python
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • 零基础

  • 专项

  • web框架

    • django框架

      • web框架介绍
      • python虚拟环境
      • 创建django项目与应用
      • django中的路由系统
      • web框架设计模式
      • 模板
      • 静态文件引用
      • web应用开发模式
      • 请求和响应
      • 视图
      • ORM与模型
      • 数据库操作
        • 创建对象
          • 批量创建
        • 更新对象
          • 指定要更新的字段
          • 一次更新多个对象
        • 检索对象
          • 管理器
          • QuerySet
          • 检索全部对象
          • 过滤
          • 检索单个对象
          • 排序
          • 切片
          • 选择字段
          • 条件查询
          • 条件组合
          • 聚合查询
          • 分组
        • 删除对象
        • 关联对象操作
          • 多对一
          • 正向
          • 更新,修改
          • 删除
          • 查
          • 反向
          • 增
          • 删除
          • 改
          • 查
          • 多对多
          • 一对一
        • 跨表查询
        • 执行原生SQL
          • 1. 执行原生查询并返回模型实例
          • 2. 执行原生查询
      • 项目实战一
      • 表单
      • RESTful API
      • 项目实战二
      • djangoadmin
    • drf

  • python
  • web框架
  • django框架
心蓝
2022-12-21
目录

数据库操作

# 数据库操作

一旦创建 数据模型后,Django 自动给予你一套数据库抽象 API,允许你创建(create),检索(retrieve),更新(update)和删除(delete)对象。

为了方便调试我们通过下面的命令进入交互式python命令行:

python manage.py shell
1

我们使用这个命令而不是简单的使用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
2

# 创建对象

创建一个模型对象,可以直接通过关键字参数实例化,然后调用save()方法将其存入数据库。

from crm.models import Student
s = Student(name='心蓝', age=18)
s.save()
1
2
3

这在幕后执行了 INSERT SQL 语句。

django在你显示的调用save()才会操作数据库。save()方法没有返回值。

还有以一种创建对象并一步到位的保存方法是create。

# 使用create方法,create方法直接写入数据库
st = Student.objects.create(name='赵六')
1
2

还可以使用get_or_create方法,先查询,没有才创建,但是如果找到了多个会引发MultipleObjectsReturned错误。

 Student.objects.get_or_create(name='xinlan')
 (<Student: Student object (1)>, False)
1
2

返回值是一个元组,第一个元素是模型对象,如果是查询返回的,第二个元素为False,如果是新创建则为True。

# 批量创建

批量创建对象可以利用上面的方法通过for循环来实现,但是会执行多条sql语句,不够高效。

bulk_create(objs, batch_size=None, ignore_conflicts=False)这个方法可以有效的将提供的对象插入数据表(一般来说,不管多少个对象,只需进行一次查询),并以列表形式返回创建的对象,顺序与提供的相同:

Students.objects.bulk_create([
	Student(name='张三'),
	Student(name='李四')
])
1
2
3
4

但是需要注意:

  1. 模型的save()方法不会被调用,pre_save和post_save信号将不会被发送。
  2. 在多表继承的模式下,它不能与子模型一起工作
  3. 如果模型的主键是一个AutoField,主键属性只能在某些数据库(目前是PostgreSQL,MariaDB 10.5+,和 SQLite 3.35+)上检索到。在其他数据库中,它将不会被设置。
  4. 对于多对多的关系,它行不通。

batch_size 参数控制在一次查询中创建多少对象。默认情况是在一个批次中创建所有对象,但 SQLite 除外,默认情况是每个查询最多使用 999 个变量。

在支持它的数据库上(除了Oracle),将 ignore_conflicts 参数设置为 True 告诉数据库忽略插入任何不合格的约束条件的行,如重复的唯一值。启用该参数会禁用在每个模型实例上设置主键(如果数据库正常支持的话)。

更多详情见官方文档 (opens new window)

# 更新对象

要将修改保存至数据库中已有的某个对象,使用save()方法。

有一个已经存入数据库中的Student实例s,修改其年龄,并在数据库中更新其记录:

s.age = 19
s.save()
1
2

在幕后,执行了update语句。

# 指定要更新的字段

直接调用save()方法更新时,在sql中会更新所有字段。

可以在调用save()方法时传递给参数update_fields一个字段名列表,那么只有列表中命名的字段才会被更新,而不是所有的字段都被更新,可以轻微提升性能优势。

update_fields参数可以是任何包含字符串的可迭代对象。一个空的update_fields可迭代对象将跳过保存。值为None将对所有字段进行更新。

s.name = '心蓝'
s.save(update_fields=['name']) # 在update语句上只会更新name字段
1
2

# 一次更新多个对象

有时候,你想统一设置查询集中的所有对象的某个字段,你可以通过update()方法。例如:

Student.object.all().update(sex=1)
1

方法 update() 立刻被运行,并返回匹配查询调节的行数(若某些行早已是新值,则可能不等于实际匹配的行数)。

要认识到 update() 方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的save()方法,或发射pre_save或post_save信号,或使用auto_now字段选项。

若想保存查询集中的每项,并确保调用了每个实例的save()方式,你并不需要任何特殊的函数来处理问题。迭代它们,调用它们的save()方法:

for item in my_queryset:
    item.save()
1
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."
1
2
3
4
5
6
7

# QuerySet

一个QuerySet代表来自数据库中对象的一个集合,它可以迭代,支持切片,不支持负索引,可以通过list将其转换成列表。在SQL的层面上,QuerySet对应SELECT语句。

# 检索全部对象

从数据库中检索对象最简单的方式就是检索全部。只需要在Manager上调用all()方法:

all_students = Student.objects.all()
1

方法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
1
2
3

# 过滤

all()返回的QuerySet包含了数据表中所有的对象。通过下面的两个方法可以对QuerySet进行过滤。

filter(**kwargs)
1

返回一个新的QuerySet,包含的对象满足给定查询参数。

exclude(**kwargs)
1

返回一个新的QuerySet,包含的对象不满足给定查询参数。

例如查询所有叫心蓝的学生

Student.objects.filter(name="心蓝")
# 或者
Student.objects.all().filter(name='心蓝')
1
2
3

# 检索单个对象

filter()总是返回一个QuerySet,即便只有一个对象满足查询条件——这种请情况下,QuerySet只包含一个元素。

如果知道只有一个对象满足查询条件,可以在管理器上使用get()方法,它会直接返回这个对象:

s = Student.objects.get(pk=1)  # 如果不存在会抛出异常
1

除了get方法之外,还有一些快捷方法获取单个对象。

  • first()
Student.objects.first()  # 获取第一个对象
1
  • last
Student.objects.last()   # 获取最后一个对象
1

# 排序

order_by(*fields),会根据给定字段排序,默认QuerySet按照模型类中的Meta类中定义的来排序。管理器和QuerySet上都可以调用。

Student.objects.all().order_by('name')  # 根据名字升序排序
Student.objects.order_by('-name') # 根据名字降序排序
1
2

# 切片

使用python列表的切片的语法来获取部分数据,它等价于SQL中LIMIT与OFFSET子句。

Student.objects.all()[:5]   # 获取前5条 等价于  LIMIT 5
Student.objects.all()[2:5]   # 等价于 LIMIT 3 OFFSET 2
1
2

# 选择字段

  • values(fields)

返回一个QuerySet,这个QuerySet返回一个字典列表,而不是数据对象。参数fields指定了select语句中我们想要限制查询的字段。返回的字典中只会包含我们指定的字段。如果不指定,则包含所有字段。

Student.objects.values('name')
<QuerySet [{'name': '张柏芝'}, {'name': '刘德华'}, {'name': '心蓝'}]>

1
2
3
  • only(fields)

返回一个QuerySet,这个QuerySet返回一个对象列表。参数fields指定了select语句中我们想要限制查询的字段。注意,only一定会包含主键字段。

Student.objects.only('name')
<QuerySet [<Student: 张柏芝>, <Student: 刘德华>, <Student: 心蓝>]>
1
2
  • defer(fields)

返回一个QuerySet,这个QuerySet返回一个对象列表。参数fields指定了select语句中我们想要排除的查询的字段。注意,defer一定会包含主键字段。

Student.objects.defer('c_time')
1

only和defer返回的对象还是可以正常访问没有包含在select语句中的字段,只是会再次查询数据库。

# 条件查询

在filter,exclude,get中可以接收参数实现各种比较条件的查询。

  • exact

准确匹配.如果给的值是None,它会被解释成SQL NULL 看下面的案例

Student.objects.get(id__exact=14)
Student.objects.get(qq__exact=None)  
1
2

id__exact=14等价于id = 14,默认情况不带exact

  • iexact 不分大小写的匹配。
Blog.objects.get(name__iexact='beatles blog')
Blog.objects.get(name__iexact=None)
1
2
  • in

在一个给定的可迭代对象中,通常是一个列表,元组,或queryset。虽然不是经常用但是字符串也可以。

Entry.objects.filter(id__in=[1, 3, 4])
Entry.objects.filter(headline__in='abc')
1
2
  • gt

大于

Entry.objects.filter(id__gt=4)
1
  • gte 大于等于
  • lt 小于
  • lte 小于等于
  • range

范围区间

Student.objects.filter(age__range=(18, 20))
1

更多字段参考官方文档 (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))
1
2
3
4

SQL 等价于:

SELECT ... WHERE x=1 AND y=2
1
  • 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))
1
2
3

SQL 等价于:

SELECT ... WHERE x=1 OR y=2
1

# 聚合查询

  • count 统计数量

    # 统计所有学生的数量
    Students.objects.count()
    # 统计所有男生的数量
    Students.objects.filter(sex=1).count()
    
    1
    2
    3
    4
  • Avg 平均值

    # 计算同学们的年龄平均值
    from django.db.models import Avg
    Students.objects.aggregate(age_avg=Avg('age'))
    
    1
    2
    3
  • Max 最大值

    # 找到最大年龄的学生
    from django.db.models import Max
    Students.objects.aggregate(Max('age'))
    
    1
    2
    3
  • Min 最小值

    # 找到最小年龄的学生
    from django.db.models import Min
    Students.objects.aggregate(Min('age'))
    
    1
    2
    3
  • Sum 求和

    # 计算缴费总金额
    from django.db.models import Sum
    Enroll.objects.aggregate(Sum('pay'))
    
    1
    2
    3

# 分组

分组,聚合,需要结合values,annotate和聚合方法看下面的案例

# 查询男生女生多少人
Student.objects.values('sex').annotate(Count('sex'))  # annotate 默认按照主键分组

1
2
3

更多聚合参考官方文档 (opens new window)

# 删除对象

通常,删除方法被命名为 delete()。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。例子:

s = Student.objects.get(pk=1)
s.delete()
1
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()
1
2
3
4
5
6
7
8
9
10
11

==注意,主表表数据要入库(也即是Channel对象要save()之后)之后,引用表才能创建==

ForeignKey字段的更新,和普通字段没有什么区别。

# 删除

如果一个外键字段有null=True的设置(即,它允许空值),您可以指定None来删除关系。例如:

s1.channel = None
s1.save()
1
2
# 查
# 查询所有百度渠道的学生
Student.objects.filter(channel__name='百度') #  跨表查询
channel = Channel.objects.get(name='百度')
channel.student_set.all()  # 反向查询
1
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全部加入关联对象集合
1
2
3
4
5
# 删除
# 1. 从相关对象中移除指定的模型对象
# 清除某个渠道中的某些学生
ch.student_set.remove(s1, s2, ...)
# 2. 从相关对象中删除所有的 对象
# 清除某个渠道中的所有学生
ch.student_set.clear()
1
2
3
4
5
6
# 改
# 替换对象集
ch.student_set.set([s1, s2])
# 如果clear 可以调用 先clear再添加,如果过不行就直接添加
1
2
3
# 查
# 1. 查询所有
ch.student_set.all()
# 2. 条件查询
ch.student_set.filter(name='心蓝')
# 和objects一样的使用
1
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有哪些学生报名
1
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])
1
2

当多对多的关系指定了中间表的时候,还可以通过中间表对象像普通的多对一关系一样操作。看下面的案例:

# 创建一个报名记录,s1学员报名了c1课程
e = Enroll()
e.course = c1
e.student = s1
e.save()
1
2
3
4
5

你可能会问,既然这样,那指定中间表还要多对多字段干什么?因为查询非常方便,看下面的案例:

# 查询报名某课程的学生
c1.students.all()
# 查询某学生报名的课程
s1.course_set.all()
1
2
3
4

使用ManyToManyField之后,这两个表可以有api直接访问。

# 一对一

一对一关系非常类似于多对一关系。如果模型上定义了一对一字段,那么该模型的实例将可以通过模型的一个简单属性访问相关对象。例如:

# 给某学生添加学生详情
s1 = Student.objects.first()
sd = StudentDetail.objects.create(student=s1,city='北京',salary=50000)
<Student: yoyo>
1
2
3
4

不同之处在于反向查询。一对一关系中的相关模型可以通过相关模型的小写属性名访问一个Manager对象,但是这个Manager只是一个对象,而不是一个对象集合:

s1.studentdetail
<StudentDetail: yoyo>
1
2

如果没有给这个关系分配对象,Django将抛出一个DoesNotExist异常。

# 跨表查询

Django提供了一种强大而直观的方法,可以在查询中“跟踪”关系,在幕后自动处理为SQL连接。要跨越关系,只需使用跨模型的相关字段的字段名,以双下划线分隔,直到您到达您想要的字段为止。

例如,查询男生都报名了什么课程

Course.objects.filter(students__sex=1).distinct() 
1

这个关系要多深就可以有多深。

它也向后工作。要引用反向关系,只需使用模型的小写名称。

这个例子查询所有报名了python课程的学员:

Student.objects.filter(course__name__contains='python') 
1

我们再看一个例子:

查询所有报名了python的百度渠道的学员

Student.objects.filter(course__name__contains='python', channel__name='百度')
1

# 执行原生SQL

# 1. 执行原生查询并返回模型实例

在管理器上调用raw()方法用于执行远程SQL查询,就会返回模型实例,语法格式如下:

Manager.raw(raw_query, params=(), translations=None)
1

该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet一样被迭代获取对象实例。

for stu in Student.objects.raw('select * from crm_student'):
    print(stu)
1
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
1
2
3
4
5
6
7
8

本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。

#django
上次更新: 2022/12/26, 16:59:39
ORM与模型
项目实战一

← ORM与模型 项目实战一→

最近更新
01
requests让接口测试如此简单 原创
03-31
02
最简明的python正则教程
03-30
03
pycharm激活码
12-30
更多文章>
Theme by Vdoing | Copyright © 2019-2025 心蓝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式