Django模型层


Django之模型层:单表操作

ORM简介

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。
如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库。
但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下

#1. sql语句的执行效率:应用开发程序员需要耗费一大部分精力去优化sql语句 #2. 数据库迁移:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行。

原生SQL与ORM的对应关系如下:

如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率。

表单操作

创建django项目,新建名为app01的app,在app01的models.py中创建模型

class Employee(models.Model): # 必须是models.Model的子类 id=models.AutoField(primary_key=True) name=modelsharField(max_length=16) gender=models.BooleanField(default=1) birth=models.DateField() department=modelsharField(max_length=30) salary=models.DecimalField(max_digits=10,decimal_places=1)

django的orm支持多种数据库,如果想将上述模型转为mysql数据库中的表,需要在settings.py中写入

# 删除\注释掉原来的DATABASES配置项,新增下述配置 DATABASES = } }

在链接mysql数据库前,必须事先创建好数据库

mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上

其实python解释器在运行django程序时,django的orm底层操作数据库的python模块默认是mysqldb而非pymysql,然而对于解释器而言,python2.x解释器支持的操作数据库的模块是mysqldb,而python3.x解释器支持的操作数据库的模块则是pymysql,,毫无疑问,目前我们的django程序都是运行于python3.x解释器下,于是我们需要修改django的orm默认操作数据库的模块为pymysql,具体做法如下

# 在__init__.py文件中写去以下两句代码 import pymysql pymysql.install_as_MySQL()

确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.x与django1.x处理添加方式不同

# django1.x版本,在下述列表中新增我们的app名字即可 INSTALLED_APPS = [ 'djangoontrib.admin', 'djangoontrib.auth', 'djangoontribontenttypes', 'djangoontrib.sessions', 'djangoontrib.messages', 'djangoontrib.staticfiles', 'app01', # 'app02' # 若有新增的app,依次添加即可 ] # django2.x版本,可能会帮我们自动添加app,只是换了一种添加方式 INSTALLED_APPS = [ 'djangoontrib.admin', 'djangoontrib.auth', 'djangoontribontenttypes', 'djangoontrib.sessions', 'djangoontrib.messages', 'djangoontrib.staticfiles', 'app01.apps.App01Config', # 如果默认已经添加了,则无需重复添加 # 'app02.apps.App02Config', # 若有新增的app,按照规律依次添加即可 ]

如果想打印orm转换过程中的sql,需要在settings中进行配置日志:

LOGGING = , }, 'loggers': , } }

最后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :

$ python manage.py makemigrations $ python manage.py migrate # 注意: # 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行 # 2、数据库迁移记录的文件存放于app01下的migrations文件夹里 # 3、了解:使用命令python manage.py showmigrations可以查看没有执行migrate的文件

在使用的是django1.x版本时,如果报如下错误

djangoore.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None

那是因为MySQLclient目前只支持到python3.4,如果使用的更高版本的python,需要找到文件C:\Programs\Python\Python32\Lib\sitepackages\Django2.0py3.6.egg\django\db\backends\mysql,这个路径里的文件

# 注释下述两行内容即可 if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

在表生成之后,如果需要增加、删除、修改表中字段,需要这么做

# 一:增加字段 #1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值 publish = modelsharField(max_length=12,default='人民出版社',null=True) #1.2、重新执行那两条数据库迁移命令 # 二:删除字段 #2.1 直接注释掉字段 #2.2 重新执行那两条数据库迁移命令 # 三:修改字段 #2.1 将模型类中字段修改 #2.2 重新执行那两条数据库迁移命令

添加记录

方式一:

# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录 obj = Employee(name="Jie", gender=0, birth='19970127', department="财务部", salary=100.1) # 2、调用对象下的save方法,即可以将一条记录插入数据库 obj.save()

方式二:

# 每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示 obj = Employee.objectsreate(name="Egon", gender=0, birth='19970127', department="财务部", salary=100.1)

查询记录

模型Employee对应表app01_employee,表app01_employee中的每条记录都对应类Employee的一个对象,我们以该表为例,来介绍查询API,读者可以自行添加下述记录,然后配置url、编写视图测试下述API

mysql> select * fr app01_employee; +++++++ | id | name | gender | birth | department | salary | +++++++ | 1 | Egon | 0 | 19970127 | 财务部 | 100.1 | | 2 | Kevin | 1 | 19980227 | 技术部 | 10.1 | | 3 | Lili | 0 | 19900227 | 运营部 | 20.1 | | 4 | T | 1 | 19910227 | 运营部 | 30.1 | | 5 | Jack | 1 | 19920227 | 技术部 | 11.2 | | 6 | Robin | 1 | 19880227 | 技术部 | 200.3 | | 7 | Rose | 0 | 19890227 | 财务部 | 35.1 | | 8 | Egon | 0 | 19970127 | 财务部 | 100.1 | | 9 | Egon | 0 | 19970127 | 财务部 | 100.1 | +++++++

每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作。

Part1:

注意:下述方法(除了count外)的返回值都是一个模型类Employee的对象,为了后续描述方便,我们统一将模型类的对象称为"记录对象",每一个”记录对象“都唯一对应表中的一条记录。

# 1. get(**kwargs) # 1.1: 有参,参数为筛选条件 # 1.2: 返回值为一个符合筛选条件的记录对象(有且只有一个),如果符合筛选条件的对象超过一个或者没有都会抛出错误。 obj=Employee.objects.get(id=1) print(obj.name,obj.birth,obj.salary) #输出:Egon 19970127 100.1 # 2、first() # 2.1:无参 # 2.2:返回查询出的第一个记录对象 obj=Employee.objects.first() # 在表所有记录中取第一个 print(obj.id,obj.name) # 输出:1 Egon # 3、last() # 3.1: 无参 # 3.2: 返回查询出的最后一个记录对象 obj = Employee.objects.last() # 在表所有记录中取最后一个 print(obj.id, obj.name) # 输出:9 Egon # 4、count(): # 4.1:无参 # 4.2:返回包含记录对象的总数量 res = Employee.objectsount() # 统计表所有记录的个数 print(res) # 输出:9 # 注意:如果我们直接打印Employee的对象将没有任何有用的提示信息,我们可以在模型类中定义__str__来进行定制 class Employee(models.Model): ...... # 在原有的基础上新增代码如下 def __str__(self): return "<%s:%s>" %(self.id,self.name) # 此时我们print(obj)显示的结果就是: <本条记录中id字段的值:本条记录中name字段的值>

Part2:

注意:下述方法查询的结果都有可能包含多个记录对象,为了存放查询出的多个记录对象,django的ORM自定义了一种数据类型Queryeset,所以下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象

# 1、filter(**kwargs): # 1.1:有参,参数为过滤条件 # 1.2:返回值为QuerySet对象,QuerySet对象中包含了符合过滤条件的多个记录对象 queryset_res=Employee.objects.filter(department='技术部') # print(queryset_res) # 输出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]> # 2、exclude(**kwargs) # 2.1: 有参,参数为过滤条件 # 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象 queryset_res=Employee.objects.exclude(department='技术部') # 3、all() # 3.1:无参 # 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的所有记录对象 queryset_res = Employee.objects.all() # 查询出表中所有的记录对象 # 4、order_by(*field): # 4.1:有参,参数为排序字段,可以指定多个字段,在字段1相同的情况下,可以按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆代表降序排(如"id") # 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象 queryset_res = Employee.objects.order_by("salary","id") # 先按照salary字段升序排,如果salary相同则按照id字段降序排 # 5、values(*field) # 5.1:有参,参数为字段名,可以指定多个字段 # 5.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个字典,字典的key即我们传入的字段名 queryset_res = Employee.objects.values('id','name') print(queryset_res) # 输出:<QuerySet [, , ......]> print(queryset_res[0]['name']) # 输出:Egon # 6、values_list(*field): # 6.1:有参,参数为字段名,可以指定多个字段 # 6.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个小元组,字典的key即我们传入的字段名 queryset_res = Employee.objects.values_list('id','name') print(queryset_res) # 输出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]> print(queryset_res[0][1]) # 输出:Egon

Part3:
Part2中所示查询API的返回值都是QuerySet类型的对象,QuerySet类型是django ORM自定义的一种数据类型,专门用来存放查询出的多个记录对象,该类型的特殊之处在于

1、queryset类型类似于python中的列表,支持索引操作

# 过滤出符合条件的多个记录对象,然后存放到QuerySet对象中 queryset_res=Employee.objects.filter(department='技术部') # 按照索引从QuerySet对象中取出第一个记录对象 obj=queryset_res[0] print(obj.name,obj.birth,obj.salary)

2、管理器objects下的方法queryset下同样可以调用,并且django的ORM支持链式操作,于是我们可以像下面这样使用

# 简单示范: res=Employee.objects.filter(gender=1).order_by('id').values_list('id','name') print(res) # 输出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'T'), (2, 'Kevin')]>

Part4:
其他查询API

# 1、reverse(): # 1.1:无参 # 1.2:对排序的结果取反,返回值为QuerySet对象 queryset_res = Employee.objects.order_by("salary", "id").reverse() # 2、exists(): # 2.1:无参 # 2.2:返回值为布尔值,如果QuerySet包含数据,就返回True,否则返回False res = Employee.objects.filter(id=100).exists() print(res) # 输出:False # 3、distinct(): # 3.1:如果使用的是Mysql数据库,那么distinct()无需传入任何参数 # 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象 res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct() print(res) # 输出:<QuerySet []> res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>

基于双下划线的模糊查询

F与Q查询

F查询

在上面所有的例子中,我们在进行条件过滤时,都只是用某个字段与某个具体的值做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较两个不同字段的值,如下

# 一张书籍表中包含字段:评论数cmentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍 fr django.db.models import F Book.objects.filter(cmnetNum__lt=F('keepNum'))

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作

# 查询评论数大于收藏数2倍的书籍 fr django.db.models import F Book.objects.filter(cmnetNum__lt=F('keepNum')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

Book.objects.all().update(price=F("price")+30)

Q查询

filter() 等方法中逗号分隔开的多个关键字参数都是逻辑与(AND) 的关系。 如果我们需要使用逻辑或(OR)来连接多个条件,就用到了Django的Q对象
可以将条件传给类Q来实例化出一个对象,Q的对象可以使用&| 操作符组合起来,&等同于and,|等同于or

fr django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon")) # 等同于sql:select * fr app01_employee where id < 5 or name = 'Egon';

Q 对象可以使用~ 操作符取反,相当于NOT

fr django.db.models import Q Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon")) # 等同于sql:select * fr app01_employee where not (id < 5) or name = 'Egon';

当我们的过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面

fr django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100) # 等同于sql:select * fr app01_employee where (id < 5 or name = 'Egon') and salary < 100;

聚合查询

聚合查询aggregate()是把所有查询出的记录对象整体当做一个组,我们可以搭配聚合函数来对整体进行一个聚合操作

fr django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数 # 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合 res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg fr app01_employee; print(res1) # 输出: # 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合 res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg fr app01_employee; print(res2) # 输出: res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg fr app01_employee where id > 3; print(res3) # 输出:

aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,例如

Avg("salary") 合成的名字为 'salary__avg'

若我们想定制字典的key名,我们可以指定关键参数,如下

res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal fr app01_employee; print(res1) # 输出: # 关键字参数名就会被当做字典的key

如果我们想得到多个聚合结果,那就需要为aggregate传入多个参数

res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) # 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal fr app01_employee; print(res1) # 输出:

分组查询

分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values("分组字段").annotate(聚合函数),如下

# 表中记录 mysql> select * fr app01_employee; +++++++ | id | name | gender | birth | department | salary | +++++++ | 1 | Egon | 0 | 19970127 | 财务部 | 100.1 | | 2 | Kevin | 1 | 19980227 | 技术部 | 10.1 | | 3 | Lili | 0 | 19900227 | 运营部 | 20.1 | | 4 | T | 1 | 19910227 | 运营部 | 30.1 | | 5 | Jack | 1 | 19920227 | 技术部 | 11.2 | | 6 | Robin | 1 | 19880227 | 技术部 | 200.3 | | 7 | Rose | 0 | 19890227 | 财务部 | 35.1 | +++++++ # 查询每个部门下的员工数 res=Employee.objects.values('department').annotate(num=Count('id')) # 相当于sql: # select department,count(id) as num fr app01_employee group by department; print(res) # 输出:<QuerySet [, , ]>

跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段

res=Employee.objects.values('department').annotate(num=Count('id')).values('num') # 相当于sql: # select count(id) as num fr app01_employee group by department; print(res) # 输出:<QuerySet [, , ]>

跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件,如下

# 查询男员工数超过2人的部门名 res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department') print(res) # 输出:<QuerySet []> # 解析: # 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息 # 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count # 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门 # 4、最后的values('department')代表从最终的结果中只取部门名

总结:

1、values()在annotate()前表示group by的字段,在后表示取值 1、filter()在annotate()前表示where条件,在后表示having

需要注意的是,如果我们在annotate前没有指定values(),那默认用表中的id字段作为分组依据,而id各不相同,如此分组是没有意义的,如下

res=Employee.objects.annotate(Count('name')) # 每条记录都是一个分组 res=Employee.objects.all().annotate(Count('name')) # 同上

修改记录

直接修改单条记录

可以修改记录对象属性的值,然后执行save方法从而完成对单条记录的直接修改

# 1、获取记录对象 obj=Employee.objects.filter(name='Egon')[0] # 2、修改记录对象属性的值 obj.name='EGON' obj.gender=1 # 3、重新保存 obj.save()

修改QuerySet中的所有记录对象

QuerySet对象下的update()方法可以更QuerySet中包含的所有对象,该方法会返回一个整型数值,表示受影响的记录条数(相当于sql语句执行结果的rows)

queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.update(name='EGON',gender=1)

删除记录

直接删除单条记录

可以直接调用记录对象下的delete方法,该方法运行时立即删除本条记录而不返回任何值,如下

obj=Employee.objects.first() obj.delete()

删除QuerySet中的所有记录对象

每个 QuerySet下也都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象(如果QuerySet对象中只有一个记录对象,那也就只删一条),如下

queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.delete()

需要强调的是管理objects下并没有delete方法,这是一种保护机制,是为了避免意外地调用 Employee.objects.delete() 方法导致所有的记录被误删除从而跑路。但如果你确认要删除所有的记录,那么你必须显式地调用管理器下的all方法,拿到一个QuerySet对象后才能调用delete方法删除所有

Employee.objects.all().delete()

Django之模型层:多表操作

表关系回顾

在讲解MySQL时,我们提到,把应用程序的所有数据都放在一张表里是极不合理的。

比如我们开发一个员工管理系统,在数据库里只创建一张员工信息表,该表有四个字段:工号、姓名、部门名、部门职能描述,此时若公司有1万名员工,但只有3个部门,因为每一名员工后都需要跟着部门信息(部门名、部门职能),所以将会导致部门信息出现大量重复、浪费空间。

解决方法就是将数据存放于不同的表中,然后基于foreign key建立表之间的关联关系。

细说的话,表之间存在三种关系:多对一、一对一、多对多,那如何确定两张表之间的关系呢?按照下述步骤操作即可

左表<>右表 # 步骤一:先分析 #分析1、先站在左表的角度 是否左表的多条记录可以对应右表的一条记录 #分析2、再站在右表的角度去找 是否右表的多条记录可以对应左表的一条记录 # 步骤二:后确定关系 # 多对一 如果只有"分析1"成立,那么可以确定两张表的关系是:左表多对一右表,关联字段应该创建在左表中,然后foreign key 右表一个字段(通常是id) 如果只有"分析2"成立,那么可以确定两张表的关系是:右表多对一左表,关联字段应该创建在右表中,然后foreign key 左表一个字段(通常是id) # 一对一 如果"分析1"和"分析2"都不成立,而是左表的一条记录唯一对应右表的一条记录,反之亦然。这种情况很简单,就是在左表foreign key右表的基础上,将左表的关联字段设置成unique即可 # 多对多 如果"分析1"和"分析2"同时成立,则证明这两张表是一个双向的多对一,即多对多,需要创建一张单独的新表来专门存放二者的关系,关联字段应该创建在新表中,然后在新表中分别foreign key两张表的id字段

我们以一个图书管理系统为背景,设计了下述四张表,让我们来找一找它们之间的关系

书籍表:app01_book

出版社表:app01_publish

作者表:app01_author

作者详细信息表:app01_authordetail

例1、app01_book与app01_publish

找关系

左表(app01_book)<>右表(app01_publish) # 步骤一: #分析1、先站在左表的角度 左表的多条记录代表多版本书籍,右表的一条记录代表一个出版社,多本书籍对应同一个出版社 ?? #分析2、再站在右表的角度去找 右表的多条记录代表多个出版社,左表的一条记录代表一本书,多个出版社不能出版同一本书 ? # 步骤二:后确定关系 # 多对一 只有"分析1"成立,那么可以确定两张表的关系是:左表(app01_book)多对一右表(app01_publish),关联字段应该创建在左表(app01_book)中,然后foreign key 右表(app01_publish)的id字段

sql语句

# 1、由于foreign key的影响,必须先创建被关联表 CREATE TABLE app01_publish ( id INT PRIMARY KEY auto_increment, name VARCHAR (20) ); # 2、才能创建出关联表 CREATE TABLE app01_book ( id INT PRIMARY KEY auto_increment, title VARCHAR (20), price DECIMAL (8, 2), pub_date DATE, publish_id INT, # 新增关联字段 FOREIGN KEY (publish_id) REFERENCES app01_publish (id) ON UPDATE CASCADE ON DELETE CASCADE );

例2、app01_author与app01_authordetail

找关系

左表(app01_author)<>右表(app01_authordetail) 一个作者唯一对应一条自己的详情信息,反之亦然,所以两张表是一对一的关系。在左表中新增关联字段并添加unique约束,然后foreign key右表

sql语句

# 1、由于foreign key的影响,必须先创建被关联表 CREATE TABLE app01_authordetail ( id INT PRIMARY KEY auto_increment, tel VARCHAR (20) ); # 2、才能创建出关联表 CREATE TABLE app01_author ( id INT PRIMARY KEY auto_increment, name VARCHAR (20), age INT, authordetail_id INT UNIQUE, # 新增关联字段,并添加唯一性约束unique FOREIGN KEY (authordetail_id) REFERENCES app01_authordetail (id) ON UPDATE CASCADE ON DELETE CASCADE );

例3、app01_book与app01_author

找关系

左表(app01_book)<>右表(app01_author) # 步骤一: #分析1、先站在左表的角度 左表的多条记录代表多版本书籍,右表的一条记录代表一个作者,多本书籍可以由同一个作者编写 ?? #分析2、再站在右表的角度去找 右表的多条记录代表多个作者,左表的一条记录代表一本书,多个作者可以合作编写同一本书 ?? # 步骤二:后确定关系 # 多对多 "分析1"和"分析2"同时成立,证明这两张表是多对多的关系,需要创建一张单独的新表来专门存放二者的关系,关联字段应该创建在新表中,然后在新表中分别foreign key两张表的id字段

sql语句

# 1、创建被关联表一:app01_book,例1中已创建 # 2、创建被关联表二:app01_author,例2中已创建 # 3、创建新表,存放app01_book于app01_author的关联关系 CREATE TABLE app01_book_authors ( id INT PRIMARY KEY auto_increment, book_id INT, # 新增关联字段,用来关联表app01_book author_id INT, # 新增关联字段,用来关联表app01_author FOREIGN KEY (book_id) REFERENCES app01_book (id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (author_id) REFERENCES app01_author (id) ON UPDATE CASCADE ON DELETE CASCADE );

上述三个例子中生成的表如下

创建模型

模型类如下

fr django.db import models # 表app01_publish class Publish(models.Model): nid = models.AutoField(primary_key=True) name = modelsharField(max_length=20) # 表app01_book class Book(models.Model): nid = models.AutoField(primary_key=True) title = modelsharField(max_length=20) price = models.DecimalField(max_digits=8, decimal_places=2) pub_date = models.DateField() # 表app01_book多对一表app01_publish,参数to指定模型名,参数to_field指定要关联的那个字段 publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=modelsASCADE) # 我们自己写sql时,针对书籍表与作者表的多对关系,需要自己创建新表,而基于django的orm,下面这一行代码可以帮我们自动创建那张关系表 authors=models.ManyToManyField(to='Author') # 变量名为authors,则新表名为app01_book_authors,若变量名为xxx,则新表名为app01_book_xxx # 表app01_author class Author(models.Model): nid = models.AutoField(primary_key=True) name = modelsharField(max_length=20) age = models.IntegerField() # 表app01_author一对一表app01_authordetail author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=modelsASCADE) # 表app01_authordetail class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) tel = modelsharField(max_length=20)

强调:

在创建关联时,针对参数to,如果传入的是字符串(to="模型名"),则模型类的定义不区分先后顺序,如果传入的是模型名(to=Author),则Author类必须事先定义

添加、删除、修改记录

添加记录

强调!!:上图所示的表名、字段名都是mysql中的真实表/物理表,而我们下述所示所有操作,都是通过模型类来操作物理表,例如无论增删改查,所使用的字段名都模型类中的字段

按照上图所示,由于foreign key的关系,我们需要事先往app01_publish与app01_authordetail里插入记录

# 1、需求:通过模型Publish往表app01_publish里插入三家出版社 Publish.objectsreate(name='北京出版社') Publish.objectsreate(name='长春出版社') Publish.objectsreate(name='大连出版社') # 2、需求:通过模型AuthorDetail往表app01_authordetail里插入三条作者详情 AuthorDetail.objectsreate(tel='18611312331') AuthorDetail.objectsreate(tel='15033413881') AuthorDetail.objectsreate(tel='13011453220')

按照上图所示,插入时会涉及到多张表,我们同样分三种情况来介绍

1、多对一:app01_book与app01_publish

# 需求:书籍(葵花宝典、菊花宝典、桃花宝典)都是在北京出版社出版的 # 1、先通过模型Publish从出版社表app01_publish查出北京出版社 publish_obj=Publish.objects.filter(name='北京出版社').first() # 上述代码也可以简写为:publish_obj=Publish.objects.get(name='北京出版社') # 2、再通过模型Book往书籍表app01_book里插入三本书籍与出版社的对应关系 # 方式一:使用publish参数指定关联 book_obj1=Book.objectsreate(title='葵花宝典',price=2000,pub_date='1985311',publish=publish_obj) book_obj2=Book.objectsreate(title='菊花宝典',price=3000,pub_date='1990121',publish=publish_obj) book_obj3=Book.objectsreate(title='桃花宝典',price=0,pub_date='1991123',publish=publish_obj) # 方式二:使用publish_id参数指定关联 book_obj1=Book.objectsreate(title='葵花宝典',price=2000,pub_date='1985311',publish_id=publish_obj.nid) book_obj2=Book.objectsreate(title='菊花宝典',price=3000,pub_date='1990121',publish_id=1) # 在已经出版社id的情况下,可以直接指定 book_obj3=Book.objectsreate(title='桃花宝典',price=0,pub_date='1991123',publish_id=1) # 注意:无论方式一还是方式二得到的书籍对象book_obj1、book_obj2、book_obj3 # 都可以调用publish字段来访问关联的那一个出版社对象 # 都可以调用publish_id来访问关联的那一个出版社对象的nid print(book_obj1.publish,book_obj1.publish_id) print(book_obj2.publish,book_obj2.publish_id) print(book_obj3.publish,book_obj3.publish_id) # 三本书关联的是同一个出版社,所以输出结果均相同

参照上述步骤,把剩余三本书与出版社的对应关系也插入

book_obj1 = Book.objectsreate(title='玉女心经', price=5000, pub_date='1988324', publish_id=2) book_obj2 = Book.objectsreate(title='玉男心经', price=3000, pub_date='1985617', publish_id=2) book_obj3 = Book.objectsreate(title='九阴真经', price=6000, pub_date='1983817', publish_id=3)

2、一对一:app01_author与app01_authordetail

# 需求:插入三个作者,并与作者详情表一一对应 # 由于作者详情表我们已经事先创建好记录,所以只需要往作者表插入记录即可 # 方式一:需要事先过滤出作者详情的对象,然后通过模型Author的字段author来指定要关联的作者详情对象(略) # 方式二:确定作者详情对象的id,然后通过模型Author通过字段author_id来指定关联关系, Author.objectsreate(name='egon',age=18,author_detail_id=1) Author.objectsreate(name='kevin',age=38,author_detail_id=2) Author.objectsreate(name='rose',age=28,author_detail_id=3)

3、多对多:app01_book与app01_author

# 我们参照物理表app01_book_authors制定需求,需要创建如下关系 # 1、葵花宝典的作者为:egon、kevin # 2、菊花宝典的作者为:egon、kevin、rose # 3、桃花宝典的作者为:egon、kevin # 4、玉女心经的作者为:kevin、rose # 5、玉男心经的作者为:kevin # 6、九阴真经的作者为:egon、rose # 需要创建出上述关系,具体做法如下 # 1、先获取书籍对象 book_obj1=Book.objects.get(title='葵花宝典') book_obj2=Book.objects.get(title='菊花宝典') book_obj3=Book.objects.get(title='桃花宝典') book_obj4=Book.objects.get(title='玉女心经') book_obj5=Book.objects.get(title='玉男心经') book_obj6=Book.objects.get(title='九阴真经') # 2、然后获取作者对象 egon=Author.objects.get(name='egon') kevin=Author.objects.get(name='kevin') rose=Author.objects.get(name='rose') # 3、最后依次创建上述关系:在原生SQL中多对多关系涉及到操作第三张关系表,但是在ORM中我们只需要操作模型类Book下的字段author即可 book_obj1.authors.add(egon,kevin) book_obj2.authors.add(egon,kevin,rose) book_obj3.authors.add(egon,kevin) book_obj4.authors.add(kevin,rose) book_obj5.authors.add(kevin) book_obj6.authors.add(egon,rose)

可以通过书籍对象下的authors字段获取其所关联的所有作者对象

book_obj1.authors.all() # 返回一个存有多个作者的queryset

删除、修改记录

# 1、book_obj.authors.remove() :将某个特定的对象从被关联对象集合中去除 # 从菊花宝典的作者集合中去掉作者rose rose = Author.objects.get(name='rose') book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authors.remove(rose) # 2、book_obj.authorslear():清空被关联对象集合 # 清空菊花宝典所关联的所有作者 book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authorslear() # 3、book_obj.authors.set():先清空再重新设置  # 玉男心经的作者原来为kevin,要求设置为egon、rose egon=Author.objects.get(name='egon') rose=Author.objects.get(name='rose') book_obj5 = Book.objects.get(title='玉男心经') book_obj5.authors.set([egon,rose]) # 多个作者对象放到列表里

查询记录

数据库操作最常用的还是查询操作,在介绍ORM下多表关联查询时,需要事先记住关于ORM模型的一个非常重要的概念:在使用模型类进行多表关联查询时,如果确定两张表存在关联关系,那么在选取一个表作为起始(为了后续描述方便,我们将其简称为"基表")后,可以跨表引用来自另外一张中的字段值,这存在正向与反向之分

如果关联字段存在于基表中,称之为正向查询,否则,称之为反向查询

例如表模Book与Publish,关联字段存在于Book中

# 当以Book为基表时,称之为正向查询 Book(基表)正向>Publish # 当以Publish为基表时,称之为反向查询 Book<反向Publish(基表)

使用原生sql进行多表关联查询时无非两种方式:子查询、join连表查询,ORM里同样有两种查询方式(严格依赖正向、反向的概念)

基于对象的跨表查询

1、跨两张表查询

1.1、一对一查询(模型类Author与AuthorDetail)

正向查询,按关联字段:author_detail

# 需求:查询作者egon的手机号 # 1、先取出作者对象 egon=Author.objects.filter(name='egon').first() # 2、正向查询:根据作者对象下的关联字段author_detail取到作者详情 print(egon.author_detail.tel) # 输出:18611312331

反向查询,按模型名(小写):author

# 需求:查询手机号为'18611312331'的作者名 # 1、先取出作者的详情对象 tel=AuthorDetail.objects.filter(tel='18611312331').first() # 2、反向查询:根据小写的模型名author取到作者对象 print(tel.author.name) # 输出:egon

1.2、多对一查询(模型类Book与Publish)

正向查询,按关联字段:publish

# 需求:查询葵花宝典的出版社名字 # 1、先取书籍对象 book_obj=Book.objects.filter(title='葵花宝典').first() # 2、正向查询:根据书籍对象下的关联字段publish取到出版社 print(book_obj.publish.name) # 输出:北京出版社

反向查询,按模型名(小写)_set:book_set

# 需求:查询北京出版社都出版的所有书籍名字 # 1、先取出出版社对象 publish_obj=Publish.objects.filter(name='北京出版社').first() # 2、反向查询:根据book_set取到所有的书籍 book_objs=publish_obj.book_set.all() print([book_obj.title for book_obj in book_objs]) # 输出:['葵花宝典', '菊花宝典', '桃花宝典']

1.3、多对多查询(模型类Book与Author)

正向查询,按关联字段,如authors

# 需求:查询葵花宝典的所有作者 # 1、先取出书籍对象 book_obj=Book.objects.filter(title='葵花宝典').first() # 2、正向查询:根据书籍对象下的关联字段authors取到所有作者 author_objs=book_obj.authors.all() print([obj.name for obj in author_objs]) # 输出:['egon', 'kevin']

反向查询,按模型名(小写)_set:如author_set

# 需求:查询作者rose出版的所有书籍 # 1、先取出作者对象 egon=Author.objects.filter(name='rose').first() # 2、反向查询:根据book_set取到作者对象 book_objs=egon.book_set.all() print([book_obj.title for book_obj in book_objs]) # 输出:['玉女心经', '九阴真经', '玉男心经']

2、连续跨>2张表查询

连续跨>2张表的操作的套路与上面的案例都是一样的

# 需求:查询葵花宝典的作者们的手机号 book_obj=Book.objects.filter(title='葵花宝典').first() author_objs=book_obj.authors.all() print([author_obj.author_detail.tel for author_obj in author_objs]) # 输出:['18611312331', '15033413881']

基于双下划线的跨表查询

1、跨两张表查询

1.1、一对一查询(模型类Author与AuthorDetail)

正向查询,按关联字段+双下划线:author_detail__

# 需求:查询作者egon的手机号 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res = Author.objects.filter(name='egon').values('author_detail__tel').first() print(res['author_detail__tel']) # # 注意:基于双下划线的跨表查询会被django的orm识别为join操作,所以上述代码相当于如下sql,后续案例均是相同原理,我们不再累述 select app01_authordetail.tel fr app01_author inner join app01_authordetail on app01_author.author_detail_id = app01_authordetail.nid where app01_author.name = 'egon';

反向查询,按模型名(小写)+双下划线:author__

# 需求:查询手机号为'18611312331'的作者名 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res=AuthorDetail.objects.filter(tel='18611312331').values('author__name').first() print(res) #

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询作者egon的手机号,如果我们选取的基表是AuthorDetail,那么就成了反向查询,应该用反向查询的语法 res = AuthorDetail.objects.filter(author__name='egon').values('tel').first() print(res) # # 2、针对上例中反向查询的需求:查询手机号为'18611312331'的作者名,如果我们选取的基表是Author,那么就成了正向查询,应该用正向查询的语法 res=Author.objects.filter(author_detail__tel='18611312331').values('name').first() print(res) #

1.2、多对一查询(模型类Book与Publish)

正向查询,按关联字段+双下划线:publish__

# 需求:查询葵花宝典的出版社名字 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=Book.objects.filter(title='葵花宝典').values('publish__name').first() print(res['publish__name']) #

反向查询,按模型名(小写)+双下划线:book__

# 需求:查询北京出版社都出版的所有书籍名字 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = Publish.objects.filter(name='北京出版社').values('book__title') print(res) # <QuerySet [, , ]>

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询葵花宝典的出版社名字,如果我们选取的基表是Publish,那么就成了反向查询,应该用反向查询的语法 res = Publish.objects.filter(book__title='葵花宝典').values('name').first() print(res) # # 2、针对上例中反向查询的需求:查询北京出版社都出版的所有书籍名字,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法 res=Book.objects.filter(publish__name='北京出版社').values('title') print(res) # <QuerySet [, , ]>

1.3、多对多查询(模型类Book与Author)

正向查询,按关联字段+双下划线:authors__

# 需求:查询葵花宝典的所有作者 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=Book.objects.filter(title='葵花宝典').values('authors__name') print(res) # <QuerySet [, ]>

反向查询,按模型名(小写)+双下划线:如book__

# 需求:查询作者rose出版的所有书籍 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = Author.objects.filter(name='rose').values('book__title') print(res) # <QuerySet [, , ]>

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询葵花宝典的所有作者,如果我们选取的基表是authors,那么就成了反向查询,应该用反向查询的语法 res=Author.objects.filter(book__title='葵花宝典').values('name') print(res) # <QuerySet [, ]> # 2、针对上例中反向查询的需求:查询作者rose出版的所有书籍,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法 res=Book.objects.filter(authors__name='rose').values('title') print(res) # <QuerySet [, , ]>

2、连续跨>2张表查询

连续跨>2张表的操作的套路与上面的案例都是一样的,可以连续接n个双下划线,只需要在每次连双下划线时,确定是正向还是反向即可

# 需求1:查询北京出版社出版过的所有书籍的名字以及作者的姓名、手机号 # 方式一:基表为Publish res=Publish.objects.filter(name='北京出版社').values_list('book__title','book__authors__name','book__authors__author_detail__tel') # 方式二:基表为Book res=Book.objects.filter(publish__name='北京出版社').values_list('title','authors__name','authors__author_detail__tel') # 循环打印结果均为 for obj in res: print(obj) ''' 输出: ('葵花宝典', 'egon', '18611312331') ('菊花宝典', 'egon', '18611312331') ('桃花宝典', 'egon', '18611312331') ('葵花宝典', 'kevin', '15033413881') ('菊花宝典', 'kevin', '15033413881') ('桃花宝典', 'kevin', '15033413881') ('菊花宝典', 'rose', '13011453220') ''' # 需求2:查询手机号以186开头的作者出版过的所有书籍名称以及出版社名称 # 方式一:基表为AuthorDetail res=AuthorDetail.objects.filter(tel__startswith='186').values_list('author__book__title','author__book__publish__name') # 方式二:基表为Book res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('title','publish__name') # 方式三:基表为Publish res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_list('book__title','name') # 循环打印结果均为 for obj in res: print(obj) ''' 输出: ('葵花宝典', '北京出版社') ('菊花宝典', '北京出版社') ('桃花宝典', '北京出版社') ('九阴真经', '大连出版社') '''

上一篇:JS

下一篇:书单(转载)


Copyright © 2002-2019 k262电脑网 www.k262.cn 皖ICP备2020016292号
温馨提示:部分文章图片数据来源与网络,仅供参考!版权归原作者所有,如有侵权请联系删除!QQ:251442993 热门搜索 网站地图