ORM
简介咱们在使用Django
框架开发web
应用的过程当中,不可避免地会涉及到数据的管理操做(如增、删、改、查),而一旦谈到数据的管理操做,就须要用到数据库管理软件,例如mysql
、oracle
、Microsoft SQL Server
等。python
若是应用程序须要操做数据(好比将用户注册信息永久存放起来),那么咱们须要在应用程序中编写原生sql语句,而后使用pymysql模块远程操做mysql数据库,详见图一^①^mysql
可是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,以下git
为了解决上述问题,django
引入了ORM
的概念,ORM
全称Object Relational Mapping
,即对象关系映射,是在pymysq
之上又进行了一层封装,对于数据的操做,咱们无需再去编写原生sql
,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM
会将其转换/映射成原生SQL
而后交给pymysql
执行,详见图二^②^程序员
如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮咱们作了优化且支持多种数据库,这极大地提高了咱们的开发效率,下面就让咱们来详细学习ORM的使用吧web
models.py
建立django
项目,新建名为app01
的app
,在app01
的models.py
中建立模型sql
class Employee(models.Model): # 必须是models.Model的子类 id=models.AutoField(primary_key=True) name=models.CharField(max_length=16) gender=models.BooleanField(default=1) birth=models.DateField() department=models.CharField(max_length=30) salary=models.DecimalField(max_digits=10,decimal_places=1)
settings.py
django
的`orm
支持多种数据库,若是想将上述模型转为mysql
数据库中的表,须要settings.py
中数据库
# 删除\注释掉原来的DATABASES配置项,新增下述配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库 'NAME': 'db1', # 要链接的数据库 'USER': 'root', # 连接数据库的用于名 'PASSWORD': '', # 连接数据库的用于名 'HOST': '127.0.0.1', # mysql服务监听的ip 'PORT': 3306, # mysql服务监听的端口 'ATOMIC_REQUEST': True, #设置为True表明同一个http请求所对应的全部sql都放在一个事务中执行 #(要么全部都成功,要么全部都失败),这是全局性的配置,若是要对某个 #http请求放水(而后自定义事务),能够用non_atomic_requests修饰器 'OPTIONS': { "init_command": "SET storage_engine=INNODB", #设置建立表的存储引擎为INNODB } } }
在链接mysql
数据库前,必须事先建立好数据库django
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
,具体作法以下python3.x
确保配置文件settings.py
中的INSTALLED_APPS
中添加咱们建立的app
名称,django2.x
与django1.x
处理添加方式不一样api
# django1.x版本,在下述列表中新增咱们的app名字便可 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01', # 'app02' # 如有新增的app,依次添加便可 ] # django2.x版本,可能会帮咱们自动添加app,只是换了一种添加方式 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 若是默认已经添加了,则无需重复添加 # 'app02.apps.App02Config', # 如有新增的app,按照规律依次添加便可 ]
若是想打印orm
转换过程当中的sql
,须要在settings
中进行配置日志:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
最后在命令行中执行两条数据库迁移命令,便可在指定的数据库db1
中建立表 :
$ python manage.py makemigrations $ python manage.py migrate # 注意: # 一、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行 # 二、数据库迁移记录的文件存放于app01下的migrations文件夹里 # 三、了解:使用命令python manage.py showmigrations能够查看没有执行migrate的文件
注意1:在使用的是django1.x
版本时,若是报以下错误
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
那是由于MySQLclient
目前只支持到python3.4
,若是使用的更高版本的python
,须要找到文件C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.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__)
注意2:当咱们直接去数据库里查看生成的表时,会发现数据库中的表与orm
规定的并不一致,这彻底是正常的,事实上,orm
的字段约束就是不会所有体如今数据库的表中,好比咱们为字段gender
设置的默认值default=1
,去数据库中查看会发现该字段的default
部分为null
mysql> desc app01_employee; # 数据库中标签前会带有前缀app01_ +------------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(16) | NO | | NULL | | | gender | tinyint(1) | NO | | NULL | | | birth | date | NO | | NULL | | | department | varchar(30) | NO | | NULL | | | salary | decimal(10,1) | NO | | NULL | | +------------+---------------+------+-----+---------+----------------+
虽然数据库没有增长默认值,可是咱们在使用orm
插入值时,彻底为gender
字段插入空,orm
会按照本身的约束将空转换成默认值后,再提交给数据库执行
在表生成以后,若是须要增长、删除、修改表中字段,须要这么作
# 一:增长字段 # 1.一、在模型类Employee里直接新增字段,强调:对于orm来讲,新增的字段必须用default指定默认值 publish = models.CharField(max_length=12,default='人民出版社',null=True) #1.二、从新执行那两条数据库迁移命令 # 二:删除字段 # 2.1 直接注释掉字段 # 2.2 从新执行那两条数据库迁移命令 # 三:修改字段 # 3.1 将模型类中字段修改 # 3.2 从新执行那两条数据库迁移命令
# 一、用模型类建立一个对象,一个对象对应数据库表中的一条记录 obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1) # 二、调用对象下的save方法,便可以将一条记录插入数据库 obj.save()
# 每一个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操做,其中增长操做以下所示 obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
API
模型Employee
对应表app01_employee
,表app01_employee
中的每条记录都对应类Employee
的一个对象,咱们以该表为例,来介绍查询API
,读者能够自行添加下述记录,而后配置url
、编写视图测试下述API
mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name | gender | birth | department | salary | +----+-------+--------+------------+------------+--------+ | 1 | Egon | 0 | 1997-01-27 | 财务部 | 100.1 | | 2 | Kevin | 1 | 1998-02-27 | 技术部 | 10.1 | | 3 | Lili | 0 | 1990-02-27 | 运营部 | 20.1 | | 4 | Tom | 1 | 1991-02-27 | 运营部 | 30.1 | | 5 | Jack | 1 | 1992-02-27 | 技术部 | 11.2 | | 6 | Robin | 1 | 1988-02-27 | 技术部 | 200.3 | | 7 | Rose | 0 | 1989-02-27 | 财务部 | 35.1 | | 8 | Egon | 0 | 1997-01-27 | 财务部 | 100.1 | | 9 | Egon | 0 | 1997-01-27 | 财务部 | 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 1997-01-27 100.1 # 二、first() # 2.1:无参 # 2.2:返回查询出的第一个记录对象 obj=Employee.objects.first() # 在表全部记录中取第一个 print(obj.id,obj.name) # 输出:1 Egon # 三、last() # 3.1: 无参 # 3.2: 返回查询出的最后一个记录对象 obj = Employee.objects.last() # 在表全部记录中取最后一个 print(obj.id, obj.name) # 输出:9 Egon # 四、count(): # 4.1:无参 # 4.2:返回包含记录对象的总数量 res = Employee.objects.count() # 统计表全部记录的个数 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
对象中包含了查询出的多个记录对象
# 一、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>>]> # 二、exclude(**kwargs) # 2.1: 有参,参数为过滤条件 # 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象 queryset_res=Employee.objects.exclude(department='技术部') # 三、all() # 3.1:无参 # 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的全部记录对象 queryset_res = Employee.objects.all() # 查询出表中全部的记录对象 # 四、order_by(*field): # 4.1:有参,参数为排序字段,能够指定多个字段,在字段1相同的状况下,能够按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆表明降序排(如"-id") # 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象 queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,若是salary相同则按照id字段降序排 # 五、values(*field) # 5.1:有参,参数为字段名,能够指定多个字段 # 5.2:返回值为QuerySet对象,QuerySet对象中包含的并非一个个的记录对象,而上多个字典,字典的key即咱们传入的字段名 queryset_res = Employee.objects.values('id','name') print(queryset_res) # 输出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]> print(queryset_res[0]['name']) # 输出:Egon # 六、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
<u>Part2
中所示查询API
的返回值都是QuerySet
类型的对象,QuerySet
类型是django ORM
自定义的一种数据类型,专门用来存放查询出的多个记录对象,该类型的特殊之处在于
queryset
类型相似于python
中的列表,支持索引操做# 过滤出符合条件的多个记录对象,而后存放到QuerySet对象中 queryset_res=Employee.objects.filter(department='技术部') # 按照索引从QuerySet对象中取出第一个记录对象 obj=queryset_res[0] print(obj.name,obj.birth,obj.salary)
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, 'Tom'), (2, 'Kevin')]>
Part4
其余查询API
# 一、reverse(): # 1.1:无参 # 1.2:对排序的结果取反,返回值为QuerySet对象 queryset_res = Employee.objects.order_by("salary", "-id").reverse() # 二、exists(): # 2.1:无参 # 2.2:返回值为布尔值,若是QuerySet包含数据,就返回True,不然返回False res = Employee.objects.filter(id=100).exists() print(res) # 输出:False # 三、distinct(): # 3.1:若是使用的是Mysql数据库,那么distinct()无需传入任何参数 # 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象 res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct() print(res) # 输出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]> res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>
F
与Q
查询F
查询在上面全部的例子中,咱们在进行条件过滤时,都只是用某个字段与某个具体的值作比较。若是咱们要对两个字段的值作比较,那该怎么作呢?
Django
提供 F()
来作这样的比较。F()
的实例能够在查询中引用字段,来比较两个不一样字段的值,以下
# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum'))
Django
支持 F()
对象之间以及F()
对象和常数之间的加减乘除和取模的操做
# 查询评论数大于收藏数2倍的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum')*2)
修改操做也可使用F函数,好比将每一本书的价格提升30元:
Book.objects.all().update(price=F("price")+30)
filter()
等方法中逗号分隔开的多个关键字参数都是逻辑与(AND)
的关系。 若是咱们须要使用逻辑或(OR)
来链接多个条件,就用到了Django
的Q
对象
能够将条件传给类Q
来实例化出一个对象,Q
的对象可使用&
和|
操做符组合起来,&
等同于and
,|
等同于or
from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon")) # 等同于sql:select * from app01_employee where id < 5 or name = 'Egon';
Q
查询Q
对象可使用~
操做符取反,至关于NOT
from django.db.models import Q Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon")) # 等同于sql:select * from app01_employee where not (id < 5) or name = 'Egon';
当咱们的过滤条件中既有or又有and,则须要混用Q对象与关键字参数,但Q
对象必须位于全部关键字参数的前面
from django.db.models import Q Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100) # 等同于sql:select * from app01_employee where (id < 5 or name = 'Egon') and salary < 100;
聚合查询aggregate()
是把全部查询出的记录对象总体当作一个组,咱们能够搭配聚合函数来对总体进行一个聚合操做
from 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 from app01_employee; print(res1) # 输出:{'salary__avg': 70.73} # 二、aggregate()会把QuerySet对象中包含的全部记录对象当成一组进行聚合 res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee; print(res2) # 输出:{'salary__avg': 70.73} res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3; print(res3) # 输出:{'salary__avg': 71.0}
aggregate()
的返回值为字典类型,字典的key
是由聚合字段的名称___聚合函数的名称
合成的,例如
Avg("salary") 合成的名字为 'salary__avg'
若咱们想定制字典的key名,咱们能够指定关键参数,以下
res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee; print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当作字典的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 from app01_employee; print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}
分组查询annotate()
至关于sql
语句中的group by
,是在分组后,对每一个组进行单独的聚合,须要强调的是,在进行单表查询时,annotate()
必须搭配values()
使用:values
(“分组字段”).annotate
(聚合函数),以下
# 表中记录 mysql> select * from app01_employee; +----+-------+--------+------------+------------+--------+ | id | name | gender | birth | department | salary | +----+-------+--------+------------+------------+--------+ | 1 | Egon | 0 | 1997-01-27 | 财务部 | 100.1 | | 2 | Kevin | 1 | 1998-02-27 | 技术部 | 10.1 | | 3 | Lili | 0 | 1990-02-27 | 运营部 | 20.1 | | 4 | Tom | 1 | 1991-02-27 | 运营部 | 30.1 | | 5 | Jack | 1 | 1992-02-27 | 技术部 | 11.2 | | 6 | Robin | 1 | 1988-02-27 | 技术部 | 200.3 | | 7 | Rose | 0 | 1989-02-27 | 财务部 | 35.1 | +----+-------+--------+------------+------------+--------+ # 查询每一个部门下的员工数 res=Employee.objects.values('department').annotate(num=Count('id')) # 至关于sql: # select department,count(id) as num from app01_employee group by department; print(res) # 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]>
跟在annotate
前的values
方法,是用来指定分组字段,即group by
后的字段,而跟在annotate
后的values
方法,则是用来指定分组后要查询的字段,即select
后跟的字段
res=Employee.objects.values('department').annotate(num=Count('id')).values('num') # 至关于sql: # select count(id) as num from app01_employee group by department; print(res) # 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>
跟在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 [{'department': '技术部'}]> # 解析: # 一、跟在annotate前的filter(gender=1) 至关于 where gender = 1,先过滤出全部男员工信息 # 二、values('department').annotate(male_count=Count("id")) 至关于group by department,对过滤出的男员工按照部门分组,而后聚合出每一个部门内的男员工数赋值给字段male_count # 三、跟在annotate后的filter(male_count__gt=2) 至关于 having male_count > 2,会过滤出男员工数超过2人的部门 # 四、最后的values('department')表明从最终的结果中只取部门名
总结
一、values()在annotate()前表示group by的字段,在后表示取值 一、filter()在annotate()前表示where条件,在后表示having
须要注意的是,若是咱们在annotate前没有指定values(),那默认用表中的id字段做为分组依据,而id各不相同,如此分组是没有意义的,以下
res=Employee.objects.annotate(Count('name')) # 每条记录都是一个分组 res=Employee.objects.all().annotate(Count('name')) # 同上
能够修改记录对象属性的值,而后执行save
方法从而完成对单条记录的直接修改
# 一、获取记录对象 obj=Employee.objects.filter(name='Egon')[0] # 二、修改记录对象属性的值 obj.name='EGON' obj.gender=1 # 三、从新保存 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()