我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。
如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库,详见图1
但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下
为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行,详见图2
。。。。。。插图1
原生SQL与ORM的对应关系示例如下
插图2
若图片需要修改,则查看地址:https://www.processon.com/diagraming/588d8be2e4b098bf4d1ecb1d
如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率,下面就让我们来详细学习ORM的使用吧
| class Employee(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) |
| |
| DATABASES = { |
| 'default': { |
| 'ENGINE': 'django.db.backends.mysql', |
| 'NAME': 'db1', |
| 'USER': 'root', |
| 'PASSWORD': '', |
| 'HOST': '127.0.0.1', |
| 'PORT': 3306, |
| 'ATOMIC_REQUEST': True, |
| |
| |
| 'OPTIONS': { |
| "init_command": "SET storage_engine=INNODB", |
| } |
| } |
| } |
| mysql> create database db1; |
| |
| INSTALLED_APPS = [ |
| 'django.contrib.admin', |
| 'django.contrib.auth', |
| 'django.contrib.contenttypes', |
| 'django.contrib.sessions', |
| 'django.contrib.messages', |
| 'django.contrib.staticfiles', |
| 'app01', |
| |
| ] |
| |
| |
| INSTALLED_APPS = [ |
| 'django.contrib.admin', |
| 'django.contrib.auth', |
| 'django.contrib.contenttypes', |
| 'django.contrib.sessions', |
| 'django.contrib.messages', |
| 'django.contrib.staticfiles', |
| 'app01.apps.App01Config', |
| |
| ] |
| LOGGING = { |
| 'version': 1, |
| 'disable_existing_loggers': False, |
| 'handlers': { |
| 'console':{ |
| 'level':'DEBUG', |
| 'class':'logging.StreamHandler', |
| }, |
| }, |
| 'loggers': { |
| 'django.db.backends': { |
| 'handlers': ['console'], |
| 'propagate': True, |
| 'level':'DEBUG', |
| }, |
| } |
| } |
| $ python manage.py makemigrations |
| $ python manage.py migrate |
| |
| # 注意: |
| # 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行 |
| # 2、数据库迁移记录的文件存放于app01下的migrations文件夹里 |
| # 3、了解:使用命令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\base.py
这个路径里的文件(mac或linux请在终端执行pip show django查看django的安装路径,找到base.py即可)
| |
| 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; |
| +------------+---------------+------+-----+---------+----------------+ |
| | 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会按照自己的约束将空转换成默认值后,再提交给数据库执行
| |
| |
| publish = models.CharField(max_length=12,default='人民出版社',null=True) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
方式一:
| |
| obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1) |
| |
| obj.save() |
方式二:
| |
| obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1) |
模型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的对象,为了后续描述方便,我们统一将模型类的对象称为"记录对象",每一个”记录对象“都唯一对应表中的一条记录,
| |
| |
| |
| obj=Employee.objects.get(id=1) |
| print(obj.name,obj.birth,obj.salary) |
| |
| |
| |
| |
| obj=Employee.objects.first() |
| print(obj.id,obj.name) |
| |
| |
| |
| |
| obj = Employee.objects.last() |
| print(obj.id, obj.name) |
| |
| |
| |
| |
| res = Employee.objects.count() |
| print(res) |
| |
| |
| class Employee(models.Model): |
| ...... |
| |
| def __str__(self): |
| return "<%s:%s>" %(self.id,self.name) |
| |
Part2:
!!!强调!!!:下述方法查询的结果都有可能包含多个记录对象,为了存放查询出的多个记录对象,django的ORM自定义了一种数据类型Queryeset,所以下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象
| |
| |
| |
| queryset_res=Employee.objects.filter(department='技术部') |
| |
| |
| |
| |
| |
| queryset_res=Employee.objects.exclude(department='技术部') |
| |
| |
| |
| |
| queryset_res = Employee.objects.all() |
| |
| |
| |
| |
| queryset_res = Employee.objects.order_by("salary","-id") |
| |
| |
| |
| |
| queryset_res = Employee.objects.values('id','name') |
| print(queryset_res) |
| print(queryset_res[0]['name']) |
| |
| |
| |
| |
| queryset_res = Employee.objects.values_list('id','name') |
| print(queryset_res) |
| print(queryset_res[0][1]) |
Part3:
Part2中所示查询API的返回值都是QuerySet类型的对象,QuerySet类型是django ORM自定义的一种数据类型,专门用来存放查询出的多个记录对象,该类型的特殊之处在于
1、queryset类型类似于python中的列表,支持索引操作
| |
| queryset_res=Employee.objects.filter(department='技术部') |
| |
| 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) |
Part4:
其他查询API
| |
| |
| |
| queryset_res = Employee.objects.order_by("salary", "-id").reverse() |
| |
| |
| |
| |
| res = Employee.objects.filter(id=100).exists() |
| print(res) |
| |
| |
| |
| |
| res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct() |
| print(res) |
| |
| res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() |
| print(res1) |
在上面所有的例子中,我们在进行条件过滤时,都只是用某个字段与某个具体的值做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较两个不同字段的值,如下
| |
| from django.db.models import F |
| Book.objects.filter(commnetNum__lt=F('keepNum')) |
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
| |
| from django.db.models import F |
| Book.objects.filter(commnetNum__lt=F('keepNum')*2) |
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
| Book.objects.all().update(price=F("price")+30) |
更新字段等于字段本身再拼接一个字符串应该这样做
| from django.db.models.functions import Concat |
| from django.db.models import Value |
| |
| Employee.objects.filter(nid__lte=3).update(name=Concat(F('name'),Value('_sb'))) |
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")) |
| |
| |
Q
对象可以使用~
操作符取反,相当于NOT
| from django.db.models import Q |
| Employee.objects.filter(~Q(id__gt=5) | Q(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) |
| |
| |
聚合查询aggregate()是把所有查询出的记录对象整体当做一个组,我们可以搭配聚合函数来对整体进行一个聚合操作
| from django.db.models import Avg, Max, Sum, Min, Max, Count |
| |
| |
| res1=Employee.objects.aggregate(Avg("salary")) |
| print(res1) |
| |
| |
| res2=Employee.objects.all().aggregate(Avg("salary")) |
| print(res2) |
| |
| res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) |
| print(res3) |
aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,例如
| Avg("salary") 合成的名字为 'salary__avg' |
若我们想定制字典的key名,我们可以指定关键参数,如下
| res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) |
| |
| print(res1) |
如果我们想得到多个聚合结果,那就需要为aggregate传入多个参数
| res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) |
| |
| |
| print(res1) |
分组查询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')) |
| |
| |
| |
| print(res) |
| |
跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段
| res=Employee.objects.values('department').annotate(num=Count('id')).values('num') |
| |
| |
| |
| print(res) |
| |
跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件,如下
| |
| res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department') |
| |
| print(res) |
| |
| |
| |
| |
| |
| |
总结:
| 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方法从而完成对单条记录的直接修改
| |
| obj=Employee.objects.filter(name='Egon')[0] |
| |
| obj.name='EGON' |
| obj.gender=1 |
| |
| obj.save() |
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下也都有一个 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() |