Django(6)之模型与数据库

Django(6)之模型与数据库

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

Django 对各种数据库提供了很好的支持,包括: PostgreSQL、MySQL、SQLite 和 Oracle,而且为这些数据库提供了统一的调用 API,这些 API 统称为 ORM 框架。
通过使用 Django 内置的 ORM 框架可以实现数据库连接和读写操作。

构建模型

ORM 框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。从效果上说,其实是创建了一个可在编程语言中使用的“虚拟对象数据
库”,通过对虚拟对象数据库操作从而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在 Django 中,虚拟对象数据库也称为模型。通过模型实
现对目标数据库的读写操作,实现方法如下:

  1. 配置目标数据库信息,主要在 settings.py 中设置数据库信息,具体配置步骤可查看第2节
  2. 构建虚拟对象数据库,在 App 的 models.py 文件中以类的形式定义模型。
  3. 通过模型在目标数据库中创建相应的数据表。
  4. 在视图函数中通过对模型操作实现目标数据库的读写操作。

本节主要讲述如何构建模型并通过模型在目标数据库中生成相应的数据表。以 test01 项目为例其配置信息如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test01_db',
        'USER': 'root',
        'PASSWORD': '123',
        'HOST': '10.0.1.200',
        'PORT': '3306',
    }
}

在上面配置的 HOST 对应主机创建 test01_db 库,并授权用户可远程登录:

> grant all on *.* to root@'%' identified by '123';
> flush privilges;
> create database test01_db default charset utf8mb4;

test01_db 数据库当前没有数据表,数据表只能通过模型创建,因为 Django 对模型和目标数据库之间有自身的映射规则,如果自己在数据库中创建数据表,可能不一定符合 Django 的建表规则,从而导致模型和目标数据库无法建立通信联系。大概了解项目的环境后,在项目 indexmodels.py 文件中定义模型,代码如下:

from django.db import models


# 产品分类表
class Type(models.Model):
    id = models.AutoField(primary_key=True)
    type_name = models.CharField(max_length=20)


# 产品信息表
class Product(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=50)
    weight = models.CharField(max_length=20)
    size = models.CharField(max_length=20)
    type = models.ForeignKey(Type, on_delete=models.CASCADE)

上述代码分别定义了模型 TypeProduct,定义说明如下:

  • 模型以类的形式进行定义,并且继承 Django 的 models.Model 类。一个类代表目标数据库的一张数据表,类的命名一般以首字母大写开头。
  • 模型的字段以类属性进行定义,如 id = models.IntegerField(primary_key=True),代表在数据表 Type 中命名一个名为 id 的字段,该字段的数据类型为整型并设置为主键。

完成模型的定义后,接着在目标数据库中创建相应的数据表,在目标数据库中创建表是通过 Django 的管理工具 manage.py 完成的,创建表的指令如下:

# 根据 models.py 生成相关 py 文件,用于创建数据表
test01> python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Product
    - Create model Type
    - Add field type to product
# 创建数据表
test01> python manage.py migrate

在目标数据库中创建数据表需要执行两次指令,分别是 makemigrationsmigrate 指令。创建过程说明如下:

  • makemigrations 指令用于将 index 所定义的模型生成 0001_initial.py 文件,该文件存放在 indexmigrations 文件夹。
  • 0001_initial.py 文件将 models.py 的内容生成数据表的脚本代码。而 migrate 指令根据脚本代码在目标数据库中生成相对应的数据表。指令运行完成后,可在数据库看到已创建的数据表。

查看数据库的数据表:

[test01_db]> show tables;
+----------------------------+
| Tables_in_test01_db        |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| index_product              |
| index_type                 |
+----------------------------+
12 rows in set (0.00 sec)

可以看到,数据表 index_productindex_type 是由 index 的模型所创建的,分别对应模型 ProductType。其他数据表是 Django 内置功能所使用的数据表,分别是会话 session、用户认证管理和 Admin 日志记录等。
在上述例子中,我们创建了数据表 index_productindex_type,而表字段是在模型中定义的,在模型 TypeProduct 中定义的字段类型有整型和字符串类型,但在实际开发中,我们需要定义不同的数据类型来满足各种需求,因此 Django 划分了多种不同的数据类型,如下所示:

  • models.AutoField:默认会生成一个名为 id 的字段并为 int 类型;
  • models.CharField:字符串类型;
  • models.BooleanField:布尔类型;
  • models.ComaSeparatedIntegerField:用逗号分隔的整数类型;
  • models.DateField:日期(date)类型;
  • models.DateTimeField:日期类型(datetime)类型;
  • models.Decimal:十进制小数类型;
  • models.EmailField:字符串类型(邮箱正则表达式);
  • models.FloatField:浮点类型;
  • models.IntegerField:整数类型;
  • models.BigIntegerField:长整数类型;
  • models.IPAddressField:字符串类型(IPv4 正则表达式);
  • models.GenericIPAddressField:字符串类型,参数 protocol 可以是:bothIPv4IPv6,验证 IP 地址;
  • models.NullBooleanField:允许为空的布尔类型;
  • models.PositiveIntegerField:正整数的整数类型;
  • models.PositiveSmallIntegerField:小正整数类型;
  • models.TextField:长文本类型;
  • models.TimeField:时间类型,显示时分秒 HH:MM[:ss[.uuuuuu]]
  • models.URLField:字符串,地址为正则表达式;
  • models.BinaryField:二进制数据类型;

从上述可知,Django 提供的字段类型还会对数据进行正则处理和验证功能等,进一步完善了数据的严谨性。除了表字段类型之外,每个表字段还可以设置相应的参数,使得表字段更加完善。字段参数说明如下所示:

  • Null:如为 True,字段是否可以为空;
  • Blank:如为 True,设置在 Admin 站点管理中添加数据时可允许空值;
  • Default:设置默认值;
  • primary_key:如为 True,将字段设置为主键;
  • db_column:设置数据库中的字段名称;
  • Unique:如为 True,将字段设置成唯一属性,默认为 False
  • db_index:如为 True,为字段添加数据库索引;
  • verbose_name:在 Admin 站点管理设置字段的显示名称;
  • related_name:关联对象反向引用描述符,用于多表查询,可解决一个数据表有两个外键同时指向另一个数据表而出现重名的问题;

数据表的关系

一个模型对应目标数据库的一个数据表,但我们知道,每个数据表之间是可以存在关联的,表与表之间有三种关系:一对一、一对多和多对多。

一对一

一对一存在于在两个数据表中,第一个表的某一行数据只与第二个表的某一行数据相关,同时第二个表的某一行数据也只与第一个表的某一行数据相关,这种表关系
被称为一对一关系,以下面两张表为例:

image.png

上面两个表的 ID 字段是一一对应的,并且不会在同一表中有重复 ID,使用这种关系通常是一个数据表有太多字段,将常用的字段抽取出来并组成一个新的数据表。在模型中可以通过 OnoToOneField 来构建数据表的一对一关系,代码如下:

class Performer(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    nationality = models.CharField(max_length=20)
    masterpiece = models.CharField(max_length=50)


class PerformerInfo(models.Model):
    id = models.IntegerField(primary_key=True)
    performer = models.OneToOneField(Performer, on_delete=models.CASCADE)
    birth = models.CharField(max_length=20)
    elapse = models.CharField(max_length=20)

一对多

一对多存在于两个或两个以上的数据表中,第一个表的数据可以与第二个表的一到多行数据进行关联,但是第二个表的每一行数据只能与第一个表的某一行进行关联,以下面两张表为例:

image.png

在上面的第二个表中,字段 ID 的数据可以重复并且在第一个表中找到对应的数据,而第一个表的字段 ID 是唯一的,这是一种最为常见的表关系。在模型中可以通过
ForeignKey 来构建数据表的一对多关系,代码如下:

class Performer(models.Model):
   id = models.IntegerField(primary_key=True)
   name = models.CharField(max_length=20)
   nationality = models.CharField(max_length=20)


class Program(models.Model):
   id = models.IntegerField(primary_key=True)
   performer = models.ForeignKey(Performer, on_delete=models.CASCADE)
   name = models.CharField(max_length=20)

多对多

多对多存在于在两个或两个以上的数据表中,第一个表的某一行数据可以与第二个表的一到多行数据进行关联,同时在第二个表中的某一行数据也可以与第一个表的一到多行数据进行关联,以下面两个三个表为例进行说明。

image.png

从上面的三个数据表中可以发现,一个演员可以参加多个节目,而一个节目也可以由多个演员来共同演出。每个表的字段 ID 都是唯一的,在第三个表中可以发现,节目 ID 和演员 ID 出现了重复的数据,分别对应表 1 和表 2 的字段 ID,多对多关系需要使用新的数据表来管理两个表的数据关系。在模型中可以通过 ManyToManyField 来构建数据表多的对多关系,代码如下:

class Performer(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    nationality = models.CharField(max_length=20)


class Program(models.Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    performer = models.ManyToManyField(Performer)

数据表的读写

前两节主要通过对模型的定义来构建目标数据库的数据表,而本节主要通过模型的操作来实现目标数据库的读写操作。数据库的读写操作主要对数据进行增、删、改、查。以前面的数据表 index_typeindex_product 为例,分别在两个数据表中添加如下数据:

insert into index_type values(1,'手机'),(2,'平板电脑'),(3,'智能穿戴'),(4,'通用配件');
insert into index_product values(1,'荣耀V10','172g','157.00*74.98*6.97mm',1);
insert into index_product values(2,'HUAWEI nova 2s','169g','156.9*75.1*.7.5mm',1);
insert into index_product values(3,'荣耀Waterplay','465g','248*173*7.8mm',2);
insert into index_product values(4,'荣耀畅玩平板','460g','229.8*159.8*7.95mm',2);
insert into index_product values(5,'PORSCHE DESIGN','64g','45*48.3*12.6mm',3);
insert into index_product values(6,'华为运动手环','21g','44*19.7*10.3mm',3);
insert into index_product values(7,'荣耀移动电源10000mh','210g','139*73.7*15.5mm',4);
insert into index_product values(8,'荣耀体脂秤','1850g','300*300*23.7mm',4);

为了更好地演示数据库的读写操作,在 test01 项目中使用 shell 模式(启动命令行和执行脚本)进行讲述,该模式主要为方便开发人员开发和调试程序。在 PyCharm 的 Terminal 下开启 shell 模式,输入 python manage.py shell 指令即可开启,如下图所示:

test01>python manage.py shell
Python 3.6.11 (default, Aug  5 2020, 19:41:03) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

在 shell 模式下,若想对数据表 index_product 插入数据,则可输入以下代码实现:

>>> from index.models import *
>>> p = Product()
>>> p.name = '荣耀V9'
>>> p.weight = '111g'
>>> p.size = '120*75*7mm'
>>> p.type_id = 1
>>> p.save()

通过对模型 Product 进行操作实现数据表 index_product 的数据插入,插入方式如下:

  1. models.py 中导入模型 Product
  2. 对模型 Product 声明并实例化,生成对象 p
  3. 对对象 p 的属性进行逐一赋值,对象 p 的属性来自于模型 Product 所定义的字段。
  4. 完成赋值后需要对 p 进行保存才能作用在目标数据库。

需要注意的是,模型 Product 的外键命名为 type,但在目标数据库中变为 type_id,因此对对象 p 进行赋值的时候,外键的赋值应以目标数据库的字段名为准。
上述代码运行结束后,可以在数据库中查看数据的插入情况,如下所示:

> select * from index_product;
+----+---------------------------+--------+---------------------+---------+
| id | name                      | weight | size                | type_id |
+----+---------------------------+--------+---------------------+---------+
|  1 | 荣耀V10                   | 172g   | 157.00*74.98*6.97mm |       1 |
|  2 | HUAWEI nova 2s            | 169g   | 156.9*75.1*.7.5mm   |       1 |
|  3 | 荣耀Waterplay             | 465g   | 248*173*7.8mm       |       2 |
|  4 | 荣耀畅玩平板              | 460g   | 229.8*159.8*7.95mm  |       2 |
|  5 | PORSCHE DESIGN            | 64g    | 45*48.3*12.6mm      |       3 |
|  6 | 华为运动手环              | 21g    | 44*19.7*10.3mm      |       3 |
|  7 | 荣耀移动电源10000mh       | 210g   | 139*73.7*15.5mm     |       4 |
|  8 | 荣耀体脂秤                | 1850g  | 300*300*23.7mm      |       4 |
|  9 | 荣耀V9                    | 111g   | 120*75*7mm          |       1 |
+----+---------------------------+--------+---------------------+---------+
9 rows in set (0.00 sec)

除了上述方法外,数据的插入还有以下两种方式,代码如下:

# 方法一:通过 Django 的 ORM 框架提供的 API 实现,使用 create 方法实现数据插入
>>> Product.objects.create(name='荣耀V9',weight='111g',size='120*25*7mm',type_id=1)
<Product: Product object (10)>
# 方法二:在实例化时直接设置属性值
>>> p = Product(name='荣耀V9',weight='111g',size='120*25*7mm',type_id=1)
>>> p.save()

如果想对现有的数据进行更新,实现步骤与数据插入的方法大致相同,唯一的区别是在模型实例化之后,要更新数据,需要先进行一次数据查询,将查询结果以对象的形式赋给 p,最后对 p 的属性重新赋值就能实现数据的更新,代码如下:

>>> p = Product.objects.get(id=9)
>>> p.name = '华为荣耀V9'
>>> p.save()

上述代码运行结束后,可以在数据库中查看数据更新的情况,如下所示:

> select * from index_product where id=9;
+----+----------------+--------+------------+---------+
| id | name           | weight | size       | type_id |
+----+----------------+--------+------------+---------+
|  9 | 华为荣耀V9     | 111g   | 120*75*7mm |       1 |
+----+----------------+--------+------------+---------+
1 row in set (0.00 sec)

除此之外,还可以使用 update 方法实现单条或多条数据的更新,使用方法如下:

# 通过 Django 的 ORM 框架提供的 API 实现
# 更新单条数据,查询条件 get 适用于查询单条数据
Product.object.get(id=9).update(name='华为荣耀V9')
# 更新多条数据,查询条件 filter 以列表格式返回,查询结果可能是一条或多条数据
Product.objects.filter(name='荣耀V9').update(name='华为荣耀V9')
# 全表数据更新,不使用查询条件,默认对全表的数据进行更新
roduct.objects.update(name='华为荣耀V9')

如果要对数据进行删除处理,有三种方式:删除表中全部数据、删除一条数据和删除多条数据。实现三种删除方式的代码如下:

# 删除表中全部数据
Product.objects.all().delete()
# 删除一条id为1的数据
Product.objects.get(id=1).delete()
# 删除多条数据
Product.objects.filter(name='华为荣耀V9').delete()

数据删除由 ORM 框架的 delete 方法实现。从数据的删除和更新可以看到这两种数据操作都使用查询条件 getfilter,查询条件 getfilter 的区别如下。

  • 查询条件 get:查询字段必须是主键或者唯一约束的字段,并且查询的数据必须存在,如果查询的字段有重复值或者查询的数据不存在,程序都会抛出异常信息。
  • 查询条件 filter:查询字段没有限制,只要该字段是数据表的某一字段即可。查询结果以列表的形式返回,如果查询结果为空(查询的数据在数据库中找不到),就返回空列表。

数据查询是数据库操作中最为复杂并且内容最多的部分,我们以代码的形式来讲述如何通过 ORM 框架提供的 API 实现数据查询,代码如下:

>>> from index.models import *
# 全表查询,等同于 SQL 语句 Select * from index_product,数据以列表形式返回
>>> p = Product.objects.all()
>>> p[1].name
'HUAWEI nova 2s'

# 查询前 5 条数据,等同于 SQL 语句 Select * from index_product LIMIT 5
>>> p = Product.objects.all()[:5]
>>> p
<QuerySet [<Product: Product object (1)>, <Product: Product object (2)>, <Product: Product object (3)>, <Product: Product object (4)>, <Product: Product object (5)>]>

# 查询某个字段,等同于 SQL 语句 Select name from index_product
# values 方法,以列表形式返回数据,列表元素以字典格式表示
>>> p = Product.objects.values('name')
>>> p[1]['name']
'HUAWEI nova 2s'

# values_list 方法,以列表表示返回数据,列表元素以元组格式表示
>>> p = Product.objects.values_list('name')[:3]
>>> p
<QuerySet [('荣耀V10',), ('HUAWEI nova 2s',), ('荣耀Waterplay',)]>

# 使用 get 方法查询数据,等同 SQL 语句 Select * from index_product where id=2
>>> p = Product.objects.get(id = 2)
>>> p.name
'HUAWEI nova 2s'

# 使用 filter 方法查询数据,注意区分 get 和 filter 的差异
>>> p = Product.objects.filter(id=2)
>>> p[0].name
'HUAWEI nova 2s'

# SQL 的 and 查询主要在 filter 里面添加多个查询条件
>>> p = Product.objects.filter(name = '华为荣耀V9', id = 9)
>>> p[0].name
'华为荣耀V9'

# SQL 的 or 查询,需要引入 Q,编写格式:Q(field=value)|Q(field=value)
# 等同于 SQL 语句 Select * from index_product where name='华为荣耀V9' or id=10
>>> p = Product.objects.filter(Q(name='华为荣耀V9')|Q(id=10))
>>> p
<QuerySet [<Product: Product object (9)>, <Product: Product object (10)>]>

# 使用 count 方法统计查询数据的数据量
>>> p = Product.objects.filter(name='华为荣耀V9').count()
>>> p
1

# 去重查询,distinct 方法无需设置参数,去重方式根据 values 设置的字段执行
# 等同于 SQL 语句 Select DISTINCT name from index_product where name='华为荣耀V9'
>>> p = Product.objects.values('name').filter(name='华为荣耀V9').distinct()
>>> p
<QuerySet [{'name': '华为荣耀V9'}]>

# 根据字段 id 降序排列,降序只要在 order_by 里面的字段前面加 '-' 即可
# order_by 可设置多字段排列,如 Product.objects.order_by('-id', 'name')
>>> p = Product.objects.order_by('-id')
>>> p[1].id
10

# 聚合查询,实现对数据值求和、求平均值等。Django 提供 annotate 和 aggregate 方法实现
# annotate 类似于 SQL 里面的 GROUP BY 方法,如果不设置 values,就会默认对主键进行 GROUP BY 分组
# 等同于 SQL 语句 Select name,SUM(id) AS 'id_sum' from index_product GROUP BY name ORDER BY NULL
>>> p = Product.objects.values('name').annotate(Sum('id'))
>>> print(p.query)
SELECT `index_product`.`name`, SUM(`index_product`.`id`) AS `id__sum` FROM `index_product` GROUP BY `index_product`.`name` ORDER BY NULL

# aggregate 是将某个字段的值进行计算并只返回计算结果
# 等同于 SQL 语句 Select COUNT(id) AS 'id_count' from index_product
>>> p = Product.objects.aggregate(id_count=Count('id'))
>>> p
{'id_count': 11}

上述代码讲述了日常开发中常用的数据查询方法,但有时候需要设置不同的查询条件来满足多方面的查询要求。上述例子中,查询条件 filterget 使用等值的方法来
匹配结果。若想使用大于、不等于和模糊查询的匹配方法,则可以使用下表所示的匹配符实现。

匹配符使用说明
__exactfilter(name__exact='荣耀')精确等于,如 SQL 的 like '荣耀'
__iexactfilter(name__iexact='荣耀')精确等于并忽略大小写
__containsfilter(name__contains='荣耀')模糊匹配,如 SQL 的 like '%荣耀%'
__icontainsfilter(name__icontains='荣耀')模糊匹配并忽略大小写
__gtfilter(id__gt=5)大于
__gtefilter(id__gte=5)大于等于
__ltfilter(id__lt=5)小于
__ltefilter(id__lte=5)小于等于
__infilter(id__in=[1,2,3])判断是否在列表内
__startswithfilter(name__startswith='荣耀')以 ... 开头
__istartswithfilter(name__istartswith='荣耀')以 ... 开头并忽略大小写
__endswithfilter(name__endswith='荣耀')以 ... 结尾
__iendswithfilter(name__iendswith='荣耀')以 ... 结尾并忽略大小写
__rangefilter(name__range=='荣耀')在 ... 范围内
__yearfilter(date__year=2020)日期字段的年份
__monthfilter(date__month=12)日期字段的月份
__dayfilter(date__day=30)日期字段的天数
__isnullfilter(name__isnull=True/False)判断是否为空

从上表中可以看到,只要在查询的字段后添加相应的匹配符,就能实现多种不同的数据查询,如 filter(id_ gt=9) 用于获取字段id大于9的数据。在 shell 模式下使
用该匹配符进行数据查询,代码如下:

>>> from index.models import *
>>> p = Product.objects.filter(id__gt=9)
>>> p
<QuerySet [<Product: Product object (10)>, <Product: Product object (11)>]>

多表查询

一对多或一对一的表关系是通过外键实现关联的,而多表查询分为正向查询和反向查询。以模型 ProductType 为例:

  • 如果查询对象的主体是模型 Type,要查询模型 Type 的数据,那么该查询称为正向查询。
  • 如果查询对象的主体是模型 Type,要通过模型 Type 查询模型 Product 的数据,那么该查询称为反向查询。

无论是正向查询还是反向查询,两者的实现方法大致相同,代码如下:

# 正向查询
>>> t = Type.objects.filter(product__id=11)
>>> t[0].type_name
'手机'
  
# 反向查询
>>> t[0].product_set.values('name')
<QuerySet [{'name': '荣耀V10'}, {'name': 'HUAWEI nova 2s'}, {'name': '华为荣耀V9'}, {'name': '荣耀V9'}, {'name': '荣耀V9'}]>

从上面的代码分析,因为正向查询的查询对象主体和查询的数据都来自于模型 Type,因此正向查询在数据库中只执行了一次 SQL 查询。而反向查询通过 t[0].product_set.values('name') 来获取模型 Product的 数据,因此反向查询执行了两次 SQL 查询,首先查询模型 Type 的数据,然后根据第一次查询的结果再查询与模型 Product 相互关联的数据。

为了减少反向查询的查询次数,我们可以使用 select_related 方法实现,该方法只执行一次 SQL 查询就能达到反向查询的效果。select_related 使用方法如下:

# 查询模型 Product 的字段 name 和模型 Type 的字段 type_name
# 等同于 SQL: SELECT name,type_name FROM index_product INNER JOIN index_type on type_id=id
>>> p = Product.objects.select_related('type').values('name','type__type_name')
# 输出 SQL 查询语句
>>> print(p.query)
SELECT `index_product`.`name`, `index_type`.`type_name` FROM `index_product` INNER JOIN `index_type` ON (`index_product`.`type_id` = `index_type`.`id`)

# 查询两个模型的全部数据
# SELECT * FROM index_product INNER JOIN index_type ON type_id = id
>>> p = Product.objects.select_related('type').all()
>>> print(p.query)
SELECT `index_product`.`id`, `index_product`.`name`, `index_product`.`weight`, `index_product`.`size`, `index_product`.`type_id`, `index_type`.`id`, `index_type`.`type_name` FROM `index_product` INNER JOIN `index_type` O
N (`index_product`.`type_id` = `index_type`.`id`)

# 获取两个模型的数据,以模型 Product 的 id 大于 8 为查询条件
# SELECT * FROM index_product INNER JOIN index_type ON type_id=id WHERE index_product>8
>>> p = Product.objects.select_related('type').filter(id__gt=8)
>>> print(p.query)
SELECT `index_product`.`id`, `index_product`.`name`, `index_product`.`weight`, `index_product`.`size`, `index_product`.`type_id`, `index_type`.`id`, `index_type`.`type_name` FROM `index_product` INNER JOIN `index_type` O
N (`index_product`.`type_id` = `index_type`.`id`) WHERE `index_product`.`id` > 8

# 获取两个模型的数据,以模型 Type 的 type_name 字段等于 '手机' 作为查询条件
# SELECT * FROM index_product INNER JOIN index_type ON type_id=id WHERE index_type.type_name='手机'
>>> p = Product.objects.select_related('type').filter(type__type_name='手机').all()
>>> print(p.query)
SELECT `index_product`.`id`, `index_product`.`name`, `index_product`.`weight`, `index_product`.`size`, `index_product`.`type_id`, `index_type`.`id`, `index_type`.`type_name` FROM `index_product` INNER JOIN `index_type` O
N (`index_product`.`type_id` = `index_type`.`id`) WHERE `index_type`.`type_name` = 手机
# 输出一个 Product 的名字
>>> p[0].name
'荣耀V10'
# 输出一个 Product 所属的类型名称
>>> p[0].type.type_name
'手机'

select_related 的使用说明如下:

  • 以模型 Product 作为查询对象主体,当然也可以使用模型 Type,只要两表之间有外键关联即可。
  • 设置 select_related 的参数值为 "type",该参数值是模型 Product 定义的 type 字段。
  • 如果在查询过程中需要使用另一个数据表的字段,可以使用“外键_字段名”来指向该表的字段。如 type__type_name 代表由模型 Product 的外键 type 指向模型 Type 的字段 type_nametype 代表模型 Product 的外键 type,双下画线 _ 代表连接符,type_name 是模型 Type 的字段。

除此之外,select_related 还可以支持三个或三个以上的数据表同时查询,以如下 models.py 定义作为例子进行说明。

from django.db import models

# 省份信息表
class Province(models.Model):
    name = models.CharField(max_langth=10)

# 城市信息表
class City(models.Model):
    name = models.CharField(max_length=5)
    province = models.ForeignKey(Province, on_delete=models.CASCADE)

# 人物信息表
class Person(models.Model):
    name = models.CharField(max_length=10)
    living = models.ForeignKey(City, on_delete=models.CASCADE)

在上述模型中,模型 Person 通过外键 living 关联模型 City,模型 City 通过外键 province 关联模型 Province,从而使三个模型形成一种递进关系。

例如查询张三现在所居住的省份,首先通过模型 Person 和模型 City 查出张三所居住的城市,然后通过模型 City 和模型 Province 查询当前城市所属的省份。因此,select_related 的实现方法如下:

p = Person.objects.select_related('living_province').get(name='张三')
p.living.province

在上述例子可以发现,通过设置 select_ related 的参数值即可实现三个或三个以上的多表查询。例子中的参数值为 living__province,参数值说明如下:

  • living 是模型 Person 的字段,该字段指向模型 City
  • province 是模型 City 的字段,该字段指向模型 Province
  • 两个字段之间使用双下画线连接并且两个字段都是指向另一个模型的,这说明在查询过程中,模型 Person 的字段 living 指向模型 City,再从模型 City
    字段 province 指向模型 Province,从而实现三个或三个以上的多表查询。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/django6.html

Buy me a cup of coffee ☕.