Django之模型层:表操作
一、ORM简介
- MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
- ORM是“对象-关系-映射”的简称。
#sql中的表 #创建表: CREATE TABLE employee( id INT PRIMARY KEY auto_increment , name VARCHAR (20), gender BIT default 1, birthday DATA , department VARCHAR (20), salary DECIMAL (8,2) unsigned, ); #sql中的表纪录 #添加一条表纪录: INSERT employee (name,gender,birthday,salary,department) VALUES ("alex",1,"1985-12-12",8000,"保洁部"); #查询一条表纪录: SELECT * FROM employee WHERE age=24; #更新一条表纪录: UPDATE employee SET birthday="1989-10-24" WHERE id=1; #删除一条表纪录: DELETE FROM employee WHERE name="alex" #python的类 class Employee(models.Model): id=models.AutoField(primary_key=True) name=models.CharField(max_length=32) gender=models.BooleanField() birthday=models.DateField() department=models.CharField(max_length=32) salary=models.DecimalField(max_digits=8,decimal_places=2) #python的类对象 #添加一条表纪录: emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部") emp.save() #查询一条表纪录: Employee.objects.filter(age=24) #更新一条表纪录: Employee.objects.filter(id=1).update(birthday="1989-10-24") #删除一条表纪录: Employee.objects.filter(name="alex").delete() 示例
我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。
如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库,详见图1
但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下
#1. sql语句的执行效率:应用开发程序员需要耗费一大部分精力去优化sql语句 #2. 数据库迁移:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题
为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行,详见图2
。。。。。。插图1
原生SQL与ORM的对应关系示例如下
如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率,下面就让我们来详细学习ORM的使用吧
django测试环境搭建
import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "one_search.settings") import django django.setup() # 你就可以在下面测试django任何的py文件
Django终端打印SQL语句
如果你想查看orm语句内部真正的sql语句有两种方式
1.如果是queryset对象 可以直接点query查看 2.配置文件中 直接配置 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level': 'DEBUG', }, }}
#只要是queryset对象就可以无限制的点queryset对象的方法 queryset.filter().filter().filter()
二 单表操作
2.1 按步骤创建表
2.1.1
创建django项目,新建名为app01的app,在app01的models.py中创建模型
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)
2.1.2
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 } } }
2.1.3
在链接mysql数据库前,必须事先创建好数据库
mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上
2.1.4
确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.x与django1.x处理添加方式不同
# 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,按照规律依次添加即可 ]
2.1.5
如果想打印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', }, } }
2.1.6
最后我们需要的驱动是PyMySQL,然后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :
NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的__init__,在里面写入:
import pymysql pymysql.install_as_MySQLdb()
$ 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
这个路径里的文件
# 注释下述两行内容即可 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会按照自己的约束将空转换成默认值后,再提交给数据库执行
2.1.7
在表生成之后,如果需要增加、删除、修改表中字段,需要这么做
# 一:增加字段 #1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值 publish = models.CharField(max_length=12,default='人民出版社',null=True) #1.2、重新执行那两条数据库迁移命令 # 二:删除字段 #2.1 直接注释掉字段 #2.2 重新执行那两条数据库迁移命令 # 三:修改字段 #2.1 将模型类中字段修改 #2.2 重新执行那两条数据库迁移命令
2.1.8
更多字段和参数
每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR
数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:
字段
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField(models.IntegerField): def db_type(self, connection): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
参数
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
元信息
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural
2.2记录
删除,直接注释掉字段,执行数据库迁移命令即可
新增字段,在类里直接新增字段,直接执行数据库迁移命令会提示输入默认值,此时需要设置
publish = models.CharField(max_length=12,default='人民出版社',null=True)
注意:
1 数据库迁移记录都在 app01下的migrations里
2 使用showmigrations命令可以查看没有执行migrate的文件
3 makemigrations是生成一个文件,migrate是将更改提交到数据量
2.2.1 添加记录
方式一:
# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录 obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1) # 2、调用对象下的save方法,即可以将一条记录插入数据库 obj.save()
方式二:
# 每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示 obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
2.2.2 查询记录
2.2.2.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 # 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.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对象中包含了查询出的多个记录对象
# 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 [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]> 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, 'Tom'), (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 [{'name': 'Egon', 'salary': Decimal('100.1')}]> res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct() print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>
2.2.2.2 基于双下划线的模糊查询
Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。
''' 正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表 '''
一对多查询
# 练习: 查询苹果出版社出版过的所有书籍的名字与价格(一对多) # 正向查询 按字段:publish queryResult=Book.objects .filter(publish__name="苹果出版社") .values_list("title","price") # 反向查询 按表名:book queryResult=Publish.objects .filter(name="苹果出版社") .values_list("book__title","book__price") 查询的本质一样,就是select from的表不一样
# 正向查询按字段,反向查询按表名小写 # 查询红楼梦这本书出版社的名字 # select * from app01_book inner join app01_publish # on app01_book.publish_id=app01_publish.nid ret=Book.objects.filter(name='红楼梦').values('publish__name') print(ret) ret=Publish.objects.filter(book__name='红楼梦').values('name') print(ret)
多对多查询
# 练习: 查询alex出过的所有书籍的名字(多对多) # 正向查询 按字段:authors: queryResult=Book.objects .filter(authors__name="yuan") .values_list("title") # 反向查询 按表名:book queryResult=Author.objects .filter(name="yuan") .values_list("book__title","book__price")
# 正向查询按字段,反向查询按表名小写 # 查询红楼梦这本书出版社的名字 # select * from app01_book inner join app01_publish # on app01_book.publish_id=app01_publish.nid ret=Book.objects.filter(name='红楼梦').values('publish__name') print(ret) ret=Publish.objects.filter(book__name='红楼梦').values('name') print(ret) # sql 语句就是from的表不一样 # -------多对多正向查询 # 查询红楼梦所有的作者 ret=Book.objects.filter(name='红楼梦').values('authors__name') print(ret) # ---多对多反向查询 ret=Author.objects.filter(book__name='红楼梦').values('name') ret=Author.objects.filter(book__name='红楼梦').values('name','author_detail__addr') print(ret)
多对多关系其它常用API:
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被关联对象集合 book_obj.authors.set() #先清空再设置
一对一查询
# 查询alex的手机号 # 正向查询 ret=Author.objects.filter(name="alex").values("authordetail__telephone") # 反向查询 ret=AuthorDetail.objects.filter(author__name="alex").values("telephone")
# 查询lqz的手机号 # 正向查 ret=Author.objects.filter(name='lqz').values('author_detail__telephone') print(ret) # 反向查 ret= AuthorDatail.objects.filter(author__name='lqz').values('telephone') print(ret)
进阶练习(连续跨表)
# 练习: 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name="人民出版社") .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects .filter(name="人民出版社") .values_list("book__title","book__authors__age","book__authors__name") # 练习: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="151") .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="151") .values("book__title","book__publish__name")
# ----进阶练习,连续跨表 # 查询手机号以33开头的作者出版过的书籍名称以及书籍出版社名称 # author_datail author book publish # 基于authorDatail表 ret=AuthorDatail.objects.filter(telephone__startswith='33').values('author__book__name','author__book__publish__name') print(ret) # 基于Author表 ret=Author.objects.filter(author_detail__telephone__startswith=33).values('book__name','book__publish__name') print(ret) # 基于Book表 ret=Book.objects.filter(authors__author_detail__telephone__startswith='33').values('name','publish__name') print(ret) # 基于Publish表 ret=Publish.objects.filter(book__authors__author_detail__telephone__startswith='33').values('book__name','name') print(ret)
related_name
publish = ForeignKey(Blog, related_name='bookList')
# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多) # 反向查询 不再按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name="人民出版社") .values_list("bookList__title","bookList__price")
反向查询时,如果定义了related_name ,则用related_name替换表名,例如:
2.2.2.3 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)
Q查询
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
对象可以使用~
操作符取反,相当于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;
2.2.24 聚合查询
聚合查询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} # 2、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')}
2.2.2.5 分组查询
分组查询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': '技术部'}]> # 解析: # 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')) # 同上
2.2.3 修改记录
2.2.3.1 直接修改单条记录
可以修改记录对象属性的值,然后执行save方法从而完成对单条记录的直接修改
# 1、获取记录对象 obj=Employee.objects.filter(name='Egon')[0] # 2、修改记录对象属性的值 obj.name='EGON' obj.gender=1 # 3、重新保存 obj.save()
2.2.3.2 修改QuerySet中的所有记录对象
QuerySet对象下的update()方法可以更QuerySet中包含的所有对象,该方法会返回一个整型数值,表示受影响的记录条数(相当于sql语句执行结果的rows)
queryset_obj=Employee.objects.filter(id__gt=5) rows=queryset_obj.update(name='EGON',gender=1)
2.2.4 删除记录
2.2.4.1 直接删除单条记录
可以直接调用记录对象下的delete方法,该方法运行时立即删除本条记录而不返回任何值,如下
obj=Employee.objects.first() obj.delete()
2.2.4.2 删除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()
三、查询练习
练习:统计每一本书作者个数
from django.db.models import Avg, Max, Sum, Min, Max, Count book_list = models.Book.objects.all().annotate(author_num=Count("authors")) for book in book_list: print(book.name) print(book.author_num) book_list = models.Book.objects.all().annotate(author_num=Count("authors")).values('name','author_num') print(book_list)
练习:统计每一个出版社的最便宜的书
publishList=Publish.objects.annotate(MinPrice=Min("book__price")) for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice)
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects.annotate(MinPrice=Min("book__price")).values_list("name","MinPrice") print(queryResult)
练习:统计每一本以py开头的书籍的作者个数:
queryResult=Book.objects.filter(title__startswith="Py").annotate(num_authors=Count('authors'))
练习:统计不止一个作者的图书:(作者数量大于一)
ret=models.Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('name','author_num') print(ret)
练习:根据一本图书作者数量的多少对查询集 QuerySet
进行排序:
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
练习:查询各个作者出的书的总价格:
ret=models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price") print(ret)
练习:查询每个出版社的名称和书籍个数
ret=models.Publish.objects.all().annotate(c=Count('book__name')).values('name','c') print(ret)
单表下的分组查询
''' 查询每一个部门名称以及对应的员工数 emp: id name age salary dep 1 alex 12 2000 销售部 2 egon 22 3000 人事部 3 wen 22 5000 人事部 ''' # select count(id) from emp group by dep # 示例一:查询每一个部门的名称,以及平均薪水 # select dep,Avg(salary) from app01_emp group by dep from django.db.models import Avg, Count, Max, Min ret=Emp.objects.values('dep').annotate(Avg('salary')) # 重新命名 ret=Emp.objects.values('dep').annotate(avg_salary=Avg('salary')) print(ret) # ---*******单表分组查询ORM总结:表名.objects.values('group by 的字段').annotate(聚合函数('统计的字段')) # 示例2 查询每个省份对应的员工数 ret=Emp.objects.values('province').annotate(Count('id')) ret=Emp.objects.values('province').annotate(c=Count('id')) print(ret) # 补充知识点: ret=Emp.objects.all() # select * from emp ret=Emp.objects.values('name') # select name from emp # ****单表下,按照id进行分组是没有任何意义的 ret=Emp.objects.all().annotate(Avg('salary')) print(ret) # ******多表分组查询 # 查询每一个出版社出版的书籍个数 ret=Book.objects.values('publish_id').annotate(Count('nid')) print(ret) # 查询每个出版社的名称以及出版社书的个数(先join在跨表分组) # 正向 ret=Publish.objects.values('name').annotate(Count('book__name')) ret=Publish.objects.values('nid').annotate(c=Count('book__name')).values('name','c') print(ret) # 反向 ret=Book.objects.values('publish__name').annotate(Count('name')) ret=Book.objects.values('publish__name').annotate(c=Count('name')).values('publish__name','c') print(ret) # 查询每个作者的名字,以及出版过书籍的最高价格 ret=Author.objects.values('pk').annotate(c=Max('book__price')).values('name','c') print(ret) # 跨表查询的模型:每一个后表模型.objects.value('pk').annotate(聚合函数('关联表__统计字段')).values() # 查询每一个书籍的名称,以及对应的作者个数 ret=Book.objects.values('pk').annotate(c=Count('authors__name')).values('name','c') print(ret) # 统计不止一个作者的图书 ret=Book.objects.values('pk').annotate(c=Count('authors__name')).filter(c__gt=1).values('name','c') print(ret)