26.8 Django多表操作

2024-07-25_013923

1. 表关系

表关系在数据库中指的是表与表之间的连接和依赖关系.
这种关系定义了数据如何在不同的表之间进行交互和关联, 是实现数据一致性和完整性的重要手段.

1.1 关系分类

多表关系在数据库中通常分为以下几种类型:
* 1. 一对一(One-to-One)关系: 在数据库中, 这种关系通常通过外键约束和唯一约束来实现.
     在ORM中, 这可以通过在一个模型类中嵌入另一个模型类的实例或者通过引用(如外键)来实现.
* 2. 一对多/多对一(One-to-Many / Many-to-One)关系:
     这是一个常见的数据库关系, 其中一个表中的行可以关联到另一个表中的多行.
     在ORM中, 这通常通过在'一'的模型类中定义一个指向'多'的模型类的集合属性来实现.
     例如, 一个用户可以有多个订单, 所以在用户模型中会有一个订单集合.
* 3. 多对多(Many-to-Many)关系: 这种关系更复杂, 因为它允许两个表中的多行相互关联.
     在数据库中, 这通常通过一个额外的关联表来实现, 该表包含两个外键, 分别指向两个原始表的主键.
     在ORM中, 这通常通过定义一个关联表模型(尽管在某些ORM框架中可能是隐式的)
     并在两个模型类中各添加一个指向对方模型的集合属性来实现.
在ORM中, 多表关系允许以面向对象的方式处理数据库中的复杂数据关联.

1.2 外键字段

在Django的ORM中, OneToOneField, ForeignKey, 和ManyToManyField字段分别用于实现数据库中的一对一, 一对多, 和多对多关系.
这些字段在定义时, Django会为它们设置一些默认行为, 以确保数据的一致性和完整性.

以下是这三个字段的一些默认行为:
* OneToOneField(一对一字段).
  - 默认行为: OneToOneField本质上是一个特殊的ForeignKey(外键), 
    它要求每个关联对象只能对应一个主对象(反之亦然), 即它们之间是一对一的关系.
    
  - 数据库层面: 在数据库中, OneToOneField通常在关联表上创建一个外键约束, 并且这个外键字段被设置为唯一(UNIQUE),
    以确保每个关联对象(, 在关联表中的每一行)只能关联到一个主对象(, 被OneToOneField引用的那个模型的对象(, 记录)).

  - Django ORM: 在Django的ORM中, 可以像访问模型的普通字段一样访问通过OneToOneField关联的对象.
    Django会自动处理这个关联, 并在需要时执行JOIN操作.
    OneToOneField继承自ForeignKey, 但具有一些额外的限制(在数据库中创建一个唯一的外键约束)来确保关系的唯一性.
    
* ForeignKey(外键).
  - 默认行为: ForeignKey用于表示一对多的关系, 即一个对象可以关联到多个其他对象.
  
  - 数据库层面: 在数据库中, ForeignKey创建一个外键约束, 这个约束确保了外键列的值必须是它所引用的表中的某个主键值,
    或者为NULL(如果外键字段被设置为null=True).
    
  - Django ORM: 在Django的ORM中, 可以通过关联管理器(related manager)来访问与当前对象关联的其他对象.
    例如, 如果有一个Author模型和一个Book模型, 其中Book通过ForeignKey关联到Author, 
    那么可以通过author.book_set.all()来获取该作者的所有书籍.
    
* ManyToManyField(多对多字段).
  - 默认行为: ManyToManyField用于表示多对多的关系, 即两个对象集合之间可以相互关联.
  
  - 数据库层面: Django会自动为ManyToManyField创建一个关联表(也称为中间表或连接表),
    这个表包含两个外键字段, 分别指向两个关联模型的主键.
    Django还会为这个表创建一个唯一约束, 以确保不会有重复的关联记录(除非明确指定了through参数来使用一个自定义的中间模型).
    
  - Django ORM: 在Django的ORM中, 可以像访问普通字段的集合一样访问通过ManyToManyField关联的对象.
    Django提供了丰富的API来查询和操作这些关联对象, 包括添加, 删除和查询关联对象.
每种字段类型都有其特定的使用参数, 下面将分别介绍这些参数:
* OneToOneField和ForeignKey的主要参数:
  - to: 必需参数, 指定关联的模型类, 或模型类的字符串.
    如果关联的模型先定义了则可以使用模型, : to=model, 如果关联的模型在后面定义了可以使用使用字符串形式, : to='models'.
  - on_delete: 当被关联的对象被删除时, Django将如何处理这个字段.
  - related_name: 定义了从关联模型到当前模型的反向关系名.
    如果不设置, Django将使用模型名加下划线和小写的模型名作为默认的反向关系名.
  - related_query_name: 用于定义反向查询时使用的名称.
  - limit_choices_to: 用于在Django管理后台或表单中限制可选择的对象.
  - to_field: 默认情况下, Django会使用关联模型的主键作为关联字段.
    to_field允许指定关联模型中的另一个字段.
  - parent_link: 在使用多表继承时, 这个参数用于指向父模型中的OneToOneField, 表示继承关系.
  - db_constraint: 这个参数控制着是否在数据库中为这一对一关系创建外键约束. 默认为True, 创建.
    外键约束是数据库层面用来保证数据完整性的一个机制, 它可以确保关联的数据在数据库中始终保持一致和有效.
  - null: 表示该字段在数据库中可以为NULL. 对于ForeignKey, 通常设置为null=True以允许空值.
  - blank: 表示该字段在表单中可以为空.
  - verbose_name: 是一个可选的字段参数, 它用于为模型或模型字段提供一个人类可读的名称.
    这个名称在Django的管理后台(admin site)中特别有用, 因为它会用于表单标签, 列标题和其他需要显示字段名称的地方.

ManyToManyField字段
* ManyToManyField的主要参数:
  - to: 必需参数,指定关联的模型类.
  - limit_choices_to: 同ForeignKey.
  - related_name: 同ForeignKey.
  - related_query_name: 同ForeignKey.
  - symmetrical: 当模型自引用时(即模型指向自己), 需要设置为False, 因为Django默认认为关系是对称的.
    对于非自引用关系, 通常不需要设置(弃用, through替代symmetrical).
  - through: 允许指定一个中间模型来管理多对多关系. 如果设置了此参数, Django将不会创建默认的中间表.
  - through_fields: 当使用自定义中间模型时, 需要指定哪两个字段作为多对多关系的连接字段.
    如果不设置该参数, Django通常能够自动处理并创建必要的中间表和字段.
    但是在一些特定情况下, 如中间模型包含多个外键或递归关系时, 明确指定through_fields是必要的.
  - db_table: 当不使用自定义中间模型时, 可以指定数据库表的名称.
 
注意: ManyToManyField不支持on_delete参数, 因为多对多关系涉及两个模型集合之间的关联, 而不是单个对象之间的关联,
所以不存在'删除被关联对象时如何处理'的问题.
但是, 如果通过自定义中间模型(through)来实现多对多关系, 可以在中间模型中使用ForeignKey并为其设置on_delete参数.

1.3 级联设置

在Django的ORM中, 当定义模型之间的关系(如ForeignKey, OneToOneField, ManyToManyField),
on_delete参数是一个非常重要的选项, 它定义了当关联的对象被删除时, 应该如何处理依赖于该对象的数据库条目.

on_delete参数可以设置为多种不同的值, 以实现不同的行为.
以下是几种on_delete的常见设置:
* 1. models.CASCADE: 级联删除.
     如果主对象被删除, 那么所有关联的外键对象也会被删除. 这是默认行为.
* 2. models.PROTECT: 阻止删除.
     如果尝试删除的对象具有外键关系, 并且这些关系被设置为PROTECT, 则删除操作会抛出一个ProtectedError异常, 阻止删除.
* 3. SET_NULL: 设置为NULL. 仅当外键字段允许NULL值时有效. 
     如果主对象被删除, 所有关联的外键字段都会被设置为NULL.
* 4. SET_DEFAULT: 设置为默认值, 仅当外键字段有默认值时有效.
     如果主对象被删除, 所有关联的外键字段都会被设置为它们的默认值.
* 5. SET(): 设置为特定值或调用一个可调用对象.
     可以传递一个值或一个可调用的对象(如函数), 当主对象被删除时, 所有关联的外键字段都会被设置为这个值或该可调用对象返回的值.
* 6. DO_NOTHING: 什么都不做.
     如果数据库级别支持(如PostgreSQL的ON DELETE NO ACTION), 
     则当主对象被删除时, 外键字段不会被自动修改, 但这可能会导致数据库完整性错误.
     
注意: ORM中没有为MySQL提供级联更新. 

2. 模型创建

2.1 子表和主表的概念

子表和主表(也称为从表, 父表或引用表和被引用表)的概念是这种关系的一个重要方面.
* 主表(Parent Table  Referenced Table): 主表是含有另一个表(子表)通过外键引用的数据的表.
  它通常包含一个或多个主键字段, 这些字段的值在表中是唯一的, 用于唯一标识表中的每一行.
  主表中的这些主键值可能被子表中的外键字段所引用.

* 子表(Child Table  Referencing Table): 子表是包含外键字段的表, 该外键字段用于引用主表中的主键字段或唯一字段.
  子表中的外键字段可以包含主表中主键字段的重复值, 这表示子表中的多行可以'指向''关联'到主表中的同一行.
小提示:
在子表中, 同一个被绑定表的ID(即外键所引用的ID)可以出现多次, 
这体现了'多'的关系, 即多个子表记录可以关联到主表的同一个记录上.

相反, 被绑定表(主表)中的每条记录(包括其ID)是唯一的, 不会重复, 
这体现了'一'的关系, 即每个主表记录都是独立的, 不与其他主表记录重复.

配置了外键约束(如ON DELETE CASCADE), 在删除关联的父记录时, 所有相关的子记录也会被自动删除.
但是, 在修改外键时, 这种约束通常不会影响, 除非显式地删除了子记录或父记录.

2.2 数据库与日志配置

# settings.py 配置文件

# 数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 更改数据库引擎为MySQL
        'NAME': 'MyDjango',  # 数据库名
        'USER': 'root',  # 数据库用户名
        'PASSWORD': '123456',  # 数据库密码
        'HOST': 'localhost',  # 数据库主机地址
        'PORT': '3306',  # MySQL的默认端口是3306
        # 以下参数不是必需的, 但可能对于某些情况很有用
        'OPTIONS': {
            'sql_mode': 'traditional',  # 设置SQL模式
            'charset': 'utf8mb4',  # 推荐使用utf8mb4字符集来支持完整的Unicode字符集, 包括表情符号
        },
    }
}


# ORM日志
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    
    # 处理程序
    'handlers': {
        # 定义一个日志处理程序, 日志级别为DEBUG, 信息在终端展示
        'console': {
            'level': 'DEBUG',  # 日志级别
            'class': 'logging.StreamHandler',  # 消息发送到标准输出
        },
    },
    
    # 日志记录器
    'loggers': {
        # Django数据库后端的日志记录
        'django.db.backends': {
            'handlers': ['console'],  # 使用的日志处理程序(前面定义的console)
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}

image-20240714123734059

2.3 一对一关系

在Django ORM框架中, 使用OneToOneField定义模型之间一对一关系的字段类型.
通过在一个模型类中嵌入另一个模型类的实例('组合')或通过定义一个明确的一对一关系映射来实现.
这种关系意味着两个模型之间的记录是互相关联的, 且这种关联是唯一的.
可以在两张表中的任意一张表中建立关联字段, 但推荐外键建立在次表上.
示例: 定义两个Django模型: Author(作者, 主表)和AuthorDetail(作者详情表, 次表), 一个作者详情信息只能对应一个作者.
# index的models.py
from django.db import models


# 作者表(主)
class Author(models.Model):
    # id字段(自动添加)
    # 作者姓名
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return self.name


# 作者详情表(次表)
class AuthorDetail(models.Model):
    # id字段(自动添加)
    # 手机号码
    phone = models.BigIntegerField(verbose_name='手机号码')
    # 作者地址
    addr = models.CharField(max_length=32, verbose_name='作者地址')
    # 外键字段 (作者表 一对一 作者详情表)
    author = models.OneToOneField(to='Author', on_delete=models.CASCADE, verbose_name='作者id')

    def __str__(self):
        return self.addr

image-20240714194451737

# 生成迁移文件
PS D:\MyDjango> Python manage.py makemigrations
...
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model AuthorDetail
    - Create model Author

# 执行迁移
PS D:\MyDjango> Python manage.py migrate   
...
Applying index.0001_initial...
...

# 先创建主表
(0.015)
CREATE TABLE `index_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(32) NOT NULL,
    `age` integer NOT NULL
);
args=None

# 后创建次表
(0.000) 
CREATE TABLE `index_authordetail` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `phone` bigint NOT NULL, `addr` varchar(32) NOT NULL, 
    `author_id` bigint NOT NULL UNIQUE
); 
args=None


# 添加外键约束
(0.016) 
ALTER TABLE `index_authordetail`
ADD CONSTRAINT `index_authordetail_author_id_1d7de489_fk_index_author_id` 
FOREIGN KEY (`author_id`) 
REFERENCES `index_author` (`id`);
args=()

# 没有级联设置的语句
使用Navicat工具的逆向数据库到模型, 查看模型之间的关系.

image-20240714194853225

在Django框架中, 在模型中定义一个外键(ForeignKey)字段时, 默认情况下, Django会自动为这个字段添加一个后缀_id.
这样做的目的是为了在数据库层面创建一个与关联表的主键相对应的字段.
如果显式地将其命名为author_detail_id, 那么创建表格的时候则为author_detail_id_id.

2.4 一对多关系

在Django ORM中, 通过ForeignKey字段来实现一对多关系. 
其中一个模型对象(通常称为'一')可以关联到多个其他模型对象(通常称为'多').
这种关系在数据库中通常通过外键实现, 但在ORM框架中, 它会被抽象化为模型之间的关联.
ForeignKey字段被添加到'多'方模型中, 指向'一'方模型的主键.
这意味着'多'方模型中的每个实例都可以关联到'一'方模型中的一个特定实例, 
'一'方模型的一个实例可以关联到'多'方模型中的零个, 一个或多个实例.
示例: 定义两个Django模型: Book(书籍)和Publish(出版社), 其中一个出版社可以出版多本书:
# index的models.py
from django.db import models


# 出版社表(被关联表)
class Publish(models.Model):
    # id字段(自动添加)
    # 出版社名字
    name = models.CharField(max_length=32, verbose_name='出版社名字')
    # 出版社地址
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    # 出版社邮箱
    email = models.EmailField(verbose_name='出版社邮箱')

    def __str__(self):
        return self.name


# 书籍表(关联表)
class Book(models.Model):
    # id字段(自动添加)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 外键: 关联出版社表的主键
    Publish = models.ForeignKey(Publish, on_delete=models.CASCADE, verbose_name='出版社id')

    def __str__(self):
        return self.title

image-20240714195911276

# 删除之前的迁移文件和数据库文件
# 生成迁移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
    index\migrations\0001_initial.py
    - Create model Publish
    - Create model Book

# 执行迁移
PS D:\MyDjango> Python manage.py migrate   
Applying index.0001_initial
...
# 先创建被关联表
(0.016) 
CREATE TABLE `index_publish` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(32) NOT NULL, 
    `addr` varchar(32) NOT NULL, 
    `email` varchar(254) NOT NULL  # 默认情况下, Django的EmailField使用VARCHAR254
);
args=None

# 后创建关联表
(0.015) 
CREATE TABLE `index_book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL,
    `Publish_id` bigint NOT NULL
);
args=None

# 添加外键约束
ALTER TABLE `index_book` 
ADD CONSTRAINT `index_book_Publish_id_5638e5c4_fk_index_publish_id` 
FOREIGN KEY (`Publish_id`) 
REFERENCES `index_publish` (`id`);
args=()

# 没有级联设置的语句
使用Navicat工具的逆向数据库到模型, 查看模型之间的关系.

image-20240714195243442

2.5 多对多关系

在Django中, 多对多(Many-to-Many)关系是通过在模型(Model)之间使用ManyToManyField字段来定义的.
Django会自动为这种关系创建一个中间表(也称为关联表或连接表), 用于存储两个模型实例之间的关系.
也可以手动定义这个中间表, 以便在这个关系上添加额外的字段或约束.
多对多关系外键可以定义在任一模型上, 或者可以使用第三个模型(通过through参数)来明确指定多对多关系的中间表.
实例: 定义两个模型: 作者表(Author), Book(书籍表), 一个作者可以写多本书, 一个书也可以有多个作者.
2.5.1 自动创建关联表
# index的models.py
from django.db import models


# 作者表
class Author(models.Model):
    # id字段(自动添加)
    # 作者姓名
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return self.name


# 书籍表
class Book(models.Model):
    # id字段(自动添加)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格(共八位, 小数占两位)
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='书籍出版时间')

    # 外键字段
    author = models.ManyToManyField(to='Author')

    def __str__(self):
        return self.title


image-20240714230700148

# 删除之前的迁移文件和数据库文件
# 生成迁移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Author
    - Create model Book

# 执行迁移
PS D:\MyDjango> Python manage.py migrate  
Applying index.0001_initial...

# 创建作者表
(0.000) CREATE TABLE `index_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(32) NOT NULL, 
    `age` integer NOT NULL
); 
args=None

# 创建书籍表
(0.015)
CREATE TABLE `index_book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL,
    `price` numeric(8, 2) NOT NULL,
    `publish_date` datetime(6) NOT NULL
); 
args=None

# 创建关联表
(0.000) 
CREATE TABLE `index_book_publish` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `book_id` bigint NOT NULL,
    `author_id` bigint NOT NULL
); 
args=None

# 为外键字段设置唯一
(0.000) 
ALTER TABLE `index_book_publish`
ADD CONSTRAINT `index_book_publish_book_id_author_id_46ca4a02_uniq` 
UNIQUE (`book_id`, `author_id`); 
args=()

# 在第三张添加外键绑定书籍表
(0.015) 
ALTER TABLE `index_book_publish` 
ADD CONSTRAINT `index_book_publish_book_id_392d9248_fk_index_book_id` 
FOREIGN KEY (`book_id`) 
REFERENCES `index_book` (`id`); args=()

# 在第三张添加外键绑定作者表
(0.016) 
ALTER TABLE `index_book_publish`
ADD CONSTRAINT `index_book_publish_author_id_3ed0741b_fk_index_author_id`
FOREIGN KEY (`author_id`) 
REFERENCES `index_author` (`id`); 
args=()
使用Navicat工具的逆向数据库到模型, 查看模型之间的关系.

image-20240714204635752

使用ManyToManyField定义一个模型字段时, : author = models.ManyToManyField(to='Author'),
这个字段确实不会在直接关联的表中(比如Book表)创建一个物理列.
相反, Django会创建一个额外的表(中间表)来存储这种多对多关系.
这个中间表通常会有两个外键列, 分别指向两个相关模型(在这个例子中是Book和Author)的主键.
但是后续的可以通过Book实例上的author属性来添加, 删除或查询与书籍相关联的作者.

对于ManyToManyField, Django会自动为创建一个中间表来管理两个模型之间的多对多关系.
表名是由Django根据两个模型的名称和它们所属的应用的名称来生成的.
生成的表名通常会是<app_label>_<model1_name>_<model2_name>的形式, 一般情况下定义ManyToManyField的模型会出现在前面.
当使用ManyToManyField自动创建的第三张表(中间表), 存在一些限制, 特别是关于级联删除(CASCADE)的设置.
Django的ManyToManyField默认不提供直接的级联删除设置(也不需要).
2.5.2 手动动创建关联表
如果需要控制级联删除或更新行为, 可以通过自定义中间表来实现.
在自定义中间表中, 可以定义Django模型的外键, 并显式地设置on_delete参数来控制.
注意: 由于ManyToManyField的特殊性, 通常不会直接在自定义中间表的外键上设置级联删除, 因为这可能会导致意外的数据丢失.

相反, 可能会在自定义的模型方法中处理这些逻辑, 或者在保存或删除模型实例时, 
使用Django的信号(如pre_delete或post_save)来执行自定义的清理或更新操作.
在数据库设计中, 多对多的关系可以通过两个一对多的关系来实现.
这种实现方式通常涉及引入一个额外的表, 用于存储两个主表之间的多对多关系.
这种方式可以在关联表中定义一个额外的字段.

手动动创建关联表特点:
- 完全手动: 开发者需要完全手动定义三张表的结构, 并在代码中显式地处理所有与多对多关系相关的操作, 如添加, 删除和查询关联记录.
- 无ORM支持: 由于中间表不是通过ManyToManyField与through参数指定的, 
  Django的ORM不会自动为这种手动创建的多对多关系提供快捷方法或跨表查询支持.
 
默认创建的中间表遵循: <app_label>_<model1_name>_<model2_name> 的命名规则.
而Python中类的定义通常是大驼峰名称, 'BookAuthor'的格式(推荐).
实例: 定义三个模型: Book(书籍表), Author(作者表), BookAuthor(关联表, 先不使用下划线分隔看看效果), 
在关联表中设置外键分别关联书籍表与作者表.
# index的models.py
from django.db import models


# 书籍表
class Book(models.Model):
    # id字段(自动添加)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格(共八位, 小数占两位)
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='书籍出版时间')

    def __str__(self):
        return self.title


# 作者表
class Author(models.Model):
    # id字段(自动添加)
    # 作者姓名
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return self.name


# 定义关联表
class BookAuthor(models.Model):
    # id字段(自动添加)
    # 外键(关联书籍表的id)
    book = models.ForeignKey(Book, on_delete=models.DO_NOTHING)  # 不推荐设置on_delete
    # 外键(关联作者表的id)
    author = models.ForeignKey(Author, on_delete=models.DO_NOTHING)
    # 记录创建时间
    creation_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'{self.book} --> {self.author}'

image-20240715132044846

# 删除之前的迁移文件和数据库文件
# 生效迁移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Author
    - Create model Book
    - Create model BookAuthor
    
# 执行迁移
PS D:\MyDjango> Python manage.py migrate   
Applying index.0001_initial...

# 创建作者表
(0.000) 
CREATE TABLE `index_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `name` varchar(32) NOT NULL,
    `age` integer NOT NULL
);
args=None


# 创建书籍表
(0.016) 
CREATE TABLE `index_book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL, 
    `price` numeric(8, 2) NOT NULL, 
    `publish_date` datetime(6) NOT NULL
);
args=None

# 创建关联表
(0.000) 
CREATE TABLE `index_bookauthor` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `creation_time` datetime(6) NOT NULL, 
    `author_id` bigint NOT NULL,
    `book_id` bigint NOT NULL
);
args=None

# 为关联表设置外键关联作者id
(0.016)
ALTER TABLE `index_bookauthor`
ADD CONSTRAINT `index_bookauthor_author_id_30476761_fk_index_author_id` 
FOREIGN KEY (`author_id`) 
REFERENCES `index_author` (`id`);
args=()

# 为关联表设置外键关联书籍id
(0.015) 
ALTER TABLE `index_bookauthor` 
ADD CONSTRAINT `index_bookauthor_book_id_26b994fc_fk_index_book_id` 
FOREIGN KEY (`book_id`) 
REFERENCES `index_book` (`id`); 
args=()
使用Navicat工具的逆向数据库到模型, 查看模型之间的关系.

image-20240714235324901

2.5.3 半自动创建关联表
半自动方式指的是通过Django的ManyToManyField字段的through参数指定一个自定义的中间表(第三张表)来管理两个模型之间的多对多关系.

半自动创建关联表特点:
- 部分自动化: Django仍然会处理一些与多对多关系相关的基本操作, 如添加, 删除和查询关联记录.
- 自定义中间表: 开发者可以自定义中间表, 并在其中添加额外的字段以存储关于两个模型之间关系的额外信息.
- ORM支持: 由于通过ManyToManyField与through参数指定了自定义的中间表, Django的ORM仍然支持大部分快捷方法,
  : add(), remove(), clear(), 同时也支持跨表查询.
# index的models.py
from django.db import models


# 书籍表
class Book(models.Model):
    # id字段(自动添加)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格(共八位, 小数占两位)
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='书籍出版时间')

    # 定义多对多关系, 通过自定义的BookAuthor模型
    authors = models.ManyToManyField(
        'Author',  # 指向Author模型
        through='BookAuthor',  # 通过自定义的BookAuthor模型
    )


def __str__(self):
    return self.title


# 作者表
class Author(models.Model):
    # id字段(自动添加)
    # 作者姓名
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return self.name


# 定义关联表
class BookAuthor(models.Model):
    # id字段(自动添加)
    # 外键(关联书籍表的id)
    book = models.ForeignKey(Book, on_delete=models.DO_NOTHING)  # 不推荐设置on_delete
    # 外键(关联作者表的id)
    author = models.ForeignKey(Author, on_delete=models.DO_NOTHING)
    # 记录创建时间
    creation_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'{self.book} --> {self.author}'

image-20240715132010840

在这个例子中, Book模型通过authors字段与Author模型建立了多对多关系, 并通过through='BookAuthor'指定了自定义的中间BookAuthor.
这样, Django就会使用BookAuthor表来存储Book和Author之间的关联信息, 并允许在这个表中添加任何额外的字段.
# 删除之前的迁移文件和数据库文件
# 生效迁移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Author
    - Create model Book
    - Create model BookAuthor
    - Add field authors to book  # 和全手动比, 多出这句: 在book表中添加一个authors字段

# 执行迁移
PS D:\MyDjango> Python manage.py migrate   
 Applying index.0001_initial...
# 创建作者表
(0.000)
CREATE TABLE `index_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `name` varchar(32) NOT NULL,
    `age` integer NOT NULL
);
args=None

# 创建书籍表
(0.015) 
CREATE TABLE `index_book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL,
    `price` numeric(8, 2) NOT NULL,
    `publish_date` datetime(6) NOT NULL
); 
args=None

# 创建关联表
(0.016)
CREATE TABLE `index_bookauthor` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `creation_time` datetime(6) NOT NULL,
    `author_id` bigint NOT NULL,
    `book_id` bigint NOT NULL
); args=None

# 为关联表设置外键关联作者id
(0.016) 
ALTER TABLE `index_bookauthor` 
ADD CONSTRAINT `index_bookauthor_author_id_30476761_fk_index_author_id` 
FOREIGN KEY (`author_id`)
REFERENCES `index_author` (`id`); 
args=()

# 为关联表设置外键关联书籍id
(0.031)
ALTER TABLE `index_bookauthor`
ADD CONSTRAINT `index_bookauthor_book_id_26b994fc_fk_index_book_id` 
FOREIGN KEY (`book_id`)
REFERENCES `index_book` (`id`);
args=()

使用Navicat工具的逆向数据库到模型, 查看模型之间的关系.

image-20240715134608413

注意: 在Book模型中定义了一个ManyToManyField字段来关联Author模型时, 这个字段并不会直接在数据库的book表中创建一个外键列.
但是后续的可以通过Book实例上的authors属性来添加, 删除或查询与书籍相关联的作者.

2.6 db_constraint 参数

在Django的ORM中, OneToOneField是一个特殊的字段类型, 用于定义两个模型之间的一对一关系.
这种关系意味着对于第一个模型中的每一个实例, 第二个模型中最多只能有一个实例与之关联, 反之亦然.

OneToOneField字段有一个可选的参数db_constraint, 这个参数控制着是否在数据库中为这一对一关系创建外键约束.
外键约束是数据库层面用来保证数据完整性的一个机制, 它可以确保关联的数据在数据库中始终保持一致和有效.

db_constraint参数:
- 默认值: True.
- 可选值: True或False.
当db_constraint=True时(默认值), Django会在数据库中为这一对一关系创建一个外键约束.
这意味着, 如果尝试将一个OneToOneField字段指向一个不存在的关联模型实例, 数据库将拒绝这个操作,从而保证了数据的完整性和一致性.

当db_constraint=False时, Django不会在数据库中创建这个外键约束.
这意味着, 可以在数据库中为OneToOneField字段设置一个不存在的关联模型实例的ID, 而不会触发数据库的错误.
这在某些特定的, 需要绕过Django ORM来操作数据库的场景下可能是有用的, 但通常不推荐这样做, 因为它会破坏数据的完整性和一致性.
使用场景:
- 保持数据一致性: 在大多数情况下, 应该保持db_constraint=True(默认值), 以确保数据库的数据完整性和一致性.
- 特殊需求: 如果需要在Django ORM之外以某种方式操作数据库, 并且需要绕过外键约束, 那么可以将db_constraint设置为False.
  但请注意, 这样做需要非常小心, 因为它可能会引入难以调试的数据一致性问题.
from django.db import models  
  

# 地址表
class Place(models.Model):  
    name = models.CharField(max_length=50)  
    address = models.CharField(max_length=80)  
  

# 餐厅表
class Restaurant(models.Model):  
    place = models.OneToOneField(Place, on_delete=models.CASCADE, db_constraint=False)  
    serves_hot_dogs = models.BooleanField(default=False)
在这个例子中, Restaurant和Place之间有一个一对一关系, 但由于db_constraint=False, 数据库层面不会强制这一关系的外键约束.
这意味着, 可以在数据库中为Restaurant实例的place字段设置一个不存在的Place实例的ID, 而不会触发数据库错误.
然而, 这通常不是一个好的做法, 因为它可能会破坏数据的完整性和一致性.

2.7 db_table参数

默认情况下, ManyToManyField字段会根据模型的名称和应用的名称自动生成一个表名.
但是, 如果想要使用不同的表名, 可以通过db_table参数来自定义表名.

db_table参数接受一个字符串值, 该字符串是你想要用于数据库表的名称.
请注意, 这个名称应该与数据库后端支持的命名规则相匹配.

* 自定义的模型可以通过定义Meta类的db_table属性设置表名.
from django.db import models


# 作者表
class Author(models.Model):
    # id字段(自动添加)
    # 作者姓名
    name = models.CharField(max_length=32, verbose_name='作者名字')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'author'


# 书籍表
class Book(models.Model):
    # id字段(自动添加)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格(共八位, 小数占两位)
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateTimeField(auto_now_add=True, verbose_name='书籍出版时间')

    # 外键字段
    author = models.ManyToManyField(to='Author', db_table='book_to_author')

    def __str__(self):
        return self.title

    class Meta:
        db_table = 'book'

image-20240715180447740

# 删除之前的迁移文件和数据库文件
# 生效迁移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Author
    - Create model Book

# 执行迁移
PS D:\MyDjango> Python manage.py migrate   
 Applying index.0001_initial...
 
# 创建作者表
(0.015)
CREATE TABLE `author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(32) NOT NULL,
    `age` integer NOT NULL
); 
args=None

# 创建书籍表
(0.000) 
CREATE TABLE `book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL,
    `price` numeric(8, 2) NOT NULL,
    `publish_date` datetime(6) NOT NULL
); 
args=None

# 创建关联表
(0.016) 
CREATE TABLE `book_to_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `book_id` bigint NOT NULL,
    `author_id` bigint NOT NULL
); 
args=None

# 为关联表添加唯一约束
(0.000)
ALTER TABLE `book_to_author` 
ADD CONSTRAINT `book_to_author_book_id_author_id_f80a4f35_uniq` 
UNIQUE (`book_id`, `author_id`);
args=()

# 创建外键关联书籍表id
(0.015) 
ALTER TABLE `book_to_author` 
ADD CONSTRAINT `book_to_author_book_id_7e3eafce_fk_book_id` 
FOREIGN KEY (`book_id`) 
REFERENCES `book` (`id`);
args=()

# 创建外键关联书籍表id
(0.016) 
ALTER TABLE `book_to_author`
ADD CONSTRAINT `book_to_author_author_id_eca116cd_fk_author_id`
FOREIGN KEY (`author_id`)
REFERENCES `author` (`id`); 
args=()

使用Navicat工具查看模型表.

2024-07-15_180711

3. 多表操作策略

3.1 操作描述

在数据库和ORM框架中, 处理一对多, 一对一时, 和单表的增删改查(CRUD)操作一样, 单独为多对多提供一些特殊方法.

详细描述如下:
* 1. 在一对多关系中, 通常有一个'父'表和一个或多个'子'.
     例如, 一个作者(Author)可以有多本书(Book).
     在这种关系中, 增删改操作通常会直接针对各个表进行, 但在关联数据时需要特别注意.
     - 增加: 在子表中添加新记录时, 需要指定外键字段以指向父表中的正确记录.
     - 删除: 删除父表中的记录时, 需要决定如何处理子表中的相关记录(级联删除, 设置为NULL或抛出错误).
     - 修改: 修改父表或子表中的记录时, 通常不需要特殊的ORM方法, 除非需要更新外键指向.

* 2. 一对一关系通常用于需要将大量数据分割到不同表中的情况, 但两个表之间的记录是紧密相关的.
     例如, 用户(User)和他们的用户详情(UserProfile)可能是一对一关系.
     增加, 删除, 修改: 这些操作与一对多关系类似, 但更侧重于确保两个表之间的数据一致性.

* 3. 多对多关系表示两个表中的记录可以相互关联.
     例如, 一个作者可以写多本书, 而一本书也可以被多个作者共同著作.
     在ORM中, 这种关系通常通过中间表(也称为关联表或联结表)来实现, 还提供了特定的方法来简化关系的管理, 如下:
     - .add(): 向多对多关系中添加一个新的关联. 例如, 将一个作者添加到一本书的作者列表中.
       添加关联: Book.author.add(author)  # 将某个作者添加到书籍的作者列表中.
     - .set([]): 设置多对多关系的完整列表, 这通常会先清除现有的关联, 然后添加新的关联.
       设置新的关联列表:  Book.Book.set([author1, author2])  # 设置书籍的作者列表为作者1和作者2.
     - .remove(): 从多对多关系中移除一个特定的关联.
       移除关联: Book.author.remove(author)  # 从书籍的作者列表中移除某个作者.
     - .clear(): 清除多对多关系中的所有关联.
        清除所有关联: Book.author.clear()  # 移除书籍的作者列表中的所有作者.
        
在ORM中进行多表操作时, 需要注意事务的管理. 确保在需要时开启事务, 并在操作完成后提交事务, 以保证数据的一致性和完整性.

3.2 测试模型创建

下面有一张'书籍详细信息汇总表', 信息如下: 
书籍名称价格出版时间出版社名称出版社邮箱出版社地址作者名称作者年龄作者手机号码作者地址
编程基础50.002022-01-01上海出版社123@qq.com上海kid18110北京
文学经典65.002021-05-10上海出版社123@qq.com上海kid18110北京
文学经典65.002021-05-10上海出版社123@qq.com上海qq19112上海
科幻小说集70.002023-03-15北京出版社456@qq.com北京kid18110北京
科幻小说集70.002023-03-15北京出版社456@qq.com北京qq19112上海
由于'文学经典''科幻小说集'两本书在关联表中都有两位作者(kid和qq), 因此这两本书在结果表中会出现两次, 每次对应一个作者.
通过将相关数据拆分到不同的表中, 并使用外键建立表之间的关系, 可以避免这种数据冗余. 
可以将上面的表格拆分为以下几个部分:
* 1. 作者表.
* 2. 作者详情表.
* 3. 出版社表.
* 4. 书籍表.
* 5. 书籍-作者关联表.
3.2.1 定义作者表模型
作者表(Authors): 存储作者的基本信息, 
: 作者id(BIGINT), 作者名称(VARCHAR), 年龄(INT)和作者详情ID(INT)(手机号码和地址存储在另一个表中).
id(作者id)name(作者名称)age(作者年龄)
1kid18
2qq19
3qaq20
# index的models.py
from django.db import models

# 定义作者表
class Author(models.Model):
    # 作者id(自动创建)
    # 作者名称(不定长字符串, 但是有一个宽度现在)
    name = models.CharField(max_length=32, verbose_name='作者名称')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    # 对象的字符串表现形式
    def __str__(self):
        return self.name
    

image-20240716001306269

3.2.2 定义作者详情表模型(一对一)
作者详情表(AuthorDetails): 存储作者的详细联系信息, 
: 作者详情表id(BIGINT), 作者手机号码(VARCHAR)和作者地址(VARCHAR).
id(作者详情id)phone(作者手机号码)addr(作者地址)author_id(作者id)
1110北京1
2112上海2
3119深圳3
# 作者详情表
class AuthorDetail(models.Model):
    # 作者id(自动创建)
    # 作者手机号码
    phone = models.CharField(max_length=11, verbose_name='作者手机号码')
    # 作者地址
    addr = models.CharField(max_length=64, verbose_name='作者地址')
    # 外键, 绑定作者表id, 并设置级联删除
    author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE)

    # 对象的字符串表现形式
    def __str__(self):
        return f'{self.id}'
    

image-20240716142131210

3.2.3 定义出版社表模型
出版社表(Publishers): 存储出版社的基本信息, 
: 出版社id(BIGINT), 出版社名称(VARCHAR), 出版社地址(VARCHAR).
id(出版社id)name(出版社名称)addr(出版社地址)email(出版社邮箱)
1上海出版社上海123@qq.com
2北京出版社北京456@qq.com
# 出版社表
class Publisher(models.Model):
    # 出版社id(自动创建)
    # 出版社名称
    name = models.CharField(max_length=32, verbose_name='出版社名称')
    # 出版社地址
    addr = models.CharField(max_length=64, verbose_name='出版社地址')
    # 出版社邮箱(默认情况下, Django的EmailField 使用较长的 VARCHAR 254)
    email = models.EmailField(verbose_name='出版社邮箱')

    # 对象的字符串表现形式
    def __str__(self):
        return self.name
    

image-20240716001352205

3.2.4 定义书籍表模型(一对多)
书籍表(Books): 存储书籍的基本信息,
: 书籍ID(BIGINT), 书籍名称(VARCHAR), 书籍价格(DECIMAL), 书籍出版时间(DATE).
id(书籍id)title(书籍名称)price(书籍价格)publish_date(书籍出版时间)publish_id(出版社id)
1编程基础50.002022-01-011
2文学经典65.002021-05-101
3科幻小说集70.002023-03-152
# 书籍表
class Book(models.Model):
    # 书籍id(自动创建)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateField(verbose_name='书籍出版时间', )
    # 外键, 绑定出版社id, 并设置级联删除
    publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
    # 外键, 使用半自动方式创建第三张表管理多对多表关系
    author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))

    # 对象的字符串表现形式
    def __str__(self):
        return self.title
    
一些开发者倾向于在外键字段名中使用复数形式, 特别是当该字段代表一个集合或列表时.
这种做法的逻辑是, 外键通常指向另一个表的一个或多个记录, 因此使用复数形式可以直观地表示这一点.

image-20240716154615786

3.2.5 定义书籍-作者关联表模型(多对多)
* 5. 书籍-作者关联表(Book_Authors): 存储书籍和作者之间的多对多关系.
     : 关联表id(BIGINT), 书籍id(INT), 作者id(INT).
id(关联表id)book_id(书籍表id)author_id(作者表id)
111
221
322
431
532
# 书籍-作者关联表
class BookAuthor(models.Model):
    # 关联表id(自动创建)
    # 外键, 绑定书籍表id, 并设置级联删除的级别为什么都不做
    book = models.ForeignKey(to='Book', on_delete=models.DO_NOTHING)
    # 外键, 绑定作者表id, 并设置级联删除的级别为什么都不做
    author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING)

    # 对象的字符串表现形式
    def __str__(self):
        return f'{self.book} --> {self.author}'
    

image-20240716001611028

3.2.6 模型的完整代码
# index的models.py
from django.db import models


# 定义作者表
class Author(models.Model):
    # 作者id(自动创建)
    # 作者名称(不定长字符串, 但是有一个宽度现在)
    name = models.CharField(max_length=32, verbose_name='作者名称')
    # 作者年龄
    age = models.IntegerField(verbose_name='作者年龄')

    # 对象的字符串表现形式
    def __str__(self):
        return self.name


# 作者详情表
class AuthorDetail(models.Model):
    # 作者id(自动创建)
    # 作者手机号码
    phone = models.CharField(max_length=11, verbose_name='作者手机号码')
    # 作者地址
    addr = models.CharField(max_length=64, verbose_name='作者地址')
    # 外键, 绑定作者表id, 并设置级联删除
    author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE)

    # 对象的字符串表现形式
    def __str__(self):
        return f'{self.id}'


# 出版社表
class Publisher(models.Model):
    # 出版社id(自动创建)
    # 出版社名称
    name = models.CharField(max_length=32, verbose_name='出版社名称')
    # 出版社地址
    addr = models.CharField(max_length=64, verbose_name='出版社地址')
    # 出版社邮箱(默认情况下, Django的EmailField 使用较长的 VARCHAR 254)
    email = models.EmailField(verbose_name='出版社邮箱')

    # 对象的字符串表现形式
    def __str__(self):
        return self.name


# 书籍表
class Book(models.Model):
    # 书籍id(自动创建)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍出版时间
    publish_date = models.DateField(verbose_name='书籍出版时间', )
    # 外键, 绑定出版社id, 并设置级联删除
    publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
    # 外键, 使用半自动方式创建第三张表管理多对多表关系
    author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))

    # 对象的字符串表现形式
    def __str__(self):
        return self.title


# 书籍-作者关联表
class BookAuthor(models.Model):
    # 关联表id(自动创建)
    # 外键, 绑定书籍表id, 并设置级联删除的级别为什么都不做
    book = models.ForeignKey(to='Book', on_delete=models.DO_NOTHING)
    # 外键, 绑定作者表id, 并设置级联删除的级别为什么都不做
    author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING)

    # 对象的字符串表现形式
    def __str__(self):
        return f'{self.book} --> {self.author}'

3.2.7 数据迁移
# 生成迁移文件
PS D:\MyDjango> python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model Author
    - Create model Book
    - Create model Publisher
    - Create model BookAuthor
    - Add field author to book  # 多对多的外键, 不会创建对应的字段, 但可以通过这个字段进行一些操作
    - Add field publisher_id to book  # 一对多的外键
    - Create model AuthorDetail

# 执行迁移:
PS D:\MyDjango> python manage.py migrate   
 Applying index.0001_initial...
 
# 创建作者表
(0.000)
CREATE TABLE `index_author` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(32) NOT NULL, 
    `age` integer NOT NULL
); 
args=None

# 创建书籍表
(0.000)
CREATE TABLE `index_book` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(32) NOT NULL,
    `price` numeric(8, 2) NOT NULL,
    `publish_date` date NOT NULL
);
args=None

# 创建出版社表
(0.016) 
CREATE TABLE `index_publisher` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `name` varchar(32) NOT NULL,
    `addr` varchar(64) NOT NULL,
    `email` varchar(254) NOT NULL
); 
args=None

# 创建书籍-作者关联表
(0.015)
CREATE TABLE `index_bookauthor` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `author_id` bigint NOT NULL, 
    `book_id` bigint NOT NULL
);
args=None

# 为书籍表创建外键字段绑定出版社id
(0.016)
ALTER TABLE `index_book`
ADD COLUMN `publisher_id` bigint NOT NULL , 
ADD CONSTRAINT `index_book_publisher_id_c0ee3645_fk_index_publisher_id` 
FOREIGN KEY (`publisher_id`) 
REFERENCES `index_publisher`(`id`);
args=[]

# 创建作者详情表
(0.016) 
CREATE TABLE `index_authordetail` (
    `id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `phone` varchar(11) NOT NULL, 
    `addr` varchar(64) NOT NULL, 
    `author_id` bigint NOT NULL UNIQUE
);
args=None


# 为作者-书籍表创建外键绑定作者表id
(0.015) 
ALTER TABLE `index_bookauthor` 
ADD CONSTRAINT `index_bookauthor_author_id_30476761_fk_index_author_id`
FOREIGN KEY (`author_id`) 
REFERENCES `index_author` (`id`); 
args=()

# 为作者-书籍表创建外键绑定书籍表id
(0.016)
ALTER TABLE `index_bookauthor`
ADD CONSTRAINT `index_bookauthor_book_id_26b994fc_fk_index_book_id`
FOREIGN KEY (`book_id`) 
REFERENCES `index_book` (`id`); 
args=()

# 为作者详情表创建外键绑定作者id
(0.015) 
ALTER TABLE `index_authordetail` 
ADD CONSTRAINT `index_authordetail_author_id_1d7de489_fk_index_author_id`
FOREIGN KEY (`author_id`) 
REFERENCES `index_author` (`id`); 
args=()

使用Navicat工具查看创建的模型表.

2024-07-16_001704

使用Navicat工具逆向数据库到模型, 查看模型之间的关联.

image-20240716155255377

4. 创建记录

多表操作插入数据描述:
一对一: 在创建子表记录时, 需要指定与主表记录相关联的外键字段.
一对多: 在创建'多'端的记录时, 需要指定与'一'端记录相关联的外键字段.
多对多: 在创建记录时, 通常不需要直接操作第三张表, 而是通过ORM提供的API来添加或删除关联关系.

4.1 外键字段使用说明

在Django中, 外键字段(: author)和携带'_id'后缀的外键字段(: author_id)在处理外键关系时代表了不同的方式和概念. 

* 携带'_id'后缀的外键字段介绍(不涉及关联表):
  - 直接操作数据库列: 当看到如: author_id时, 这通常意味着直接引用数据库表中的外键列.
    然而, 在Django的ORM中, 通常不会直接这样做, 因为Django的ORM抽象了这些底层的数据库操作.
  - 在特定情况下使用: 在某些情况下, 如自定义查询或使用Django的RawQuerySet时, 可能会遇到author_id.
    此外, 如果正在与Django生成的数据库表直接交互(例如, 使用SQL语句或数据库管理工具), 也会看到author_id列.
  - 不推荐在ORM中使用: 在Django的ORM代码中, 通常不推荐直接使用author_id, 
    因为这样做会绕过Django ORM提供的所有好处, 如自动关联查询, 数据验证等.
  - 异常提示: 如果author_id的值是一个实例对象会抛出TypeError类型错误, :
    TypeError: Field 'id' expected a number but got <Author: qaq>.
    
* 不携带_id后缀的外键字段介绍(涉及关联表):
  - Django ORM中的外键字段: 在Django模型中, : author, 是一个外键字段, 它代表了与Author模型的关联.
    当访问这个字段时, 得到的是一个指向Author模型实例的引用.
  - 自动处理关联: Django ORM会自动处理author字段与Author模型之间的关联.
    这意味着, 当通过author字段设置或获取值时, Django会处理所有的数据库查询和关联.
  - 推荐的使用方式: 在Django ORM代码中, 应该始终使用author这样的外键字段来引用关联的对象.
    这样, 可以利用Django ORM提供的所有功能和优势.
  - 异常提示: 如果author的值是一个id值会抛出ValueError值错误, :
    ValueError: Cannot assign "3": "AuthorDetail.author" must be a "Author" instance.

4.2 作者表数据(基本表)

id(作者id)name(作者名称)age(作者年龄)
1kid18
2qq19
3qaq20
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 插入两条记录
    user_row1 = Author.objects.create(id=1, name='kid', age=18)
    print(user_row1)  # kid
    user_row2 = Author.objects.create(id=2, name='qq', age=19)
    print(user_row2)  # qq

image-20240716135057261

# ORM日志:
(0.015) INSERT INTO `index_author` (`id`, `name`, `age`) VALUES (1, 'kid', 18); args=[1, 'kid', 18]
(0.000) INSERT INTO `index_author` (`id`, `name`, `age`) VALUES (2, 'qq', 19); args=[2, 'qq', 19]

4.3 作者表详情表数据(一对一)

在Django中, 处理数据库模型时, 特别是涉及到外键(ForeignKey)关系时, 有两种主要的方式来创建关联对象.
* 方式1: 外键绑定id值(不推荐). 
  在这种方式中, 直接在创建AuthorDetail对象时, 通过author_id(携带_id)字段来指定与之关联的Author对象的ID. 
  
* 方式2: 外键是一个实例(推荐).
  在这种方式中, 首先创建一个Author对象, 然后在创建AuthorDetail对象时, 将这个Author对象作为外键字段author(不携带_id)的值.
  这是Django中处理外键关系的推荐方式, 因为它更加直观, 且避免了直接操作数据库ID的需要.
id(作者详情id)phone(作者手机号码)addr(作者地址)author_id(作者id)
1110北京1
2112上海2
3119深圳3
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, AuthorDetail

    # 方式1(外键绑定id值)
    author_detail_row1 = AuthorDetail.objects.create(id=1, phone=110, addr='北京', author_id=1)
    print(author_detail_row1)
    author_detail_row2 = AuthorDetail.objects.create(id=2, phone=120, addr='上海', author_id=2)
    print(author_detail_row2)

    # 方式2(外键绑定一个实例)
    user_row3 = Author.objects.create(id=3, name='qaq', age=20)
    print(user_row3)
    author_detail_row3 = AuthorDetail.objects.create(id=3, phone=119, addr='深圳', author=user_row3)
    print(author_detail_row3)

image-20240716150154824

# ORM日志:
(0.000) 
INSERT INTO `index_authordetail` (`id`, `phone`, `addr`, `author_id`) VALUES (1, '110', '北京', 1); 
args=[1, '110', '北京', 1]

(0.000)
INSERT INTO `index_authordetail` (`id`, `phone`, `addr`, `author_id`) VALUES (2, '120', '上海', 2); 
args=[2, '120', '上海', 2]

(0.016) 
INSERT INTO `index_author` (`id`, `name`, `age`) VALUES (3, 'qaq', 20); 
args=[3, 'qaq', 20]

(0.000) 
INSERT INTO `index_authordetail` (`id`, `phone`, `addr`, `author_id`) VALUES (3, '119', '深圳', 3);
args=[3, '119', '深圳', 3]

4.4 出版社表数据(基本表)

id(出版社id)name(出版社名称)addr(出版社地址)email(出版社邮箱)
1上海出版社上海123@qq.com
2北京出版社北京456@qq.com
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Publisher

    # id值自动创建
    publisher_row1 = Publisher.objects.create(name='上海出版社', addr='上海', email='123@qq.com')
    print(publisher_row1)
    publisher_row2 = Publisher.objects.create(name='北京出版社', addr='北京', email='456@qq.com')
    print(publisher_row2)

image-20240716151502938

# ORM日志:
(0.000) 
INSERT INTO `index_publisher` (`name`, `addr`, `email`) VALUES ('上海出版社', '上海', '123@qq.com');
args=['上海出版社', '上海', '123@qq.com']
(0.000) 
INSERT INTO `index_publisher` (`name`, `addr`, `email`) VALUES ('北京出版社', '北京', '456@qq.com'); 
args=['北京出版社', '北京', '456@qq.com']

4.5 书籍表数据(一对多)

id(书籍id)title(书籍名称)price(书籍价格)publish_date(书籍出版时间)publish(出版社id)
1编程基础50.002022-01-011
2文学经典65.002021-05-101
3科幻小说集70.002023-03-152
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book
    # 日期时间模块
    from datetime import datetime

    # 因为数据分批录入的, 这里外键使用id值.
    book_row1 = Book.objects.create(title='编程基础', price='50.00',
                                    publish_date=datetime.strptime('2022-01-01', '%Y-%m-%d'),
                                    publisher_id=1)
    print(book_row1)

    book_row2 = Book.objects.create(title='文学经典', price='65.00',
                                    publish_date=datetime.strptime('2021-05-10', '%Y-%m-%d'),
                                    publisher_id=1)
    print(book_row2)

    book_row3 = Book.objects.create(title='科幻小说集', price='70.00',
                                    publish_date=datetime.strptime('2023-03-15', '%Y-%m-%d'),
                                    publisher_id=2)
    print(book_row3)

image-20240716155818732

# ORM日志:
(0.000)
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`)
VALUES ('编程基础', '50.00', '2022-01-01', 1);
args=['编程基础', '50.00', '2022-01-01', 1]

(0.015)
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`)
VALUES ('文学经典', '65.00', '2021-05-10', 1);
args=['文学经典', '65.00', '2021-05-10', 1]

(0.000) 
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`) 
VALUES ('科幻小说集', '70.00', '2023-03-15', 2); 
args=['科幻小说集', '70.00', '2023-03-15', 2]

4.6 书籍-作者关联表数据(多对多)

id(关联表id)book_id(书籍表id)author_id(作者表id)
111
221
322
431
532
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import BookAuthor

    # 因为数据分批录入的, 这里外键使用id值.
    book_author_row1 = BookAuthor.objects.create(book_id=1, author_id=1)
    print(book_author_row1)

image-20240716160901585

# ORM日志
# 插入第一条数据
(0.000) INSERT INTO `index_bookauthor` (`book_id`, `author_id`) VALUES (1, 1); args=[1, 1]

# 查询book对象(字段简化为*)
(0.000) SELECT * FROM `index_book` WHERE `index_book`.`id` = 1 LIMIT 21; args=(1,)
# # 查询author对象
(0.000) SELECT * FROM `index_author` WHERE `index_author`.`id` = 1 LIMIT 21; args=(1,)

# 省略...
# 关联表对象的字符串表现形式为:
def __str__(self):
    return f'{self.book} --> {self.author}'

# 在打印关联表对象的时候self.book和self.author, 会分别执行查询语句获取模型对象.

# 修改关联表对象的字符串表现形式为
    def __str__(self):
        return f'{self.book_id} --> {self.author_id}'
# 在打印关联表对象的时候不会执行查询语句
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import BookAuthor

    book_author_row6 = BookAuthor.objects.create(book_id=3, author_id=3)
    print(book_author_row6)

image-20240716163635988

# ORM日志:
(0.016) INSERT INTO `index_bookauthor` (`book_id`, `author_id`) VALUES (3, 3); args=[3, 3]

4.7 add方法(多对多)

在Django的ORM中, 为多对多关系的模型提供一个add()方法, 它允许将一个或多个对象添加到关联表中.
这个方法使得在Django中管理多对多关系变得简单而直观.
注意事项: 在使用add方法时, 如果尝试添加的对象已经存在于多对多关系中, Django会忽略这些重复项, 并且不会引发错误.
现在有两个模型, Book和Author, 它们之间通过多对多关系连接, 并且在Book模型中定义了一个author字段.
可以使用add()方法将一个或多个Author对象(或id值)添加到Book对象的author列表中.
* author字段虽然不是一个真正的Python列表, 但Django的ORM提供了类似列表的操作来管理这种多对多关系.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    # 添加单个对象(为编程基础书籍添加一个作者)
    # 先获取作者对象
    author = Author.objects.filter(name='qaq').first()

    # 后获取书籍对象
    book = Book.objects.filter(title='编程基础').first()
    
    # 将author添加到book的author列表中
    res = book.author.add(author)
    print(res)

image-20240716202004387

# ORM日志:
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author`
WHERE `index_author`.`name` = 'qaq' 
ORDER BY `index_author`.`id` ASC
LIMIT 1;
args=('qaq',)

(0.000)
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, ...
FROM `index_book` 
WHERE `index_book`.`title` = '编程基础'
ORDER BY `index_book`.`id` ASC
LIMIT 1;
args=('编程基础',)

# 先查询后插入
(0.000) 
SELECT `index_bookauthor`.`author_id` FROM `index_bookauthor` 
WHERE (`index_bookauthor`.`author_id` IN (3) AND `index_bookauthor`.`book_id` = 1);
args=(3, 1)

(0.000) 
INSERT INTO `index_bookauthor` (`book_id`, `author_id`) VALUES (1, 3);
args=(1, 3)

可以一次性添加多个对象到多对多关系中, 只需将对象作为列表或查询集传递给add方法:
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author
    from datetime import datetime

    # 创建Book实例
    book_row = Book.objects.create(title='玄幻小说集', price=88.88,
                                   publish_date=datetime.strptime('2024-05-1', '%Y-%m-%d'),
                                   publisher_id=2)

    # 获取多个Author实例
    author_row1 = Author.objects.get(name='kid')
    author_row2 = Author.objects.get(name='qq')

    # 为书籍添加多个作者
    book_row.author.add(author_row1, author_row2)

通过create创建对象返回的实例或使用get()方法获取实例, 通过外键字段使用add()方法会提示.
: book.author.add(author) PyCharm会出现提示(不用管):
Method 'add' has to have 'through_defaults' argument because it's used on many-to-many 
relation with an intermediate model. Consider calling it on intermediate model's own manager.

image-20240716203518396

# ORM日志:
(0.000) 
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`) 
VALUES ('玄幻小说集', '88.88', '2024-05-01', 2); 
args=['玄幻小说集', '88.88', '2024-05-01', 2]

(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author` 
WHERE `index_author`.`name` = 'kid'
LIMIT 21;
args=('kid',)

(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`
FROM `index_author` 
WHERE `index_author`.`name` = 'qq'
LIMIT 21;
args=('qq',)

(0.000)
SELECT `index_bookauthor`.`author_id` FROM `index_bookauthor` 
WHERE (`index_bookauthor`.`author_id` IN (1, 2) AND `index_bookauthor`.`book_id` = 4);
args=(1, 2, 4)

(0.000)
INSERT INTO `index_bookauthor` (`book_id`, `author_id`)
VALUES (4, 1), (4, 2); 
args=(4, 1, 4, 2)
add方法也接受主表的主键值作为参数, 使用方式如下:
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author
    from datetime import datetime

    # 创建Book实例
    book_row = Book.objects.create(title='天文选集', price=74.10,
                                   publish_date=datetime.strptime('2024-05-5', '%Y-%m-%d'),
                                   publisher_id=2)

    # 为书籍添加多个作者
    book_row.author.add(1, 2, 3)

image-20240716211228873

# ORM日志:
(0.015) 
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`)
VALUES ('天文选集', '74.10', '2024-05-05', 2); 
args=['天文选集', '74.10', '2024-05-05', 2]

(0.000) 
SELECT `index_bookauthor`.`author_id` FROM `index_bookauthor` 
WHERE (`index_bookauthor`.`author_id` IN (1, 2, 3) AND `index_bookauthor`.`book_id` = 5); 
args=(1, 2, 3, 5)

(0.000)
INSERT INTO `index_bookauthor` (`book_id`, `author_id`) 
VALUES (5, 1), (5, 2), (5, 3);
args=(5, 1, 5, 2, 5, 3)
如果尝试添加的对象已经存在于多对多关系中, Django会忽略这些重复项, 并且不会引发错误.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 创建Book实例
    book_row = Book.objects.filter(title='天文选集').first()
    # 为书籍添加多个作者
    res = book_row.author.add(1, 2, 3)
    print(res)

image-20240716212239145

# ORM日志:
(0.000)
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, ...
FROM `index_book` 
WHERE `index_book`.`title` = '天文选集'
ORDER BY `index_book`.`id` ASC 
LIMIT 1;
args=('天文选集',)

# 不执行插入数据而是执行查询
(0.000)
SELECT `index_bookauthor`.`author_id` FROM `index_bookauthor` 
WHERE (`index_bookauthor`.`author_id` IN (1, 2, 3) AND `index_bookauthor`.`book_id` = 5); 
args=(1, 2, 3, 5)

5. 查询记录

Django ORM中, 当查询一对多或多对多关系时, 默认情况下, 如果访问的是一个关联对象集合(例如, 一个作者的所有书籍),
Django会自动返回一个RelatedManager实例, 这个实例类似于一个查询集(QuerySet), 但它是专门用于管理关联对象的.
可以在这个RelatedManager实例上调用.all()来获取这个集合中的所有对象.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    book = Book.objects.filter(title='科幻小说集').first()
    print(book.author, type(book.author))  # ManyRelatedManager
    # 获取科幻小说集的所有作者对象
    print(book.author.all())  # <QuerySet [<Author: qaq>] ...>
    

5.1 正/反向查询

在Django ORM多表查询中最常见的两种查询方式是正向查询与反向查询.
这两种查询方式主要涉及到数据库表之间的关联关系, 特别是外键关系.
5.1.1 正向查询
正向查询: 是指由外键所在表(从表或子表)查询关联的主表对象或主表字段的过程.
简单来说, 就是从一个包含外键的表中, 通过外键字段来查询与之关联的另一个表(主表或父表)的数据.

特点: 查询方向是从外键所在的表(从表)到被关联的表(主表)
可以通过外键字段名直接访问关联的主表对象或字段.
现有两个模型, Book(书籍)和Author(作者)是多对多关系, 其中Book模型中包含一个指向Author模型的外键字段author.
正向查询: 则是从Book模型出发, 通过author外键字段查询关联的Author对象.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 查询天文选集的所有作者
	book = Book.objects.get(title='天文选集')  
    authors = book.author.all()  # 正向查询   
    print(authors)
    

image-20240717011029385

# ORM日志:
# 先查询书籍对象
(0.000)
SELECT * FROM `index_book` WHERE `index_book`.`title` = '天文选集' LIMIT 21; args=('天文选集',)

# 先拼表, 后过书籍id滤作者对象
(0.000) 
SELECT * FROM `index_author` 
INNER JOIN `index_bookauthor`
ON (`index_author`.`id` = `index_bookauthor`.`author_id`) 
WHERE `index_bookauthor`.`book_id` = 5 
LIMIT 21;
args=(5,)

5.1.2 反向查询
反向查询: 是指由主表对象查询与之关联的从表对象或字段的过程.
, 从一个不包含外键的表中, 通过某种方式查询与之关联的包含外键的表(从表或子表)的数据.
可以通过Django ORM自动生成的_set属性或自定义的related_name属性来进行反向查询.
如果不使用related_name, Django会默认使用模型名小写加_set作为反向查询的属性名.

特点: 查询方向是从主表到从表.
继续上面的例子, 如果想从Author模型出发, 查询所有由'kid'编写的书籍, 就需要进行反向查询.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 查询作者kid的所有书籍
    author = Author.objects.get(name="kid")
    books = author.book_set.all()  #  反向查询, 使用默认名称book_set
    print(books)

image-20240717011852668

# ORM日志:
(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author`
WHERE `index_author`.`name` = 'kid'
LIMIT 21;
args=('kid',)

# 反向查询指拼接两张表
(0.000)
SELECT * FROM `index_book` 
INNER JOIN `index_bookauthor` 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
WHERE `index_bookauthor`.`author_id` = 1
LIMIT 21; 
args=(1,)
5.1.3 反向关联名称
如果在定义外键时指定了related_name(反向关联名称), 则可以使用该名称进行反向查询.
修改Book模型中的外键参数, related_name='authored_books':
# index的models.py
# 书籍表
class Book(models.Model):
    # 书籍id(自动创建)
    # 书籍名称
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    # 书籍价格
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    # 书籍出版时间
    publish_date = models.DateField(verbose_name='书籍出版时间', )
    # 外键, 绑定出版社id, 并设置级联删除
    publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
    # 外键, 使用半自动方式创建第三张表管理多对多表关系
    author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))

    # 对象的字符串表现形式
    def __str__(self):
        return self.title

image-20240717013154062

# 生成迁移文件
PS D:\MyDjango> python manage.py makemigrations    
Migrations for 'index':
  index\migrations\0002_alter_book_author.py
    - Alter field author on book  -- 修改字段
    
# 执行迁移
PS D:\MyDjango> python manage.py migrate   
 Applying index.0002_alter_book_author...
...
使用related_name参数设置的值来进行方向查询:
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 查询作者kid的所有书籍
    author = Author.objects.get(name="kid")
    books = author.authored_books.all()  # 反向查询, 使用related_name参数定义的名称
    print(books)

image-20240717013828907

# ORM日志:
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` FROM `index_author`
WHERE `index_author`.`name` = 'kid' 
LIMIT 21;
args=('kid',)

(0.000) 
SELECT * FROM `index_book`
INNER JOIN `index_bookauthor`
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
WHERE `index_bookauthor`.`author_id` = 1 
LIMIT 21;
args=(1,)
总结:
正向查询和反向查询是Django ORM中处理表之间关联关系的两种基本方式。
正向查询通过外键字段名进行, 而反向查询则可以通过Django自动生成的_set属性或自定义的related_name属性来实现.

5.2 字段查找

5.2.1 双下划线语法
在Django中, __(双下划线)在查询集(QuerySets)中扮演着特殊的角色, 
尤其是在使用filter(), exclude(), annotate(), order_by()等方法时.

双下划线允许执行更复杂的查询, 比如跨关系查询, 执行数据库聚合函数等.
当看到类似:age__gt(字段__查询操作符), author__name(关联的模型__关联的模型主表字段)这样的表达式时, 
这里的__前后的值分别代表不同的含义:
* __前面的值: 通常指的是Django模型(Model)中的一个字段名, 
  或者是一个关系字段(: ForeignKey, ManyToManyField等)所关联的模型名(当进行跨模型查询时).
  在这个例子(author__name), author是当前模型中的一个字段名, 它指向另一个模型(Author模型),
  通常是通过ForeignKey或其他关系字段实现的.

* __ 后面的值: 指定了想要对__前面的字段进行的操作或查询条件.
  这可以是数据库中的一个操作符, 比如gt(大于), lt(小于).
  在这个特定的例子(author__name), name是Author模型中的一个字段名. 
  
  这里的author__name是在执行跨模型查询, 意思是查询当前模型中author字段关联的Author模型,
  并在这个关联的Author模型上进一步查询其name字段.

Django特有的'双下划线'(__)语法, 可以使得开发者能够轻松地在数据库层面实现复杂的查询逻辑.
5.2.2 跨表字段查询
在Django的ORM中, 跨表字段查询允许根据与主模型相关联的其他模型中的字段来过滤查询结果.
5.2.2.1 正向使用
示例: 现在有两个模型: Author和Book, 他们之间是多对多的关系靠关联进行联系.
如果想要查询所有由特定作者(比如名字为'kid'的作者)所写的书籍, 可以使用: 
Book.objects.filter(bookauthor__author__name='kid'), 这类查询表达式用于执行跨表查询. 
代码解释: Book(书籍表)先连接bookauthor(关联表)再连接author(作者表), 最后过滤出来author(作者表)中name字段值为'kid'的数据.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 查询作者kid的所有书籍
    books = Book.objects.filter(bookauthor__author__name='kid')
    print(books)

image-20240717155130212

# ORM日志:
(0.000) 
SELECT * FROM `index_book`
INNER JOIN `index_bookauthor` 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
INNER JOIN `index_author`
ON (`index_bookauthor`.`author_id` = `index_author`.`id`) 
WHERE `index_author`.`name` = 'kid' 
LIMIT 21;
args=('kid',)
由于外键是建立在Book表中, Book表使用跨表字段查询的时候可以省略掉中间表部分, 它会自动处理关联表.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 查询作者kid的所有书籍
    books = Book.objects.filter(author__name='kid')
    print(books)

image-20240717141449487

# ORM日志
(0.000)
SELECT * FROM `index_book` 
INNER JOIN `index_bookauthor` 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`) 
INNER JOIN `index_author`
ON (`index_bookauthor`.`author_id` = `index_author`.`id`) 
WHERE `index_author`.`name` = 'kid' 
LIMIT 21;
args=('kid',)

5.2.2.2 反向使用
由于外键是建立在Book表中, Author表使用跨表字段查询的时候不可以省略掉中间表部分, 需要手动动处理关联表.
示例: 如果想要查询所有由特定作者(比如名字为'kid'的作者)所写的书籍, 可以使用: bookauthor__book__title查询表达式执行跨表查询. 
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 查询书籍天文选集的所有作者
    authors = Author.objects.filter(bookauthor__book__title='天文选集')
    print(authors)

image-20240717143258343

(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`
FROM `index_author` 
INNER JOIN `index_bookauthor` 
ON (`index_author`.`id` = `index_bookauthor`.`author_id`)
INNER JOIN `index_book` 
ON (`index_bookauthor`.`book_id` = `index_book`.`id`) 
WHERE `index_book`.`title` = '天文选集'
LIMIT 21; 
args=('天文选集',)
如果省略掉中间表部分会报错, 例如
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 查询书籍天文选集的所有作者
    authors = Author.objects.filter(book__title='天文选集')
    print(authors)
  

image-20240717160947039

django.core.exceptions.FieldError: Cannot resolve keyword 'book' into field. 
Choices are: age, authordetail, authored_books, bookauthor, id, name

django.core.exceptions.FieldError: 无法将关键字'book'解析到字段中.
选项包括: age, authordetail, authored_books, bookauthor, id, name.

其中:
authordetail是作者详情表.
bookauthor是关联表.
authored_books是书籍表的外键字段设置的名称, 这个名称被related_name参数控制.


总结: 子表可以通过外键字连接主表, 主表可以通过主表的名称连接主表(对于多对多, 则使用外键的字段反向连接的名称连接子表).
使用外键的字段反向连接的名称连接子表, :  Author.objects.filter(authored_books__title='天文选集').
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 查询书籍天文选集的所有作者
    authors = Author.objects.filter(authored_books__title='天文选集')
    print(authors)

image-20240717163129839

(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author` 
INNER JOIN `index_bookauthor`
ON (`index_author`.`id` = `index_bookauthor`.`author_id`)
INNER JOIN `index_book` ON (`index_bookauthor`.`book_id` = `index_book`.`id`)
WHERE `index_book`.`title` = '天文选集'
LIMIT 21;
args=('天文选集',)

在进行多表查询时, 需要注意查询的性能. 避免使用过多的JOIN操作, 合理使用索引和查询优化技术, 以提高查询效率.
5.2.2.3 获取关联字段
如果只需要关联对象的某些字段(而不是完整的对象), 可以使用values或values_list方法来减少返回的数据量.
这些方法允许你指定一个或多个字段, Django将只返回这些字段的值.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 获取所有书籍及其作者名称的列表
    book_author_pairs = Book.objects.values('title', 'author__name')
    for pair in book_author_pairs:
        # 注意: 这将为每个作者名称创建一个条目, 如果一本书有多个作者
        print(pair['title'], pair['author__name'])

    # 使用 values_list 来获取元组列表
    # book_author_tuples = Book.objects.values_list('title', 'author__name')
    # for title, author_name in book_author_tuples:
    #     print(title, author_name)

2024-07-22_144139

# ORM日志:
(0.000)
SELECT `index_book`.`title`, `index_author`.`name`
FROM `index_book`
LEFT OUTER JOIN `index_bookauthor`  # 左外连接, 左表的记录为基础表, 右表的记录为补充表 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`) 
LEFT OUTER JOIN `index_author`  # 左外连接
ON (`index_bookauthor`.`author_id` = `index_author`.`id`);
args=()

5.3 联表查询

Django ORM为联表查询提供了两个高性能查询方法: select_related和prefetch_related.
它们的主要目的是减少数据库查询的次数, 尤其是在处理具有外键关系的模型时.
然而, 它们各自的工作原理和适用场景有所不同.
5.3.1 select_related方法
select_related方法: 用于对一对一(OneToOneField)和外键(ForeignKey, 一对多)关系进行预加载.
select_related的参数可以是外键字段的名称, 反向查询时则使用外键字段related_name设置的名称(默认为关联表的模型名称小写).
在查询一个模型时, 如果这个模型中包含了指向其他模型的外键, 并且需要在后续操作中频繁访问这些外键关联的对象, 
那么使用select_related可以减少数据库查询的次数.

工作原理: select_related通过JOIN语句进行拼表操作, 并一次性获取关联对象的数据.
返回值: 返回值是当前模型的查询集, 但每个查询集实例都包含了其相关联表中的实例的数据.

使用场景: 当需要访问关联对象的数据时, 并且关联的数据量不是非常大, 这时使用select_related可以显著提高性能.
5.4.1.1 正向使用
示例: 现在有两个模型: Author(书籍表)和AuthorDetail(作者详情表), 其中AuthorDetail通过OneToOneField关联到Author. 
访问子模型的字段可以直接通过点操作符(.)来访问这些实例的属性(即模型的字段), : author_detail.phone.
访问主实例的数据需要通过外键字段进行访问, : author_detail.author.name.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import AuthorDetail

    # 将作者详情表和作者表的拼接结果返回
    author_details = AuthorDetail.objects.select_related('author').all()

    for author_detail in author_details:
        # 可以获取两张表中的所有字段
        print(author_detail.author.name, author_detail.phone)  # 这里不会触发额外的数据库查询

image-20240717185517133

(0.000) 
SELECT `index_authordetail`.`id`, `index_authordetail`.`phone`, `index_authordetail`.`addr`, `index_authordetail`.`author_id`, `index_author`.`id`, `index_author`.`name`, `index_author`.`age`
FROM `index_authordetail` 
INNER JOIN `index_author`
ON (`index_authordetail`.`author_id` = `index_author`.`id`); 
args=()
5.4.1.2 反向使用
Author主模型拼接子模型, 在使用select_related方法时提供子模型的小写名称即可.
访问主模型的字段可以直接通过点操作符(.)来访问这些实例的属性(即模型的字段), : author.phone.
访问主实例的数据需要通过子表名称进行访问, : uthor.authordetail.phone.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 参数为关联表的模型名称小写
    authors = Author.objects.select_related('authordetail').all()

    for author in authors:
        print(author.name, author.authordetail.phone)

image-20240717193940961

(0.000) 
SELECT 
`index_author`.`id`, `index_author`.`name`, `index_author`.`age`, `index_authordetail`.`id`, `index_authordetail`.`phone`, `index_authordetail`.`addr`, `index_authordetail`.`author_id` 
FROM `index_author` 
LEFT OUTER JOIN `index_authordetail`  # 这里反向查询使用的是左连接
ON (`index_author`.`id` = `index_authordetail`.`author_id`); 
args=()
如果参数写错了会提示可用的参数.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 参数为关联表的模型名称原名, 会报错
    authors = Author.objects.select_related('AuthorDetail').all()

    for author in authors:
        print(author.name, author.authordetail.phone)

image-20240717195408386

django.core.exceptions.FieldError:
select_related中给出的字段名称无效: 'AuthorDetail' 选项包括: authordetail(模型名称小写).
可以通过外键字段的related_name参数设置名称(默认名称是模型名称小写, 使用related_name参数定义名称后, 默认就是无存在了!).
# index.models.py
# 作者详情表
class AuthorDetail(models.Model):
    # 作者id(自动创建)
    # 作者手机号码
    phone = models.CharField(max_length=11, verbose_name='作者手机号码')
    # 作者地址
    addr = models.CharField(max_length=64, verbose_name='作者地址')
    # 外键, 绑定作者表id, 并设置级联删除
    author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE,
                                  related_name='author_detail')

    # 对象的字符串表现形式
    def __str__(self):
        return f'{self.id}'

image-20240717195700657

# 生成迁移文件
PS D:\MyDjango> python manage.py makemigrations   
Migrations for 'index':
  index\migrations\0003_alter_authordetail_author.py
    - Alter field author on authordetail

# 执行迁移
PS D:\MyDjango> python manage.py migrate  
...
Author主模型拼接子模型, 在使用select_related方法时使用外键参数related_name设置的名称.
访问相关表字段的时候也是使用related_name设置的名称.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 参数为关联表的模型名称原名, 会报错
    authors = Author.objects.select_related('author_detail').all()

    for author in authors:
        print(author.name, author.author_detail.phone)

image-20240717200445833

# ORM日志:
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`, `index_authordetail`.`id`, `index_authordetail`.`phone`, `index_authordetail`.`addr`, `index_authordetail`.`author_id`
FROM `index_author` 
LEFT OUTER JOIN `index_authordetail` 
ON (`index_author`.`id` = `index_authordetail`.`author_id`); 
args=()
5.3.2 prefetch_related方法
prefetch_related方法: 用于对多对多(ManyToManyField)和反向外键(, 当从被关联模型访问关联模型时)关系进行预加载.
与select_related类似, prefetch_related也旨在减少数据库查询的次数, 但处理多对多关系时, 它使用不同的策略.

工作原理: prefetch_related通过执行两个单独的查询来完成工作: 
一个查询用于获取主对象列表, 另一个查询用于获取所有相关的对象, 并将它们分组以匹配主对象.
然后, Django将这些相关对象(可能有多个)附加到主对象上, 这样就可以像访问普通属性一样访问它们, 而无需触发额外的数据库查询.

使用场景:当需要访问大量关联对象, 或者关联的数据量很大时, prefetch_related是更好的选择.
因为它避免了使用JOIN, 这可能在处理大量数据时导致性能问题.
5.3.2.1 正向使用
现在有两个模型: Book(书籍表)和Author(作者表), 他们之间是多对多的关系靠关联进行联系. Book通过ManyToManyField关联到Author. 
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    books = Book.objects.prefetch_related('author').all()

    for book in books:
        # 因为是多对多关系, 所以 book.authors是一个QuerySet
        authors_names = [author.name for author in book.author.all()]
        print(f"书籍<<{book.title}>>的作者有: {', '.join(authors_names)}")

image-20240718000549743

# 获取所有书籍的记录
(0.000) 
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id` 
FROM `index_book`; args=()

# # 先将作者表与关联表拼接, 后面通过书籍id过滤出作者记录
(0.000) 
SELECT (`index_bookauthor`.`book_id`) AS `_prefetch_related_val_book_id`,
`index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author` 
INNER JOIN`index_bookauthor`
ON (`index_author`.`id` = `index_bookauthor`.`author_id`) 
WHERE `index_bookauthor`.`book_id` IN (1, 2, 3, 4, 5);
args=(1, 2, 3, 4, 5)
5.3.2.2 反向使用
反向查询使用prefetch_related方法, prefetch_related方法的参数使用外键related_name参数设置的名称.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author
    
    authors = Author.objects.prefetch_related('authored_books').all()
    
    for author in authors:
        # 因为是多对多关系, 所以 author.authored_books是一个QuerySet
        book_name = [book.title for book in author.authored_books.all()]
        print(f"作者<<{author.name}>> 著作的书籍: {', '.join(book_name)}")
        

image-20240718002647163

# ORM日志
# 获取所有作者表的记录
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` FROM `index_author`; 
args=()

# 先将书籍表与关联表拼接, 后面通过作者id过滤出书籍记录
(0.016)
SELECT (`index_bookauthor`.`author_id`) AS `_prefetch_related_val_author_id`, `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id`
FROM `index_book` 
INNER JOIN `index_bookauthor` 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
WHERE `index_bookauthor`.`author_id` IN (1, 2, 3); 
args=(1, 2, 3)

5.4 分组查询

在Django ORM中, 分组查询是通过annotate()方法实现的, 它允许对查询集(QuerySet)中的对象进行分组,
并对每个分组应用聚合函数来计算统计值. 
5.4.1 聚合函数
在Django ORM中, 聚合函数允许对数据库中的数据进行统计计算, : 计算总数, 平均值, 最大值, 最小值以及总和等.
这些聚合函数是在django.db.models模块中定义的, 并且可以通过annotate()或aggregate()方法在查询集(QuerySet)上调用.

以下是一些常用的Django ORM聚合函数:
* 1. Count: 计算数量. 
     这是最常用的聚合函数之一, 用于计算某个字段在查询集中的非空值的数量.
* 2. Sum: 计算总和.
     这个函数用于计算查询集中某个数值字段的总和.
* 3. Avg: 计算平均值.
     这个函数用于计算查询集中某个数值字段的平均值.
* 4. Max  Min: 分别用于计算查询集中某个字段的最大值和最小值.

使用聚合函数时, 通常会指定一个或多个字段作为这些聚合操作的基础, 这些字段是直接定义在模型中的字段.
5.4.2 aggregate方法
aggregate是Django ORM中的一个方法, 用于对查询集(QuerySet)中的一组值执行计算, 并返回一个包含聚合值的字典.
这个方法允许开发者对数据库中的数据进行汇总计算, : 计算平均值, 最大值, 最小值, 总和以及计数等,
而无需在Python代码中手动处理这些数据.

aggregate方法的特点:
- 聚合函数: aggregate方法可以与Django提供的聚合函数一起使用.
- 返回类型: aggregate方法返回一个字典, 其中包含了聚合计算的结果.
  字典的键是聚合函数的名称(或者自定义的别名), 值是计算得到的聚合值.
- 终止子句: aggregate是QuerySet的一个终止子句, 意味着在调用aggregate方法后, 将不能再对返回的字典进行进一步的链式查询操作.
使用示例: Book模型中包含price字段, 想要计算所有书籍的平均价格:
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    from django.db.models import Avg

    # 计算所有书籍的平均价格
    average_price = Book.objects.all().aggregate(Avg('price'))
    print(average_price)  # {'price__avg': Decimal('69.596000')}

image-20240722201915651

# ORM日志:
(0.000) SELECT AVG(`index_book`.`price`) AS `price__avg` FROM `index_book`; args=()
在这个例子中, aggregate方法接受一个聚合函数Avg('price')作为参数, 并返回了一个包含平均价格(键为'price__avg')的字典.
自定义键名: 如果不喜欢Django自动生成的键名(: 'price__avg'), 可以将聚合函数的结果赋值给一个变量名, 这个变量名也就是别名.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    from django.db.models import Avg

    # 计算所有书籍的平均价格
    average_price = Book.objects.all().aggregate(avg_price=Avg('price'))
    print(average_price)  # {'avg_price': Decimal('69.596000')}

image-20240722201738411

# ORM日志:
(0.000) SELECT AVG(`index_book`.`price`) AS `price__avg` FROM `index_book`; args=()
5.4.3 annotate方法
annotate()方法是Django ORM中一个非常强大的工具, 它允许在查询集(QuerySet)上添加额外的注解(Annotation),
这些注解可以是聚合函数的结果, 也可以是表达式的结果.
这些注解在查询执行时动态地计算, 并且可以作为查询集的每个对象的一个属性来访问.
现在有两个模型: Book(书籍表)和Author(作者表), 他们之间是多对多的关系靠关联进行联系. Book通过ManyToManyField关联到Author. 
# index的test.py (正向查询)
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    from django.db.models import Count

    # 使用注释来为每个Book对象添加作者数量.
    # annotate: 为书籍记录添加一个author_count的字段, 值为Count('author').
    # Count('author'): 使用Count函数统计外键author对象的数量.
    book_author_count = Book.objects.annotate(author_count=Count('author'))

    # 遍历查询集并访问book_count注解
    for book in book_author_count:
        print(f"{book.title}{book.author_count}个作者.")

image-20240722155838160

在这个例子中, Count('author')是一个聚合函数, 它计算每个书籍(Bokk 对象)通过其author外键关系所关联的Author对象的数量.
然后, 这个数量被作为author_count注解添加到每个Book对象上.
# ORM日志:
(0.000) 
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id`, 
COUNT(`index_bookauthor`.`author_id`) AS `author_count`  # 设置别名
FROM `index_book`
LEFT OUTER JOIN `index_bookauthor`  # 左外连接, author_count的值可以为0
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
GROUP BY `index_book`.`id`  # 默认按id分组
ORDER BY NULL;  # 排序为NULL
args=()
反向查询使用外键related_name参数设置的名称.
# index的test.py (反向查询)
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    from django.db.models import Count

    # 使用注释来为每个author对象添加著作的书籍数量.
    author_book_count = Author.objects.annotate(book_count=Count('authored_books'))

    # 遍历查询集并访问book_count注解
    for author in author_book_count:
        print(f"{author.name}{author.book_count}本著作.")

image-20240723004159338

在这个例子中, Count('book')是一个聚合函数, 它计算每个作者(Author 对象)通过其book外键关系所关联的Book对象的数量.
然后, 这个数量被作为book_count注解添加到每个Author对象上.
# ORM日志:
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`,
COUNT(`index_bookauthor`.`book_id`) AS `book_count`  # 设置别名
FROM `index_author` LEFT OUTER JOIN `index_bookauthor`  # 左外连接, book_count的值可以为0
ON (`index_author`.`id` = `index_bookauthor`.`author_id`)
GROUP BY `index_author`.`id`  # 默认按id分组
ORDER BY NULL;  # 排序为NULL
args=()
在同一个查询中使用多个聚合函数, 并为它们分别添加注解.
# index的test.py
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    from django.db.models import Count, Avg

    # 计算每个作者的书籍数量和平均价格
    authors_with_stats = Author.objects.annotate(
        book_count=Count('authored_books'),  # 统计书籍数量: 通过外键获取书籍的数量
        avg_price=Avg('authored_books__price'),  # 计算平均价格: 通过外键获取书籍的价格
    )

    # 遍历查询集并访问注解
    for author in authors_with_stats:
        print(f"{author.name}{author.book_count}本书, 平均价格为{author.avg_price:.2f}.")

image-20240723004247026

在这个例子中, Count('book')  Avg('book__price') 都是聚合函数, 它们分别为每个Author对象添加了book_count和avg_price注解.
# ORM日志:
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`,
COUNT(`index_bookauthor`.`book_id`) AS `book_count`,
AVG(`index_book`.`price`) AS `avg_price` 
FROM `index_author`
LEFT OUTER JOIN `index_bookauthor`
ON (`index_author`.`id` = `index_bookauthor`.`author_id`) 
LEFT OUTER JOIN `index_book` 
ON (`index_bookauthor`.`book_id` = `index_book`.`id`) 
GROUP BY `index_author`.`id`
ORDER BY NULL; args=()
在上例的基础上加上排序.
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    from django.db.models import Count, Avg

    # 计算每个作者的书籍数量和平均价格
    authors_with_stats = Author.objects.annotate(
        book_count=Count('authored_books'),  # 统计书籍数量: 通过外键获取书籍的数量
        avg_price=Avg('authored_books__price'),  # 计算平均价格: 通过外键获取书籍的价格
    ).order_by('avg_price')  # 按平均价格升序

    # 遍历查询集并访问注解
    for author in authors_with_stats:
        print(f"{author.name}{author.book_count}本书, 平均价格为{author.avg_price:.2f}.")
        

image-20240723004314556

# ORM日志;
(0.000) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`, 
COUNT(`index_bookauthor`.`book_id`) AS `book_count`, 
AVG(`index_book`.`price`) AS `avg_price` 
FROM `index_author`
LEFT OUTER JOIN `index_bookauthor`
ON (`index_author`.`id` = `index_bookauthor`.`author_id`)
LEFT OUTER JOIN `index_book` 
ON (`index_bookauthor`.`book_id` = `index_book`.`id`)
GROUP BY `index_author`.`id` 
ORDER BY `avg_price` ASC;  # 排序
args=()
5.4.4 分组查询
在Django ORM中, 通过annotate()方法与values()或values_list()结合使用来指定分组依据.
- values()方法允许指定一个或多个字段, Django会根据这些字段的值对查询集进行分组, 并对每个分组应用聚合函数.
- values_list()方法在功能上与values()类似, 但它返回的是一个元组列表而不是字典列表.
  然而, 在进行聚合操作时, 通常不需要直接使用values_list(), 因为聚合操作的结果本身就是一种聚合后的数据表示(如计数, 总和等),
  它们自然是以字典(或类似字典的结构)的形式返回的, 以便能够方便地访问每个字段的值.
  
* 分组后显示的字段名称为方法的参数名.
* 必须先分组再使用annotate, 否则就不是分组查询.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    from django.db.models import Count

    # 要按出版年份分组, 并计算每个年份的书籍数量
    # publish_date__year(从字段中读取年份的值)
    book_counts_by_year = Book.objects.values('publish_date__year').annotate(count=Count('id'))

    for i in book_counts_by_year:
        print(i)

image-20240723004343035

# ORM日志:
(0.000) 
SELECT EXTRACT(YEAR FROM `index_book`.`publish_date`),  # 字段的名称为values方法的参数名称
COUNT(`index_book`.`id`) AS `count`
FROM `index_book` 
GROUP BY EXTRACT(YEAR FROM `index_book`.`publish_date`)  # 获取年份信息
ORDER BY NULL;
args=()
示例中, publish_date__year 是双下划线(__)查询语法, 用于从DateField或DateTimeField类型的字段中提取年份部分.

5.5 子查询

子查询是一个在另一个查询内部执行的查询, 它可以返回单个值, 一行或多行多列.
5.5.1 OuterRef函数
OuterRef函数是Django ORM中的一个工具, 用于在子查询中引用外部查询(即包含该子查询的查询)的字段.
示例: 找出所有至少出版了一本书的作者.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef

    # 所有至少出版了一本书的作者
    authors_with_books = Author.objects.filter(authored_books__isnull=False).distinct()
    print(authors_with_books)

    # 使用OuterRef函数
    authors_with_books = Author.objects.filter(
        pk__in=Book.objects.filter(
            author=OuterRef('pk')  #
        ).values('author')
    ).distinct()
    print(authors_with_books)

image-20240723141717000

# ORM日志:
(0.000) 
SELECT DISTINCT 
`index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author`
INNER JOIN `index_bookauthor` 
ON (`index_author`.`id` = `index_bookauthor`.`author_id`) 
WHERE `index_bookauthor`.`book_id` IS NOT NULL
LIMIT 21;
args=()

# 子查询
(0.000) 
SELECT DISTINCT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` 
FROM `index_author` 
WHERE `index_author`.`id` 
IN (
    SELECT U1.`author_id`
    FROM `index_book` U0
    INNER JOIN `index_bookauthor` U1
    ON (U0.`id` = U1.`book_id`) 
    WHERE U1.`author_id` = `index_author`.`id`
)
LIMIT 21; args=()

5.5.2 Subquery函数
Subquery函数允许在Django ORM的查询中嵌入一个子查询, 这个子查询可以是一个完整的查询集(QuerySet),
但会被限制为只返回一个值(对于标量子查询)或一行(对于行子查询).
Subquery的使用相对复杂, 并且通常需要与OuterRef()一起使用.

当创建一个Subquery时, 它本身是一个独立的查询集(QuerySet), 但可能希望这个查询集能够基于外部查询的某些字段来过滤或计算.
这时, OuterRef()就派上用场了, 可以将OuterRef('field_name')传递给子查询的过滤条件, 以引用外部查询中名为field_name的字段.

注意事项: 
- 确保子查询只返回一个值. 
  在使用Subquery时, 需要确保子查询对于外部查询中的每一行都只返回一个值.
  这通常通过values()与切片([:1])来确保单个字段信息返回.
- 虽然OuterRef和Subquery提供了强大的查询能力, 但它们也可能导致查询性能下降, 特别是在处理大量数据时.
示例1: 找出所有至少出版了一本书的作者.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef

    author_id = Book.objects.filter(
        author=OuterRef('pk')  # 引用外部查询的主键
    )

    # 使用OuterRef函数
    authors_with_books = Author.objects.filter(
        pk__in=author_id  # 使用子查询
    ).distinct()
    print(authors_with_books)

image-20240723142917394

(0.000) 
SELECT DISTINCT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`
FROM `index_author`
WHERE `index_author`.`id` IN (
    SELECT U0.`id` 
    FROM `index_book` U0 
    INNER JOIN `index_bookauthor` U1 
    ON (U0.`id` = U1.`book_id`) 
    WHERE U1.`author_id` = `index_author`.`id`
)
LIMIT 21; args=()
Subquery函数会限制只返回一个值或一行, 否则会报错, 例如:
django.db.utils.OperationalError: (1241, 'Operand should contain 1 column(s)')
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef, Subquery

    author_id = Book.objects.filter(
        author=OuterRef('pk')  # 引用外部查询的主键
    )

    # 使用OuterRef函数
    authors_with_books = Author.objects.filter(
        pk__in=Subquery(author_id)  # 使用子查询
    ).distinct()
    print(authors_with_books)

image-20240723143153601

在使用Subquery时, 需要确保子查询对于外部查询中的每一行都只返回一个值.
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef, Subquery

    author_id = Book.objects.filter(
        author=OuterRef('pk')  # 引用外部查询的主键
    ).values('author')[:1]  # 限制只返回一个字段和一条数据

    # 使用OuterRef函数
    authors_with_books = Author.objects.filter(
        pk__in=Subquery(author_id)  # 使用子查询
    ).distinct()
    print(authors_with_books)

image-20240723163438741

在学习MySQL的时候就知道了, 单层子查询的LIMIT不能搭配IN操作符.

25.6 子查询limit和in

当只需要检查存在性而不是检索实际数据时, 可以使用EXISTS子句重写查询的方法.
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef, Exists

    # 使用 EXISTS 子句查询有书籍的作者
    authors_with_books = Author.objects.annotate(
        has_books=Exists(
            Book.objects.filter(author=OuterRef('pk'))
        )
    ).filter(has_books=True).distinct()

    print(authors_with_books)

image-20240723155848928

(0.000)
SELECT DISTINCT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`,
EXISTS(
    SELECT (1) AS `a` 
    FROM `index_book` U0 
    INNER JOIN `index_bookauthor` U1
    ON (U0.`id` = U1.`book_id`) 
    WHERE U1.`author_id` = `index_author`.`id`
    LIMIT 1
) AS `has_books`  # 为author表添加额外字段
FROM `index_author`
WHERE EXISTS(
    SELECT (1) AS `a`
    FROM `index_book` U0 
    INNER JOIN `index_bookauthor` U1 
    ON (U0.`id` = U1.`book_id`) 
    WHERE U1.`author_id` = `index_author`.`id`
    LIMIT 1
) LIMIT 21;
args=()
针对'LIMIT & IN/ALL/ANY/SOME subquery'这种情况还可以省略切片的限制.
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, Book

    from django.db.models import OuterRef, Subquery

    author_id = Book.objects.filter(
        author=OuterRef('pk')  # 引用外部查询的主键
    ).values('author')  # 限制只返回一个字段

    # 使用OuterRef函数
    authors_with_books = Author.objects.filter(
        pk__in=Subquery(author_id)  # 使用子查询
    ).distinct()
    print(authors_with_books)

image-20240723144338558

# ORM日志:
(0.000) 
SELECT DISTINCT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`
FROM `index_author`
WHERE `index_author`.`id`
IN (
    SELECT U1.`author_id` FROM `index_book` U0
    INNER JOIN `index_bookauthor` U1
    ON (U0.`id` = U1.`book_id`)
    WHERE U1.`author_id` = `index_author`.`id`
)
LIMIT 21; args=()

示例2: 使用子查询来获取每个作者的最贵书籍.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    from django.db.models import OuterRef, Subquery
    from django.db.models import Max

    # 获取每个作者最贵的书籍的价格
    max_price_per_author = Book.objects.filter(
        author=OuterRef('pk')  # 先设置过滤的外部查询关联字段
    ).values('author').annotate(  # 后通过外键分组计算最高价格
        max_price=Max('price')
    ).values('max_price')[:1]  # 限制只返回一个字段和一条数据

    authors_with_max_price = Author.objects.annotate(
        max_book_price=Subquery(max_price_per_author)
    )

    for author in authors_with_max_price:
        print(f"{author.name}: {author.max_book_price}")

image-20240723004537454

第一个values是分组, 第二个values是限制返回的信息只有一个字段, 必须限制.
(0.015) 
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`, 
(   # 书籍表拼接关联表
    SELECT MAX(U0.`price`) AS `max_price` 
    FROM `index_book` U0  # 设置别名
    INNER JOIN `index_bookauthor` U1  # 设置别名
    ON (U0.`id` = U1.`book_id`)  # 拼表条件
    WHERE U1.`author_id` = `index_author`.`id`  # 使用外部表的字段进行过滤
    GROUP BY U1.`author_id`  # 分组
    ORDER BY NULL 
    LIMIT 1
) AS `max_book_price` 
FROM `index_author`; 
args=()
示例 3: 使用子查询来过滤结果, 获取价格高于所有作者最贵书籍平均价格的书籍.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author
    from django.db.models import Avg, Subquery, OuterRef

    # 首先计算每个作者最贵书籍的平均价格
    average_max_price = Author.objects.annotate(
        max_book_price=Subquery(
            Book.objects.filter(author=OuterRef('pk')).values('author').annotate(
                max_price=Max('price')
            ).values('max_price')[:1]
        )
    ).aggregate(avg_max_price=Avg('max_book_price'))['avg_max_price']
    print(f'贵书籍的平均价格为:{average_max_price}')

    # 然后使用这个平均值来过滤书籍
    expensive_books = Book.objects.filter(price__gt=average_max_price)

    for book in expensive_books:
        print(f"{book.title}: {book.price}")


image-20240723004842768

# ORM日志:
(0.000) 
SELECT AVG(`max_book_price`) 
FROM (
    SELECT (
        SELECT MAX(U0.`price`) AS `max_price` 
            FROM `index_book` U0 
            INNER JOIN `index_bookauthor` U1
            ON (U0.`id` = U1.`book_id`) 
            WHERE U1.`author_id` = `index_author`.`id`
            GROUP BY U1.`author_id`
            ORDER BY NULL
            LIMIT 1
           ) AS `max_book_price`
    FROM `index_author`
) subquery; 
args=()

(0.000) 
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id`
FROM `index_book` 
WHERE `index_book`.`price` > 83.953333; 
args=(Decimal('83.953333'),)

6. 修改记录

6.1 修改实例对象数据

修改模型实例的数据通常与单表操作非常相似. 
当修改一个模型实例的属性时, 实际上是在内存中操作这个对象的副本.
要将这些更改持久化到数据库中, 需要调用一个方法(: save())来提交这些更改即可.
操作步骤: 1.查找需要修改的实例对象, 2.修改实例对象, 3.持久化.
示例: 为作者'kid'的所有书籍打9折进行销售.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book
    from django.db.models import F

    # 获取kid的所有书籍
    res = Book.objects.filter(author__name='kid').update(price=F('price') * 0.9)
    print(res)  # 修改的记录数量

image-20240723210047849

# ORM日志:
(0.000)
SELECT `index_book`.`id` FROM `index_book` 
INNER JOIN `index_bookauthor` 
ON (`index_book`.`id` = `index_bookauthor`.`book_id`) 
INNER JOIN `index_author`
ON (`index_bookauthor`.`author_id` = `index_author`.`id`)
WHERE `index_author`.`name` = 'kid'; 
args=('kid',)

(0.016)
UPDATE `index_book` SET `price` = (`index_book`.`price` * 0.9e0) 
WHERE `index_book`.`id` IN (2, 3, 4, 5); 
args=(0.9, 2, 3, 4, 5)

6.2 修改绑定对象

6.2.1 一对一修改绑定对象
修改一对一的外键关联: 通常先需要获取到次表的实例, 然后通过这个实例来访问和修改其关联的主表实例.
现在有两个模型, Author(作者表, )和AuthorDetail(作者详情表, ), 其中AuthorDetail通过OneToOneField与Author关联.
先获取次表(AuthorDetail)的实例, 在修改外键字段关联的主表实例, 由于是一对一关系, 不能使用已经被绑定用户的实例.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author, AuthorDetail

    # 获取一个已经存在的 AuthorDetail 实例
    author_detail_1 = AuthorDetail.objects.get(id=1)
    # 由于是一对一关系, 不能使用已经存在的用户, 只能创建一个新的的 Author 实例
    new_author = Author.objects.create(name='blue', age=22)

    # 修改author_detail_1信息的绑定者
    author_detail_1.author = new_author
    res = author_detail_1.save()  # 保存
    print(res)

image-20240723165732170

# ORM日志:
(0.015)
SELECT `index_authordetail`.`id`, `index_authordetail`.`phone`, `index_authordetail`.`addr`, `index_authordetail`.`author_id`
FROM `index_authordetail`
WHERE `index_authordetail`.`id` = 1 
LIMIT 21;
args=(1,)

(0.000)
INSERT INTO `index_author` (`name`, `age`) VALUES ('blue', 22);
args=['blue', 22]

(0.016)
UPDATE `index_authordetail` SET `phone` = '110', `addr` = '北京', `author_id` = 4
WHERE `index_authordetail`.`id` = 1; 
args=('110', '北京', 4, 1)

6.2.2 一对多修改绑定对象
修改一对多的外键关联: 通常会在'多'端的对象上修改外键字段, 指向'一'端的对象.
这里有一些步骤和示例来说明如何进行:
* 1. 获取'一'端的实例: 首先, 需要获取到与多个'多'端实例相关联的那个'一'端的实例.
* 2. 获取'多'端的实例: 然后, 可以使用这个'一'端的实例来查询所有与之相关联的'多'端的实例.
* 3. '多'端的修改外键: 最后, 可以遍历这些查询到的'多'端实例, 并对它们进行修改.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Publisher

    # Publisher是'一'端, book是'多'端
    # 获取'一'端的实例
    publisher_1 = Publisher.objects.get(id=1)
    # 获取'多'端的实例
    book = Book.objects.filter(title='天文选集').first()
    # 在'多'端修改外键值
    book.publisher = publisher_1  # 直接在Book对象上设置author属性
    book.save()

202407230058147

# ORM日志:
(0.000) 
SELECT `index_publisher`.`id`, `index_publisher`.`name`, `index_publisher`.`addr`, `index_publisher`.`email`
FROM `index_publisher` 
WHERE `index_publisher`.`id` = 1
LIMIT 21;
args=(1,)

(0.000)
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id` 
FROM `index_book`
WHERE `index_book`.`title` = '天文选集'
ORDER BY `index_book`.`id` ASC
LIMIT 1;
args=('天文选集',)

(0.015) 
UPDATE `index_book` SET `title` = '天文选集', `price` = '74.10', `publish_date` = '2024-05-05', `publisher_id` = 1 WHERE `index_book`.`id` = 5; 
args=('天文选集', '74.10', '2024-05-05', 1, 5)
6.2.3 多对多修改绑定对象
对于对于多对多关系ORM提供了set()方法用于替换一个对象与另一个对象集合之间的所有关联.
它会先删除当前对象与旧关联对象之间的所有关系, 然后建立与新对象集合之间的关系.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    # 创建一本书
    book = Book.objects.get(id=1)
    print(book.author.all())  # 作者1, 2

    # # 获取一些作者
    author2 = Author.objects.get(id=2)
    author3 = Author.objects.get(id=3)
    new_authors = [author2, author3]

    # 使用 set 方法更新这本书的作者列表
    book.author.set(new_authors)

    book = Book.objects.get(id=1)
    print(book.author.all())   # 作者2, 3

image-20240723201306301

# ORM日志:
(0.015) 
DELETE FROM `index_bookauthor`  # 作者2在新列表中不会删除
WHERE (
    `index_bookauthor`.`book_id` = 1 
       AND `index_bookauthor`.`author_id`
       IN (1)
      ); args=(1, 1)
       
...
(0.000)
INSERT INTO `index_bookauthor` (`book_id`, `author_id`)
VALUES (1, 2);  # 作者2已经存在, 插入也不会报错
args=(1, 2) 
在上面的例子中, set()方法接收一个可迭代对象(如列表或查询集), 其中包含要与之建立关系的Author实例.
它会自动处理数据库的更新, 包括删除任何现有的且不在新列表中的关系, 并添加任何新的关系.
这是处理多对多关系时非常有用的一个功能, 因为它允许你以原子方式更新整个关系集, 而不需要手动删除旧的关系和添加新的关系.

7. 删除记录

删除记录时, 可以直接调用对象的delete()方法.
需要注意的是, 如果数据库配置了外键约束(如ON DELETE CASCADE), 删除某个记录可能会导致与之相关联的其他记录也被删除.
作者详情表()  一对一   作者表(): 删除作者表中的记录, 作者详情表中的关联数据一同删除.
出版社()     一对多   书籍表(): 删除出版社表中的记录, 书籍表表中的关联数据一同删除(可能是多条数据).
作者表()     多对多   书籍表(): 删除关联表中的记录, 作者表与书籍表的记录默认不删除.
由于多个表之间存在关联, 所有从多对多的关联表开始入手, 否则删除作者表, 出版社表, 都无法成功.

7.1 多对多删除记录

在Django ORM中, 处理多对多关系的删除主要是通过操作模型实例来完成的, 而不是直接操作数据库表.
使用remove(), clear()和set()方法可以灵活地管理多对多关系中的关联.
7.1.1 remove()方法
remove()方法: 用于从多对多关系中移除一个特定的关联对象.
注意: remove()接受一个或多个模型实例作为参数, 而不是ID.
如果尝试移除一个不存在的关联, Django会抛出一个DoesNotExist异常.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    # 获取天文选集的书籍对象
    book = Book.objects.filter(title='天文选集').first()
    # 查看天文选集的所有作者
    print(book.author.all())  # <QuerySet [<Author: kid>, <Author: qq>, <Author: qaq>]>

    # 获取作者实例
    author = Author.objects.filter(name='kid').first()
    print(author)  # kid

    # 将kid作者从天文选集的作者列表中删除
    res = book.author.remove(author)
    print(res)  # None

    # 再次查看天文选集的所有作者
    print(book.author.all())  # <QuerySet [<Author: qq>, <Author: qaq>]>

image-20240723232834111

# ORM日志:
(0.000) 
DELETE FROM `index_bookauthor` 
WHERE (
    `index_bookauthor`.`book_id` = 5 AND `index_bookauthor`.`author_id` IN (1)
);
args=(5, 1)
删除记录同样可以使用反向查询的方式.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    # 获取作者kid的实例
    author = Author.objects.filter(name='kid').first()
    print(author)  # kid

    # 查询作者kid著作的所有书籍
    print(author.authored_books.all())  # <QuerySet [<Book: 文学经典>, <Book: 科幻小说集>, <Book: 玄幻小说集>]>

    # 获取文学经典的书籍对象
    book = Book.objects.filter(title='文学经典').first()
    print(book)

    # 将文学经典从作者的著作列表中移除
    res = author.authored_books.remove(book)
    print(res)  # None

    # 查看文学经典的作者列表
    print(book.author.all())  # <QuerySet [<Author: qq>]>

image-20240723235852645

# ORM日志:
(0.000)
DELETE FROM `index_bookauthor` 
WHERE (
    `index_bookauthor`.`author_id` = 1 
    AND `index_bookauthor`.`book_id` IN (2)
); 
args=(1, 2)
7.1.2 clear()方法
clear()方法: 用于删除一个实例的所有多对多关联.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book, Author

    # 获取天文选集的书籍对象
    book = Book.objects.filter(title='天文选集').first()
    # 查看天文选集的所有作者
    print(book.author.all())  # <QuerySet [<Author: qq>, <Author: qaq>]>

    # 将所有作者从天文选集的作者列表中删除
    res = book.author.clear()
    print(res)  # None

    # 查看天文选集的所有作者
    print(book.author.all())  # <QuerySet []>

2024-07-24_001041

# ORM日志:
(0.000)
DELETE FROM `index_bookauthor` WHERE `index_bookauthor`.`book_id` = 5; 
args=(5,)
清除记录同样可以使用反向查询的方式.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 获取作者kid的实例
    author = Author.objects.filter(name='kid').first()
    # 查询作者kid著作的所有书籍
    print(author.authored_books.all())  # <QuerySet [<Book: 科幻小说集>, <Book: 玄幻小说集>]>

    # 清除作者的所有著作
    res = author.authored_books.clear()
    print(res)

    # 查询作者kid著作的所有书籍
    print(author.authored_books.all())  # <QuerySet []>

image-20240724002406774

# ORM日志:
(0.000) 
DELETE FROM `index_bookauthor` WHERE `index_bookauthor`.`author_id` = 1;
args=(1,)
7.1.3 set()方法
虽然set()方法本身不直接用于删除关联, 但可以通过传递一个空的可迭代对象(如空列表)来间接地清除所有关联.
* set()方法会先删除现有的关联, 再添加新的关联.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Book

    # 获取编程基础的书籍对象
    book = Book.objects.filter(title='编程基础').first()
    # 查看编程基础的所有作者
    print(book.author.all())  # <QuerySet [<Author: qaq>, <Author: qq>]>

    # 将所有作者从编程基础的作者列表中删除
    res = book.author.set([])
    print(res)  # None

    # 查看编程基础的所有作者
    print(book.author.all())  # <QuerySet []>

image-20240724003255950

# ORM日志:
(0.000) 
DELETE FROM `index_bookauthor` 
WHERE (
    `index_bookauthor`.`book_id` = 1 AND `index_bookauthor`.`author_id` IN (2, 3)
); 
args=(1, 2, 3)
set()方法清除记录同样可以使用反向查询的方式.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 获取作者qq的实例
    author = Author.objects.filter(name='qq').first()
    # 查询作者qq著作的所有书籍
    print(author.authored_books.all())  # <QuerySet [<Book: 文学经典>, <Book: 科幻小说集>, <Book: 玄幻小说集>]>

    # 清除作者的所有著作
    res = author.authored_books.set([])
    print(res)

    # 查询作者qq著作的所有书籍
    print(author.authored_books.all())  # <QuerySet []>

image-20240724004309702

# ORM日志:
(0.000)
DELETE FROM `index_bookauthor` WHERE (
    `index_bookauthor`.`author_id` = 2 
    AND `index_bookauthor`.`book_id` IN (2, 3, 4)
);
args=(2, 2, 3, 4)

7.2 一对一删除记录

一对一关系通常通过OneToOneField实现.
如果设置了on_delete参数为models.CASCADE, 则当删除主记录时, 相关联的记录也会被自动删除.
* 多对多中关联表中的关联的实例不能直接删除, 需要先删除关联. 一对一不会出现这样的约束, 先删谁都可以.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author
    # 删除author时, 相关联的AuthorDetail也会被删除
    Author.objects.filter(name='blue').delete()

2024-07-23_224418

# ORM日志:
(0.000) DELETE FROM `index_authordetail` WHERE `index_authordetail`.`author_id` IN (4); args=(4,)
(0.000) DELETE FROM `index_author` WHERE `index_author`.`author_id` IN (4); args=(4,)
删除次表的记录对主表没有任何影响.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import AuthorDetail

    # 删除authorDetail时, 相关联的author不受影响
    AuthorDetail.objects.filter(id=2).delete()

image-20240724010052629

# ORM日志:
(0.000) DELETE FROM `index_authordetail` WHERE `index_authordetail`.`id` = 2; args=(2,)
反向查询获取到的实例是次表的, 删除次表的记录对主表没有任何影响.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Author

    # 获取qaq的实例
    author = Author.objects.filter(id=3).first()
    print(author)
    # 反向查询删除次表的数据, 主表不受影响
    author.author_detail.delete()

image-20240724012353713

# ORM日志:
(0.000) DELETE FROM `index_authordetail` WHERE `index_authordetail`.`id` IN (3); args=(3,)

7.2 一对多删除记录

一对多关系通常通过ForeignKey实现.
如果设置了on_delete参数为models.CASCADE, 则当删除主记录时, 相关联的记录也会被自动删除.
删除次表的记录对主表没有任何影响.
# index的test.py 
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')  # !!!需要修改为自己的配置文件

    import django

    django.setup()
    from index.models import Publisher
    # 删除publisher时, 相关联的book也会被删除
    res = Publisher.objects.filter(name='上海出版社').delete()
    print(res)  # (3, {'index.Book': 2, 'index.Publisher': 1})  删除了三条记录Book表中两条, Publisher表中一条

image-20240724013711399

# ORM日志:
(0.000) DELETE FROM `index_book` WHERE `index_book`.`publisher_id` IN (1); args=(1,)
(0.000) DELETE FROM `index_publisher` WHERE `index_publisher`.`id` IN (1); args=(1,)
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值