Django - 模型与数据库

模型定义与数据迁移

模型定义

ORM 框架是一种程序技术,用于实现面向对面变成语言中不同类型系统的数据之间的转换。

# index\model.py
from django.db import models

# Create your models here.
class PersonInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    hireDate = models.DateField()
    
    def __str__(self):
        return self.name
    
    class Meta:
        verbose_name = '人员信息'

在定义模型时,一般情况下都会重写函数__str__,这是设置模型的返回值,默认情况下,返回值为模型名+主键,只允许返回字符串类型

模型除了定义模型字段和重写函数__str__之外,还有Meta选项,这三者是定义模型的基本要素、Meta 选项里设有19个属性,每个属性的说明如下:

  • abstract:若为True, 则该模型为抽象模型,不会在数据库里创建数据表
  • app_label: 属性值为字符串,将模型设定为指定的项目应用,比如将index 的 moudles.py 定义的模型A 指定到其他APP里
  • db_table: 属性值为字符串,设置模型所对应的数据表的名称
  • db_tablespace: 属性值为字符串,设置模型所使用数据库的表空间
  • get_latest_by: 属性值为字符串或列表,设置模型数据的排序方式。

数据迁移

数据迁移是将项目里定义的模型生成相应的表数据,包括数据表的创建和更新。

创建数据表:

# index\models.py 

class Vocation(models.Model):
    id = models.AutoField(primary_key=True)
    job = models.CharField(max_length=20)
    title = models.CharField(max_length=20)
    name = models.ForeignKey(PersonInfo,on_delete=models.Case)

    def __str__(self):
        return str(self.id)

    class Meata:
        verbose_name = '职业信息'
  1. 执行 python manage.py makemigrations 后会生成以下文件:
import django.db.models.expressions
from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('index', '0002_alter_personinfo_options_personinfo_hiredate'),
    ]

    operations = [
        migrations.CreateModel(
            name='Vocation',
            fields=[
                ('id', models.AutoField(primary_key=True, serialize=False)),
                ('job', models.CharField(max_length=20)),
                ('title', models.CharField(max_length=20)),
                ('name', models.ForeignKey(on_delete=django.db.models.expressions.Case, to='index.personinfo')),
            ],
        ),
    ]

该脚本的代码会被migrate 命令执行,migrate 指令会根据脚本代码的内容在数据库里创建相应的数据表

  1. 执行 python manage.py migrate 即可生成数据库表

更新表结构操作:

makemigrations 和 migrate 指令还支持模型的修改,从而修改相应的数据表结构,比如在模型Vocation 里新增字段 payment,代码如下:

class Vocation(models.Model):
    id = models.AutoField(primary_key=True)
    job = models.CharField(max_length=20)
    title = models.CharField(max_length=20)
    payment = models.IntegerField(null=True,blank=True) # 新增字段
    name = models.ForeignKey(PersonInfo,on_delete =models.Case)

    def __str__(self):
        return str(self.id)

    class Meata:
        verbose_name = '职业信息'

新增字段必须将属性 null 和 blank 设置为 True 或者为模型字段设置默认值,否则执行 makemigration 指令会提示信息:

(venv) PS E:\PyProject\MyDjango> python manage.py makemigrations
It is impossible to add a non-nullable field 'payment' to vocation without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit and manually define a default value in models.py.

每次执行migrate 执行时,Django 都能精准运行 migrations 文件夹尚未办被执行的.py 文件,它不会对同一个.py 文件重复执行,因为每次执行时,Django 会将 该文件的执行记录保存在数据表django_migrations 中,数据表的数据信息如下:

如果需要重复执行 migrations 文件夹的某个.py文件,就只需要在数据表里删除相应文件执行记录。一般情况下不建议采用这种操作,因为很容易出现异常。比如表在已存在的情况下,再次执行相应的.py文件,会提示"table 'xxx' alread exites" 异常

migrate 指令还可以单独执行某个文件例如:python manage.py migrate index 0001_initial

此外Django 提供了sqlmigate指令, 可以根据 migrate下的 .py 文件生成相应的sql 语句:

(venv) PS E:\PyProject\MyDjango> python manage.py sqlmigrate index 0003_vocation 
--
-- Create model Vocation
--
CREATE TABLE `index_vocation` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `job` varchar(20) NOT NULL, `title` varchar(20) NOT NULL, `name_id` integer NOT NULL);
ALTER TABLE `index_vocation` ADD CONSTRAINT `index_vocation_name_id_a21d19c0_fk_index_personinfo_id` FOREIGN KEY (`name_id`) REFERENCES `index_personinfo` (`id`);

数据表操作

数据新增

Django 对数据库的数据进行增、删、改操作是借助内置ORM 框架所提供的API 方法实现的,简单来说,ORM 框架的数据操作API 是QuerySet 类里面定义的,然后由开发者自定义的模型对象调用QuerySet类,从而数显数据操作。

本节案例数据表如下,index_personinfo、index_vocation

可以进入Shell 模式,方便开发人员开发和程序调试:

(venv) PS E:\PyProject\MyDjango> python manage.py shell
Python 3.10.1 (tags/v3.10.1:2cd268a, Dec  6 2021, 19:10:37) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from index.models import *
>>> v = Vocation()
>>> v.job = '测试工程师'
>>> v.title = '系统测试'
>>> v.payment = 11000
>>> v.name_id = 3
>>> v.save
<bound method Model.save of <Vocation: None>>
>>> v.id
>>> v.save() 
# 获取新增后的主键值 
>>> v.id 
6

上述代码是对模型进行实例化,再对实例化队形进行赋值,从而实现数据表的数据插入操作。除了上述方法外,数据插入还有以下三种常用方法

# 方式二
>>> v = Vocation.objects.create(job='java开发',title='web开发', payment=11000,name_id=7)
>>> v.id
7
# 方式三   
>>> v_dict = {'job':'c++','title':'嵌入式开发','payment':12000,'name_id':8}
>>> v = Vocation.objects.create(**v_dict) 
>>> v.id
8
# 方式四
>>> v = Vocation(job='c',title='系统开发',payment=12000,name_id=9)
>>> v.save()

数据修改

数据修改的步骤与数据插入的步骤大致相同,唯一的区别在于数据对象来自数据表,因此需要和执行一次数据查询,查询结果以对象的形式表示,并将对象的属性进行赋值处理

如下,将index_vocation 表id为1的数据的工资修改为11000

>>> v = Vocation.objects.get(id=1) 
>>> v   
<Vocation: 1>
>>> v.payment = 11000
>>> v.save()

初次之外,还可以使用update方法完成数据的修改:

# 方式二:批量更新一条或多条数据,查询方法使用filter
>>> Vocation.objects.filter(id=2).update(payment=13000)            
1   
# 方式三:更新数据以数据字典格式表示
>>> d = {'payment': 12000} 
>>> Vocation.objects.filter(id=3).update(**d)   
1
# 若不进行查询,将对全表数据进行更新
>>> Vocation.objects.update(payment=2000)
# 使用内置方法实现数据的自增和自减
>>> from django.db.models import F
>>> Vocation.objects.filter(job='测试工程师').update(payment=F('payment') + 1) 
1
>>>

在Django 2.2 或以上版本新增了数据批量更新方法 bulk_ update,它的使用方法与 批量新增方法bulk_create 相似,使用说明如下:

# 新增两行数据
>>> v1 = Vocation.objects.create(job='财务',title='出纳',name_id=1) 
>>> v2 = Vocation.objects.create(job='财务',title='会计',name_id=1)  
# 修改字段payment 和 title 的数据
>>> v1.payment = 1000
>>> v2.title= '行政' 
# 批量修改
>>> Vocation.objects.bulk_update([v1,v2],fields=['payment','title']) 
2   

数据查询

# 全表查询
>>> v_all = Voncation.objects.all()
# 获取第一条数据的字段
>>> v_all[0].id,v_all[0].job 
(1, '软件工程师')
>>> v_all
<QuerySet [<Vocation: 1>, <Vocation: 2>, <Vocation: 3>, <Vocation: 4>, <Vocation: 5>, <Vocation: 6>, <Vocation: 7>, <Vocation: 8>, <Vocation: 9>, <Vocation: 12>, <Vocation: 13>]>

# 获取所有的job 信息
>>> for item in v_all:
...     print(item.job)
... 
软件工程师
文员
网站设计
需求分析师
项目经理
测试工程师
java开发
c++
c
财务
财务

# 查询前3条数据
>>> v = Vocation.objects.all()[:3]      
>>> v
<QuerySet [<Vocation: 1>, <Vocation: 2>, <Vocation: 3>]>
>>>

# 查询某个字段自 => select job from index_vocation
# values 方法数据以列表返回,列表元素以字典表示,参数可以接收多个字段
>>> v = Vocation.objects.values('job') 
>>> v
<QuerySet [{'job': '软件工程师'}, {'job': '文员'}, {'job': '网站设计'}, {'job': '需求分析师'}, {'job': '项目经理'}, {'job': '测试工程师'}, {'job': 'java开发'}, {'job': 'c++'}, {'jo
b': 'c'}, {'job': '财务'}, {'job': '财务'}]>

# valus_list:数据以列表返回,列表元素以元组表示,参数可以接收多个字段
>>> v = Vocation.objects.values_list('job') 
>>> v
<QuerySet [('软件工程师',), ('文员',), ('网站设计',), ('需求分析师',), ('项目经理',), ('测试工程师',), ('java开发',), ('c++',), ('c',), ('财务',), ('财务',)]>

# 同时查询多个字段
>>> v = Vocation.objects.values('job','title')[:3] 
>>> v
<QuerySet [{'job': '软件工程师', 'title': 'Python开发'}, {'job': '文员', 'title': '前台文员'}, {'job': '网站设计', 'title': '前端开发'}]>

# 使用get 方法查询数据,返回值是Vocation的实例 => select * from index_vocation where id = 1
>>> v = Vocation.objects.get(id=1)  
>>> v.id,v.job
(1, '软件工程师')
>>> v = Vocation.objects.get(id=2,job='文员') 
>>> v.id,v.job
(2, '文员')

# 使用filter 进行查询,返回值是一个QuerySet,同时多个参数就相当于SQL的 and 查询
>>> v = Vocation.objects.filter(id=2,job='文员') 
>>> v
<QuerySet [<Vocation: 2>]>
>>> v[0].job
'文员'


# SQL的 or 查询需要引入函数Q,编写格式 Q(field=value) | Q(field=value),多个Q 之间使用 "|" 隔开即可
>>> queryset = Vocation.objects.filter(Q(job='文员') | Q(payment=9000)) 
>>> queryset
<QuerySet [<Vocation: 2>, <Vocation: 4>]>

# 不等于查询,在Q 前面加上 ~ 就可以
>>> queryset = Vocation.objects.filter(~Q(job='文员'))
>>> queryset
<QuerySet [<Vocation: 1>, <Vocation: 3>, <Vocation: 4>, <Vocation: 5>, <Vocation: 6>, <Vocation: 7>, <Vocation: 8>, <Vocation: 9>, <Vocation: 12>, <Vocation: 13>]>

# 还可以使用exclude 实现 不等于
>>> queryset = Vocation.objects.exclude(job='文员')   
>>> queryset                                        
<QuerySet [<Vocation: 1>, <Vocation: 3>, <Vocation: 4>, <Vocation: 5>, <Vocation: 6>, <Vocation: 7>, <Vocation: 8>, <Vocation: 9>, <Vocation: 12>, <Vocation: 13>]>

# 使用count 方法统计查询数据的数据量
>>> v = Vocation.objects.filter(job='财务').count()  
>>> v
2
>>>

# 去重复查询
>>> v = Vocation.objects.values_list('job').distinct()
>>> v
<QuerySet [('软件工程师',), ('文员',), ('网站设计',), ('需求分析师',), ('项目经理',), ('测试工程师',), ('java开发',), ('c++',), ('c',), ('财务',)]>

# 根据薪水降序,id 正序 排序。降序排序字段前加 - 即可
>>> v = Vocation.objects.order_by('-payment', 'id') 
>>> v
<QuerySet [<Vocation: 2>, <Vocation: 3>, <Vocation: 5>, <Vocation: 8>, <Vocation: 9>, <Vocation: 6>, <Vocation: 1>, <Vocation: 7>, <Vocation: 4>, <Vocation: 12>, <Vocation: 13>]>
>>>

#  分组 使用 annotate函数,一般结合values和values_list使用,指定需要分组的字段,如果不指定默认对id分组
# annotate 接收一个位置参数,一般都是聚合函数:Sum、Count等
>>> from django.db.models import Sum,Count

# values,等效SQL: select job, count(job) from index_vocation group by job
>>> v = Vocation.objects.values('job').annotate(count=Count('job'))
>>> v
<QuerySet [{'job': '软件工程师', 'count': 1}, {'job': '文员', 'count': 1}, {'job': '网站设计', 'count': 1}, {'job': '需求分析师', 'count': 1}, {'job': '项目经理', 'count': 1}, {'jo
b': '测试工程师', 'count': 1}, {'job': 'java开发', 'count': 1}, {'job': 'c++', 'count': 1}, {'job': 'c', 'count': 1}, {'job': '财务', 'count': 2}]>

# valus_list:等效SQL: select job, count(job) from index_vocation group by job
>>> v = Vocation.objects.values_list('job').annotate(count=Count('job'))
>>> v
<QuerySet [('软件工程师', 1), ('文员', 1), ('网站设计', 1), ('需求分析师', 1), ('项目经理', 1), ('测试工程师', 1), ('java开发', 1), ('c++', 1), ('c', 1), ('财务', 2)]>

# SQL: select job, SUM(payment) from index_vocation group by job
>>> v = Vocation.objects.values_list('job').annotate(Sum('payment')) 
>>> v
<QuerySet [('软件工程师', 11000), ('文员', 13000), ('网站设计', 12000), ('需求分析师', 9000), ('项目经理', 12000), ('测试工程师', 11001), ('java开发', 11000), ('c++', 12000), ('c',
 12000), ('财务', 21000)]>
>>>


# aggregate 是计算某个字段的值并只返回计算结果
>>> v = Vocation.objects.aggregate(Sum('payment')) 
>>> v
{'payment__sum': 124001}
>>>

# SQL union 并集操作
>>> v1 = Vocation.objects.filter(payment__gt=12000) 
>>> v1
<QuerySet [<Vocation: 2>, <Vocation: 13>]>
>>> v2 = Vocation.objects.filter(payment__gt=13000) 
>>> v2
<QuerySet [<Vocation: 13>]>
>>> v1.union(v2)                                    
<QuerySet [<Vocation: 2>, <Vocation: 13>]>
>>>

#  交集:intersection
#  差集:difference

若想使用大于、不等于、模糊查询的匹配方法,则可在查询条件filter和get 里使用下面的匹配符来实现:

  • __exact: filter(job__exact='开发'), 精确匹配,如SQL 的 like '开发'
  • __exact: filter(job_iexact='开发'),精确等于并忽略大小写,
  • __contains: filter(job__contains='开发'),模糊匹配,如SQL的 like '%荣耀%'
  • __icontaions: filter(job__icontains='开发'),模糊匹配,忽略大小写
  • __gt: filter(payment__gt=11000), 大于
  • __gte: filter(payment__gte=11000),大于等于
  • __lt: filter(payment__lt=11000),小于
  • __lte: filter(payment__lte=11000), 小于等于
  • __in: filter(payment__in=[11000, 12000]),判断是否在列表内
  • __startswith: filter(payment__startswith='开发'), 以...开头
  • __istartswith: filter(payment__istartswith='开发'), 以...开头,并忽略大小写
  • __endswith: filter(payment__endswith='开发'): 以...结尾
  • __iendswith: filter(payment__iendswith='开发'): 以...结尾
  • __isnull: filter(job__isnull=True/False): 判断是否为空

综上所述,在查询数据时可以使用查询条件get或filter实现,但是两者的执行过程存在一定的差异,说明如下:

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

多表查询

一对一

class Actor(models.Model):
    # 演员表
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    
class Actor_info(models.Model):
    id = models.IntegerField(primary_key=True)
    models.OneToOneField(Actor, on_delete=models.CASCADE)   # 建议一对一映射
    birth = models.CharField(max_length=20)

一对多

正向查询:

>>> v = Vocation.objects.filter(id=1).first()
>>> v.name.hireDate                           
datetime.date(2018, 9, 18)
>>>

反向查询方式一: 使用vocation_set,返回值为queryset对象

>>> p = PersonInfo.objects.filter(id=2).first() 
>>> p
<PersonInfo: 刘备>
>>> p.vocation_set.first()
<Vocation: 1>
>>> v.job,v.payment
('软件工程师', 11000)
>>>

反向查询方式二:由模型Vocation 的外键字段 name 的参数 related_name 实现,外键字段必须设置参数related_name 才有效,否则无法查询,若设置related_name ,则无法使用vocation_set

代码如下:

>>> p = PersonInfo.objects.filter(id=2).first()
>>> p.personinfo.first()
<Vocation: 1>
>>>
>>> p.personinfo                                
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x00000223C6D73B20>
>>>
# vocation_set 无法使用
>>> p.vocation_set.first() 
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'PersonInfo' object has no attribute 'vocation_set'

正向查询和反向查询 还能在查询条件(get or filter)里使用,这种方式用于查询条件的字段不在查询对象里,比如查询对象为Vocation,查询条件是PersonInfo 的某个字段,对于这种查询可以采用以下方法实现:

# 正向查询:使用Vocation,查询张飞的雇佣日期
# name__name ,前面的name 是 Vocation 的字段name
# 后面的name 是 PerosonInfo 的字段name,两者使用双下划线连接
>>> v = Vocation.objects.filter(name__name='张飞').first()
>>> v.name.hireDate
datetime.date(2018, 9, 18)
>>>

# 反向查询:使用PersonInfo,查询job为c的工资

>>> p = PersonInfo.objects.filter(personinfo__job='c').first()
>>> p
<PersonInfo: 曹植>
>>> v = p.personinfo.first() 
>>> v.payment
12000
>>>

无论是正向查询还是反向查询,它们在数据库都需要执行两次SQL,第一次是查询某张表的数据,再通过外键关联获取另外一张表的数据,为了减少查询次数,提高查询效率,我们可以使用select_related 或 prefetch_related 方法实现,该方法只需要一次SQL查询就能实现多表查询。

select_related 主要是针对一对一和一对多关系进行优化,它是使用SQL 的 JOIN 进行优化0的,使用方法如下:

# 以PeronInfo 为查询对象:查询PersonInfo的name字段和模型Vocation的payment字段
# select_related 方法参数为字符串
# select_related  使用 左外连接 LEFT OUT JOIN 方式查询两个数据表
# 若要得到其他表的关联数据,则可使用"__"连接字段名
# 双下滑线'__' 连接字段名必须是外键字段名或外键字段参数related_name 的值

>>> p = PersonInfo.objects.select_related('personinfo').values('name','personinfo__payment') 
>>> p
<QuerySet [{'name': '张飞', 'personinfo__payment': 13000}, {'name': '张飞', 'personinfo__payment': 1000}, {'name': '张飞', 'personinfo__payment': 20000}, {'name': '刘备', 'personin
fo__payment': 11000}, {'name': '关羽', 'personinfo__payment': 9000}, {'name': '赵云', 'personinfo__payment': 12000}, {'name': '黄忠', 'personinfo__payment': 12000}, {'name': '曹操'
, 'personinfo__payment': 11001}, {'name': '郭嘉', 'personinfo__payment': 11000}, {'name': '孟获', 'personinfo__payment': 12000}, {'name': '曹植', 'personinfo__payment': 12000}]>   
>>> p = PersonInfo.objects.select_related('personinfo').values_list('name','personinfo__payment') 
>>> p
<QuerySet [('张飞', 13000), ('张飞', 1000), ('张飞', 20000), ('刘备', 11000), ('关羽', 9000), ('赵云', 12000), ('黄忠', 12000), ('曹操', 11001), ('郭嘉', 11000), ('孟获', 12000), (
'曹植', 12000)]>
# 打印查询SQL
>>> print(p.query) 
SELECT `index_personinfo`.`name`, `index_vocation`.`payment` FROM `index_personinfo` LEFT OUTER JOIN `index_vocation` ON (`index_personinfo`.`id` = `index_vocation`.`name_id`)
>>>

# 以Vocation 为查询对象:查询Vocation 的job 字段 和  PersonInfo的age字段
# select_related 使用INNER JOIN 方式查询两表数据
# select_realted 的参数为name ,代表外键字段name
>>> v = Vocation.objects.select_related('name').values('job', 'name__age')          
>>> v
<QuerySet [{'job': '文员', 'name__age': 35}, {'job': '财务', 'name__age': 35}, {'job': '财务', 'name__age': 35}, {'job': '软件工程师', 'name__age': 40}, {'job': '需求分析师', 'name
__age': 36}, {'job': '网站设计', 'name__age': 33}, {'job': '项目经理', 'name__age': 55}, {'job': '测试工程师', 'name__age': 56}, {'job': 'java开发', 'name__age': 57}, {'job': 'c++'
, 'name__age': 33}, {'job': 'c', 'name__age': 32}]>
>>> print(v.query) 
SELECT `index_vocation`.`job`, `index_personinfo`.`age` FROM `index_vocation` INNER JOIN `index_personinfo` ON (`index_vocation`.`name_id` = `index_personinfo`.`id`)


# 结合filter使用:查询薪水大于10000的人
>>> v = Vocation.objects.select_related('name').filter(payment__gt=10000).values('name__name','payment') 
>>> v
<QuerySet [{'name__name': '张飞', 'payment': 13000}, {'name__name': '张飞', 'payment': 20000}, {'name__name': '刘备', 'payment': 11000}, {'name__name': '赵云', 'payment': 12000}, {
'name__name': '黄忠', 'payment': 12000}, {'name__name': '曹操', 'payment': 11001}, {'name__name': '郭嘉', 'payment': 11000}, {'name__name': '孟获', 'payment': 12000}, {'name__name'
: '曹植', 'payment': 12000}]>
>>> print(v.query) 
SELECT `index_personinfo`.`name`, `index_vocation`.`payment` FROM `index_vocation` INNER JOIN `index_personinfo` ON (`index_vocation`.`name_id` = `index_personinfo`.`id`) WHERE `in
dex_vocation`.`payment` > 10000
>>>

那么什么时候使用模型中的字段名 或者 ForeignKey 的参数 related_name??

# 使用没有外键的表PeronInfo是否可以使用 外键字段name 关联? - 发现报错
>>> p = PersonInfo.objects.select_related('name').values_list('name','name__payment')       
Traceback (most recent call last):
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 1890, in transform
    return self.try_transform(wrapped, name)
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 1427, in try_transform
    raise FieldError(
django.core.exceptions.FieldError: Unsupported lookup 'payment' for CharField or join on the field not permitted.


# 使用Vocation 为查询对象是否可以使用 ForeignKey的参数 related_name? -- 报错
>>> v = Vocation.objects.select_related('personinfo').values('job', 'personinfo__age') 
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\query.py", line 1360, in values
    clone = self._values(*fields, **expressions)
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\query.py", line 1355, in _values
    clone.query.set_values(fields)
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 2502, in set_values
    self.add_fields(field_names, True)
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 2199, in add_fields
    join_info = self.setup_joins(
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 1867, in setup_joins
    path, final_field, targets, rest = self.names_to_path(
  File "E:\PyProject\MyDjango\venv\lib\site-packages\django\db\models\sql\query.py", line 1772, in names_to_path
    raise FieldError(
django.core.exceptions.FieldError: Cannot resolve keyword 'personinfo' into field. Choices are: id, job, name, name_id, payment, title

总结:

  • 使用定义外键字段的模型为查询对象时,只可以使用外键字段名,使用内连接
  • 使用不存在外键字段的模型为查询对象时,只能使用 ForeignKey 的参数 related_name,使用左外连接

select_related 还支持3张及3张表以上的查询,以下面的例子进行说明:

# index\models.py 新增以下模型

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

    def __str__(self):
        return str(self.name)


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

    def __str__(self):
        return f'{self.name}'

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

    def __str__(self):
        return f'{self.name}'


插入数据:

模型Person 通过外键 living 关联 模型City ,模型City 通过外键province 关联 模型 Province:

>>> p = Person.objects.select_related('living__province').get(name='Tom')
>>> p.living
<City: 苏州>
>>> p.living.province
<Province: 浙江省>

两个外键字段之间使用双下划线连接,在查询过程中,模型Person 的外键字段living 指向模型City, 再从模型City 的外键字段province 指向模型Province ,从而实现3个或3个以上的多表查询。

多对多

prefetch_related 和 select_related 的设计目的很相似,都是为了减少SQL 查询的次数,但是实现的方式不一样,对于多对多关系,使用select_related 会增加数据查询时间和内存占用,而prefetch_related 是分别查询每张数据表,然后由Python 语法来处理它们之间的关系,因此对于多对多关系的查询,prefetch_related 更有优势

# index\models.py

class Performer(models.Model):
    # 节目信息表
    id  = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    nationality = models.CharField(max_length=20)

    def __str__(self):
        return f'{self.name}'


class Program(models.Model):
    # 人员信息表
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=20)
    performer = models.ManyToManyField(Performer)

    def __str__(self):
        return f'{self.name}'

例如,查找 "喜羊羊" 节目有多少个人员参与演出,首先从节目表index_program 里找出"喜羊羊" 的数据信息, 然后通过外键字段 performer 获取参与演出的人员信息,实现过程如下:

>>> p = Program.objects.prefetch_related('performer').filter(name='喜羊羊').first()
>>> p.performer.all() # 通过外键找数据
<QuerySet [<Performer: Lily>, <Performer: Lilei>, <Performer: Tom>]>

objects是什么?
https://blog.csdn.net/qq_42655663/article/details/95919593
https://blog.csdn.net/cn_1937/article/details/94123991
https://www.django.cn/forum/forum-61764.html

posted @ 2024-05-04 21:06  chuangzhou  阅读(7)  评论(0编辑  收藏  举报