使用QuerySet操作数据
Django对数据库的数据进行增、删、改操作是借助内置ORM框架所提供的API方法实现的,简单来说,ORM框架的数据操作API是在QuerySet类里面定义的,然后由开发者自定义的模型对象调用QuerySet类,从而实现数据操作。
新增数据
Django提供了多种数据新增方法,开发者可以根据实际情况以及个人使用习惯选择某一种新增方式。为了更好地演示数据库的增、删、改操作,在项目babys使用Shell模式(启动命令行和执行脚本)进行讲述,该模式方便开发人员开发和调试程序。在PyCharm的Terminal下开启Shell模式,输入pythonmanage.py shell指令即可,如图4-10所示。
在Shell模式下,若想对数据表commodity_types新增数据,则可输入以下代码实现:
上述代码是对项目应用commodity的模型Types进行实例化,再对实例化对象的属性进行赋值,从而实现数据表commodity_types的数据新增,代码说明如下:
(1)从项目应用commodity的models.py文件中导入模型Types。
(2)对模型Types声明并实例化,生成对象t。
(3)对对象t的属性进行逐一赋值,对象t的属性来自于模型Types所定义的字段。完成赋值后,再由对象t调用save方法进行数据保存。代码运行结束后,在数据表commodity_types里查看数据的新增情况,如图4-11所示。
除了上述方法外,数据新增还有以下3种常见方法,代码如下:
除了上述方法外,数据新增还有以下3种常见方法,代码如下:
在执行数据新增时,为了保证数据的有效性,我们需要对数据进行去重判断,确保数据不会重复新增。以往的方案都是对数据表进行查询操作,如果查询的数据不存在,就执行数据新增操作。为了简化这一过程,Django提供了get_or_create方法,使用如下:
get_or_create根据每个模型字段的值与数据表的数据进行判断,判断方式如下:
只要有一个模型字段的值与数据表的数据不相同(除主键之外),就会执行数据新增操作。如果每个模型字段的值与数据表的某行数据完全相同,就不执行数据新增,而是返回这行数据的数据对象,比如对上述的字典d重复执行get_or_create,第一次是执行数据新增(若执行结果显示为True,则代表数据新增),第二次是返回数据表已有的数据信息(若执行结果显示为False,则数据表已存在数据,不再执行数据新增),如图4-12所示。
除了get_or_create之外,Django还定义了update_or_create方法,这是判断当前数据在数据表里是否存在,若存在,则进行更新操作,否则在数据表里新增数据,使用说明如下:
update_or_create是根据字典d的内容查找数据表的数据,如果能找到相匹配的数据,就执行数据修改,修改内容以字典格式传递给参数defaults即可;如果在数据表找不到匹配的数据,就将字典d的数据新增到数据表里。如果要对某个模型执行数据批量新增操作,那么可以使用bulk_create方法实现,只需将数据对象以列表或元组的形式传入bulk_create方法即可:
在使用bulk_create之前,数据类型为模型Types的实例化对象,并且在实例化过程中设置每个字段的值,最后将所有实例化对象放置在列表或元组里,以参数的形式传递给bulk_create,从而实现数据的批量新增操作。
更新数据
更新数据的步骤与数据新增的步骤大致相同,唯一的区别在于数据对象来自数据表,因此需要执行一次数据查询,查询结果以对象的形式表示,并将对象的属性进行赋值处理,代码如下:
上述代码获取数据表commodity_types里主键id等于1的数据对象t,然后修改数据对象t的firsts属性,从而完成数据修改操作。打开数据表commodity_types查看数据修改情况,如图4-13所示。
除此之外,我们还可以使用update方法实现数据更新,使用方法如下:
在Django 2.2或以上版本新增了数据批量更新方法bulk_update,它的使用与批量新增方法bulk_create相似,使用说明如下:
删除数据
删除数据有3种方式:删除数据表的全部数据、删除一行数据和删除多行数据,实现方式如下:
删除数据的过程中,如果删除的数据设有外键字段,就会同时删除外键关联的数据。比如我们在项目应用index的models.py定义模型PersonInfo和Vocation,如下所示:
如果删除模型PersonInfo里主键等于3的数据(简称为数据A),那么在模型Vocation中,有些数据(简称为数据B)关联了数据A,在删除数据A时,也会同时删除数据B。假设模型Vocation中有3行数据(数据B)关联了模型PersonInfo的数据A,数据删除过程如下所示:
从模型Vocation得知,外键字段的参数on_delete用于设置数据删除模式,比如模型Vocation的外键字段name设为CASCADE模式,不同的删除模式会影响数据删除结果,说明如下:
- PROTECT模式:如果删除的数据设有外键字段并且关联其他数据表的数据,就提示数据删除失败。
- SET_NULL模式:执行数据删除并把其他数据表的外键字段设为Null,外键字段必须将属性Null设为True,否则提示异常。
- SET_DEFAULT模式:执行数据删除并把其他数据表的外键字段设为默认值。
- SET模式:执行数据删除并把其他数据表的外键字段关联其他数据。
- DO_NOTHING模式,不做任何处理,删除结果由数据库的删除模式决定。
查询单表数据
在更新数据时,往往只修改某行数据的内容,因此在更新数据之前还要对模型进行查询操作,确定数据表某行的数据对象,最后才执行数据更新操作。我们知道数据库设有多种数据查询方式,如单表查询、多表查询、子查询和联合查询等,而Django的ORM框架对不同的查询方式定义了相应的API方法。以数据表index_personinfo和index_vocation(即4.4.3节定义的模型PersonInfo和Vocation)为例,并为数据表index_personinfo和index_vocation添加数据,数据内容如图4-14所示。
然后在项目babys的Shell模式下使用ORM框架提供的API方法实现数据查询,代码如下:
上述例子讲述了开发中常用的数据查询方法,但有时需要设置不同的查询条件来满足多方面的查询要求。上述的查询条件filter和get是使用等值的方法来匹配结果。若想使用大于、不等于或模糊查询的匹配方法,则可在查询条件filter和get里使用表4-1所示的匹配符实现。
从表4-1中可以看到,只要在查询的字段末端设置相应的匹配符,就能实现不同的数据查询方式。例如在数据表index_vocation中查询字段payment大于8000的数据,在Shell模式下使用匹配符__gt执行数据查询,代码如下:
综上所述,在查询数据时可以使用查询条件get或filter实现,但是两者的执行过程存在一定的差异,说明如下:查询条件get:查询字段必须是主键或者唯一约束的字段,并且查询的数据必须存在,如果查询的字段有重复值或者查询的数据不存在,程序就会抛出异常信息。查询条件filter:查询字段没有限制,只要该字段是数据表的某一字段即可。查询结果以列表形式返回,如果查询结果为空(查询的数据在数据表中找不到),就返回空列表。
查询多表数据
在日常的开发中,常常需要对多张数据表同时进行数据查询。多表查询需要在数据表之间建立表关系才能够实现。一对多或一对一的表关系是通过外键实现关联的,而多表查询分为正向查询和反向查询。以模型PersonInfo和Vocation为例,模型Vocation定义的外键字段name关联到模型PersonInfo。如果查询对象的主体是模型Vocation,通过外键字段name去查询模型PersonInfo的关联数据,那么该查询称为正向查询;如果查询对象的主体是模型PersonInfo,要查询它与模型Vocation的关联数据,那么该查询称为反向查询。无论是正向查询还是反向查询,两者的实现方法大致相同,代码如下:
正向查询和反向查询还能在查询条件(filter或get)里使用,这种方式用于查询条件的字段不在查询对象里,比如查询对象为模型Vocation,查询条件是模型PersonInfo的某个字段,对于这种查询可以采用以下方法实现:
无论是正向查询还是反向查询,它们在数据库里需要执行两次SQL查询,第一次是查询某张数据表的数据,再通过外键关联获取另一张数据表的数据信息。为了减少查询次数,提高查询效率,我们可以使用select_related或prefetch_related方法实现,该方法只需执行一次SQL查询就能实现多表查询。select_related主要针对一对一和一对多关系进行优化,它是使用SQL的JOIN语句进行优化的,通过减少SQL查询的次数来进行优化和提高性能,其使用方法如下:
除此之外,select_related还可以支持3个或3个以上的数据表同时查询,以下面的例子进行说明。
在上述模型中,模型Person通过外键living关联模型City,模型City通过外键province关联模型Province,从而使3个模型形成一种递进关系。我们对上述新定义的模型执行数据迁移并在数据表里插入数据,如图4-15所示。
例如,查询Tom现在所居住的省份,首先通过模型Person和模型City查出Tom所居住的城市,然后通过模型City和模型Province查询当前城市所属的省份。因此,select_related的实现方法如下:
从上述例子可以发现,通过设置select_related的参数值可实现3个或3个以上的多表查询。例子中的参数值为living__province,参数值说明如下:living是模型Person的外键字段,该字段指向模型City。
province是模型City的外键字段,该字段指向模型Province。两个外键字段之间使用双下画线连接,在查询过程中,模型Person的外键字段living指向模型City,再从模型City的外键字段province指向模型Province,从而实现3个或3个以上的多表查询。
prefetch_related和select_related的设计目的很相似,都是为了减少SQL查询的次数,但是实现的方式不一样。select_related是由SQL的JOIN语句实现的,但是对于多对多关系,使用select_related会增加数据查询时间和内存占用;而prefetch_related是分别查询每张数据表,然后由Python语法来处理它们之间的关系,因此对于多对多关系的查询,prefetch_related更有优势。
我们在项目应用index的models.py里定义模型Performer和Program,分别代表人员信息和节目信息,然后对模型执行数据迁移,生成相应的数据表,模型定义如下:
数据迁移成功后,在数据表index_performer和index_program中分别添加人员信息和节目信息,然后在数据表index_program_performer中设置多对多关系,如图4-16所示。
例如,查询“喜洋洋”节目有多少个人员参与演出,首先从节目表index_program里找出“喜洋洋”的数据信息,然后通过外键字段performer获取参与演出的人员信息,实现过程如下:
从上述例子看到,prefetch_related的使用与select_related有一定的相似之处。如果是查询一对多关系的数据信息,那么两者皆可实现,但select_related的查询效率更佳。除此之外,Django的ORM框架还提供很多API方法,可以满足开发中各种复杂的需求。
执行原生SQL语句
Django在查询数据时,大多数查询都能使用ORM提供的API方法,但对于一些复杂的查询可能难以使用ORM的API方法实现,因此Django引入了SQL语句的执行方法,有以下3种实现方法。
- extra:结果集修改器,一种提供额外查询参数的机制。
- raw:执行原始SQL并返回模型实例对象。
- execute:直接执行自定义SQL。
extra适合用于ORM难以实现的查询条件,将查询条件使用原生SQL语法实现,此方法需要依靠模型对象,在某程度上可防止SQL注入。在PyCharm里打开extra源码,如图4-17所示,它一共定义了6个参数,每个参数说明如下:
- select:添加新的查询字段,即新增并定义模型之外的字段。
- where:设置查询条件。
- params:如果where设置了字符串格式化%s,那么该参数为where提供数值。
- tables:连接其他数据表,实现多表查询。
- order_by:设置数据的排序方式。
- select_params:如果select设置字符串格式化%s,那么该参数为select提供数值。
上述参数都是可选参数,我们可根据实际情况选择所需的参数。以模型Vocation为例,使用extra实现数据查询,代码如下:
上述参数都是可选参数,我们可根据实际情况选择所需的参数。以模型Vocation为例,使用extra实现数据查询,代码如下:
下一步分析raw的语法,它和extra所实现的功能是相同的,只能实现数据查询操作,并且也要依靠模型对象,但从使用角度来说,raw更为直观易懂。在PyCharm里打开raw源码,如图4-18所示,它一共定义了4个参数,每个参数说明如下:
- raw_query:SQL语句。
- params:如果raw_query设置字符串格式化%s,那么该参数为raw_query提供数值。
- translations:为查询的字段设置别名。
- using:数据库对象,即Django所连接的数据库。
上述参数只有raw_query是必选参数,其他参数可根据需求自行选择。我们以模型Vocation为例,使用raw实现数据查询,代码如下:
最后分析execute的语法,它执行SQL语句无须经过Django的ORM框架。我们知道Django连接数据库需要借助第三方模块实现连接过程,如MySQL的mysqlclient模块和SQLite的sqlite3模块等,这些模块连接数据库之后,可通过游标的方式来执行SQL语句,而execute就是使用这种方式执行SQL语句,使用方法如下:
execute能够执行所有的SQL语句,但很容易受到SQL注入攻击,一般情况下不建议使用这种方式实现数据操作。尽管如此,它能补全ORM框架所缺失的功能,如执行数据库的存储过程。