柚子快報邀請碼778899分享:26.8 Django多表操作
柚子快報邀請碼778899分享:26.8 Django多表操作
1. 表關(guān)系
表關(guān)系在數(shù)據(jù)庫中指的是表與表之間的連接和依賴關(guān)系.
這種關(guān)系定義了數(shù)據(jù)如何在不同的表之間進(jìn)行交互和關(guān)聯(lián), 是實(shí)現(xiàn)數(shù)據(jù)一致性和完整性的重要手段.
1.1 關(guān)系分類
多表關(guān)系在數(shù)據(jù)庫中通常分為以下幾種類型:
* 1. 一對一(One-to-One)關(guān)系: 在數(shù)據(jù)庫中, 這種關(guān)系通常通過外鍵約束和唯一約束來實(shí)現(xiàn).
在ORM中, 這可以通過在一個模型類中嵌入另一個模型類的實(shí)例或者通過引用(如外鍵)來實(shí)現(xiàn).
* 2. 一對多/多對一(One-to-Many / Many-to-One)關(guān)系:
這是一個常見的數(shù)據(jù)庫關(guān)系, 其中一個表中的行可以關(guān)聯(lián)到另一個表中的多行.
在ORM中, 這通常通過在'一'的模型類中定義一個指向'多'的模型類的集合屬性來實(shí)現(xiàn).
例如, 一個用戶可以有多個訂單, 所以在用戶模型中會有一個訂單集合.
* 3. 多對多(Many-to-Many)關(guān)系: 這種關(guān)系更復(fù)雜, 因為它允許兩個表中的多行相互關(guān)聯(lián).
在數(shù)據(jù)庫中, 這通常通過一個額外的關(guān)聯(lián)表來實(shí)現(xiàn), 該表包含兩個外鍵, 分別指向兩個原始表的主鍵.
在ORM中, 這通常通過定義一個關(guān)聯(lián)表模型(盡管在某些ORM框架中可能是隱式的)
并在兩個模型類中各添加一個指向?qū)Ψ侥P偷募蠈傩詠韺?shí)現(xiàn).
在ORM中, 多表關(guān)系允許以面向?qū)ο蟮姆绞教幚頂?shù)據(jù)庫中的復(fù)雜數(shù)據(jù)關(guān)聯(lián).
1.2 外鍵字段
在Django的ORM中, OneToOneField, ForeignKey, 和ManyToManyField字段分別用于實(shí)現(xiàn)數(shù)據(jù)庫中的一對一, 一對多, 和多對多關(guān)系.
這些字段在定義時, Django會為它們設(shè)置一些默認(rèn)行為, 以確保數(shù)據(jù)的一致性和完整性.
以下是這三個字段的一些默認(rèn)行為:
* OneToOneField(一對一字段).
- 默認(rèn)行為: OneToOneField本質(zhì)上是一個特殊的ForeignKey(外鍵),
它要求每個關(guān)聯(lián)對象只能對應(yīng)一個主對象(反之亦然), 即它們之間是一對一的關(guān)系.
- 數(shù)據(jù)庫層面: 在數(shù)據(jù)庫中, OneToOneField通常在關(guān)聯(lián)表上創(chuàng)建一個外鍵約束, 并且這個外鍵字段被設(shè)置為唯一(UNIQUE),
以確保每個關(guān)聯(lián)對象(即, 在關(guān)聯(lián)表中的每一行)只能關(guān)聯(lián)到一個主對象(即, 被OneToOneField引用的那個模型的對象(行, 記錄)).
- Django ORM: 在Django的ORM中, 可以像訪問模型的普通字段一樣訪問通過OneToOneField關(guān)聯(lián)的對象.
Django會自動處理這個關(guān)聯(lián), 并在需要時執(zhí)行JOIN操作.
OneToOneField繼承自ForeignKey, 但具有一些額外的限制(在數(shù)據(jù)庫中創(chuàng)建一個唯一的外鍵約束)來確保關(guān)系的唯一性.
* ForeignKey(外鍵).
- 默認(rèn)行為: ForeignKey用于表示一對多的關(guān)系, 即一個對象可以關(guān)聯(lián)到多個其他對象.
- 數(shù)據(jù)庫層面: 在數(shù)據(jù)庫中, ForeignKey創(chuàng)建一個外鍵約束, 這個約束確保了外鍵列的值必須是它所引用的表中的某個主鍵值,
或者為NULL(如果外鍵字段被設(shè)置為null=True).
- Django ORM: 在Django的ORM中, 可以通過關(guān)聯(lián)管理器(related manager)來訪問與當(dāng)前對象關(guān)聯(lián)的其他對象.
例如, 如果有一個Author模型和一個Book模型, 其中Book通過ForeignKey關(guān)聯(lián)到Author,
那么可以通過author.book_set.all()來獲取該作者的所有書籍.
* ManyToManyField(多對多字段).
- 默認(rèn)行為: ManyToManyField用于表示多對多的關(guān)系, 即兩個對象集合之間可以相互關(guān)聯(lián).
- 數(shù)據(jù)庫層面: Django會自動為ManyToManyField創(chuàng)建一個關(guān)聯(lián)表(也稱為中間表或連接表),
這個表包含兩個外鍵字段, 分別指向兩個關(guān)聯(lián)模型的主鍵.
Django還會為這個表創(chuàng)建一個唯一約束, 以確保不會有重復(fù)的關(guān)聯(lián)記錄(除非明確指定了through參數(shù)來使用一個自定義的中間模型).
- Django ORM: 在Django的ORM中, 可以像訪問普通字段的集合一樣訪問通過ManyToManyField關(guān)聯(lián)的對象.
Django提供了豐富的API來查詢和操作這些關(guān)聯(lián)對象, 包括添加, 刪除和查詢關(guān)聯(lián)對象.
每種字段類型都有其特定的使用參數(shù), 下面將分別介紹這些參數(shù):
* OneToOneField和ForeignKey的主要參數(shù):
- to: 必需參數(shù), 指定關(guān)聯(lián)的模型類, 或模型類的字符串.
如果關(guān)聯(lián)的模型先定義了則可以使用模型, 例: to=model, 如果關(guān)聯(lián)的模型在后面定義了可以使用使用字符串形式, 例: to='models'.
- on_delete: 當(dāng)被關(guān)聯(lián)的對象被刪除時, Django將如何處理這個字段.
- related_name: 定義了從關(guān)聯(lián)模型到當(dāng)前模型的反向關(guān)系名.
如果不設(shè)置, Django將使用模型名加下劃線和小寫的模型名作為默認(rèn)的反向關(guān)系名.
- related_query_name: 用于定義反向查詢時使用的名稱.
- limit_choices_to: 用于在Django管理后臺或表單中限制可選擇的對象.
- to_field: 默認(rèn)情況下, Django會使用關(guān)聯(lián)模型的主鍵作為關(guān)聯(lián)字段.
to_field允許指定關(guān)聯(lián)模型中的另一個字段.
- parent_link: 在使用多表繼承時, 這個參數(shù)用于指向父模型中的OneToOneField, 表示繼承關(guān)系.
- db_constraint: 這個參數(shù)控制著是否在數(shù)據(jù)庫中為這一對一關(guān)系創(chuàng)建外鍵約束. 默認(rèn)為True, 創(chuàng)建.
外鍵約束是數(shù)據(jù)庫層面用來保證數(shù)據(jù)完整性的一個機(jī)制, 它可以確保關(guān)聯(lián)的數(shù)據(jù)在數(shù)據(jù)庫中始終保持一致和有效.
- null: 表示該字段在數(shù)據(jù)庫中可以為NULL. 對于ForeignKey, 通常設(shè)置為null=True以允許空值.
- blank: 表示該字段在表單中可以為空.
- verbose_name: 是一個可選的字段參數(shù), 它用于為模型或模型字段提供一個人類可讀的名稱.
這個名稱在Django的管理后臺(admin site)中特別有用, 因為它會用于表單標(biāo)簽, 列標(biāo)題和其他需要顯示字段名稱的地方.
ManyToManyField字段
* ManyToManyField的主要參數(shù):
- to: 必需參數(shù),指定關(guān)聯(lián)的模型類.
- limit_choices_to: 同F(xiàn)oreignKey.
- related_name: 同F(xiàn)oreignKey.
- related_query_name: 同F(xiàn)oreignKey.
- symmetrical: 當(dāng)模型自引用時(即模型指向自己), 需要設(shè)置為False, 因為Django默認(rèn)認(rèn)為關(guān)系是對稱的.
對于非自引用關(guān)系, 通常不需要設(shè)置(棄用, through替代symmetrical).
- through: 允許指定一個中間模型來管理多對多關(guān)系. 如果設(shè)置了此參數(shù), Django將不會創(chuàng)建默認(rèn)的中間表.
- through_fields: 當(dāng)使用自定義中間模型時, 需要指定哪兩個字段作為多對多關(guān)系的連接字段.
如果不設(shè)置該參數(shù), Django通常能夠自動處理并創(chuàng)建必要的中間表和字段.
但是在一些特定情況下, 如中間模型包含多個外鍵或遞歸關(guān)系時, 明確指定through_fields是必要的.
- db_table: 當(dāng)不使用自定義中間模型時, 可以指定數(shù)據(jù)庫表的名稱.
注意: ManyToManyField不支持on_delete參數(shù), 因為多對多關(guān)系涉及兩個模型集合之間的關(guān)聯(lián), 而不是單個對象之間的關(guān)聯(lián),
所以不存在'刪除被關(guān)聯(lián)對象時如何處理'的問題.
但是, 如果通過自定義中間模型(through)來實(shí)現(xiàn)多對多關(guān)系, 可以在中間模型中使用ForeignKey并為其設(shè)置on_delete參數(shù).
1.3 級聯(lián)設(shè)置
在Django的ORM中, 當(dāng)定義模型之間的關(guān)系(如ForeignKey, OneToOneField, ManyToManyField)時,
on_delete參數(shù)是一個非常重要的選項, 它定義了當(dāng)關(guān)聯(lián)的對象被刪除時, 應(yīng)該如何處理依賴于該對象的數(shù)據(jù)庫條目.
on_delete參數(shù)可以設(shè)置為多種不同的值, 以實(shí)現(xiàn)不同的行為.
以下是幾種on_delete的常見設(shè)置:
* 1. models.CASCADE: 級聯(lián)刪除.
如果主對象被刪除, 那么所有關(guān)聯(lián)的外鍵對象也會被刪除. 這是默認(rèn)行為.
* 2. models.PROTECT: 阻止刪除.
如果嘗試刪除的對象具有外鍵關(guān)系, 并且這些關(guān)系被設(shè)置為PROTECT, 則刪除操作會拋出一個ProtectedError異常, 阻止刪除.
* 3. SET_NULL: 設(shè)置為NULL. 僅當(dāng)外鍵字段允許NULL值時有效.
如果主對象被刪除, 所有關(guān)聯(lián)的外鍵字段都會被設(shè)置為NULL.
* 4. SET_DEFAULT: 設(shè)置為默認(rèn)值, 僅當(dāng)外鍵字段有默認(rèn)值時有效.
如果主對象被刪除, 所有關(guān)聯(lián)的外鍵字段都會被設(shè)置為它們的默認(rèn)值.
* 5. SET(): 設(shè)置為特定值或調(diào)用一個可調(diào)用對象.
可以傳遞一個值或一個可調(diào)用的對象(如函數(shù)), 當(dāng)主對象被刪除時, 所有關(guān)聯(lián)的外鍵字段都會被設(shè)置為這個值或該可調(diào)用對象返回的值.
* 6. DO_NOTHING: 什么都不做.
如果數(shù)據(jù)庫級別支持(如PostgreSQL的ON DELETE NO ACTION),
則當(dāng)主對象被刪除時, 外鍵字段不會被自動修改, 但這可能會導(dǎo)致數(shù)據(jù)庫完整性錯誤.
注意: ORM中沒有為MySQL提供級聯(lián)更新.
2. 模型創(chuàng)建
2.1 子表和主表的概念
子表和主表(也稱為從表, 父表或引用表和被引用表)的概念是這種關(guān)系的一個重要方面.
* 主表(Parent Table 或 Referenced Table): 主表是含有另一個表(子表)通過外鍵引用的數(shù)據(jù)的表.
它通常包含一個或多個主鍵字段, 這些字段的值在表中是唯一的, 用于唯一標(biāo)識表中的每一行.
主表中的這些主鍵值可能被子表中的外鍵字段所引用.
* 子表(Child Table 或 Referencing Table): 子表是包含外鍵字段的表, 該外鍵字段用于引用主表中的主鍵字段或唯一字段.
子表中的外鍵字段可以包含主表中主鍵字段的重復(fù)值, 這表示子表中的多行可以'指向'或'關(guān)聯(lián)'到主表中的同一行.
小提示:
在子表中, 同一個被綁定表的ID(即外鍵所引用的ID)可以出現(xiàn)多次,
這體現(xiàn)了'多'的關(guān)系, 即多個子表記錄可以關(guān)聯(lián)到主表的同一個記錄上.
相反, 被綁定表(主表)中的每條記錄(包括其ID)是唯一的, 不會重復(fù),
這體現(xiàn)了'一'的關(guān)系, 即每個主表記錄都是獨(dú)立的, 不與其他主表記錄重復(fù).
配置了外鍵約束(如ON DELETE CASCADE), 在刪除關(guān)聯(lián)的父記錄時, 所有相關(guān)的子記錄也會被自動刪除.
但是, 在修改外鍵時, 這種約束通常不會影響, 除非顯式地刪除了子記錄或父記錄.
2.2 數(shù)據(jù)庫與日志配置
# settings.py 配置文件
# 數(shù)據(jù)庫配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 更改數(shù)據(jù)庫引擎為MySQL
'NAME': 'MyDjango', # 數(shù)據(jù)庫名
'USER': 'root', # 數(shù)據(jù)庫用戶名
'PASSWORD': '123456', # 數(shù)據(jù)庫密碼
'HOST': 'localhost', # 數(shù)據(jù)庫主機(jī)地址
'PORT': '3306', # MySQL的默認(rèn)端口是3306
# 以下參數(shù)不是必需的, 但可能對于某些情況很有用
'OPTIONS': {
'sql_mode': 'traditional', # 設(shè)置SQL模式
'charset': 'utf8mb4', # 推薦使用utf8mb4字符集來支持完整的Unicode字符集, 包括表情符號
},
}
}
# ORM日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
# 處理程序
'handlers': {
# 定義一個日志處理程序, 日志級別為DEBUG, 信息在終端展示
'console': {
'level': 'DEBUG', # 日志級別
'class': 'logging.StreamHandler', # 消息發(fā)送到標(biāo)準(zhǔn)輸出
},
},
# 日志記錄器
'loggers': {
# Django數(shù)據(jù)庫后端的日志記錄
'django.db.backends': {
'handlers': ['console'], # 使用的日志處理程序(前面定義的console)
'propagate': True,
'level': 'DEBUG',
},
}
}
2.3 一對一關(guān)系
在Django ORM框架中, 使用OneToOneField定義模型之間一對一關(guān)系的字段類型.
通過在一個模型類中嵌入另一個模型類的實(shí)例(即'組合')或通過定義一個明確的一對一關(guān)系映射來實(shí)現(xiàn).
這種關(guān)系意味著兩個模型之間的記錄是互相關(guān)聯(lián)的, 且這種關(guān)聯(lián)是唯一的.
可以在兩張表中的任意一張表中建立關(guān)聯(lián)字段, 但推薦外鍵建立在次表上.
示例: 定義兩個Django模型: Author(作者, 主表)和AuthorDetail(作者詳情表, 次表), 一個作者詳情信息只能對應(yīng)一個作者.
# 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字段(自動添加)
# 手機(jī)號碼
phone = models.BigIntegerField(verbose_name='手機(jī)號碼')
# 作者地址
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
# 生成遷移文件
PS D:\MyDjango> Python manage.py makemigrations
...
Migrations for 'index':
index\migrations\0001_initial.py
- Create model AuthorDetail
- Create model Author
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
...
Applying index.0001_initial...
...
# 先創(chuàng)建主表
(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
# 后創(chuàng)建次表
(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=()
# 沒有級聯(lián)設(shè)置的語句
使用Navicat工具的逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)系.
在Django框架中, 在模型中定義一個外鍵(ForeignKey)字段時, 默認(rèn)情況下, Django會自動為這個字段添加一個后綴_id.
這樣做的目的是為了在數(shù)據(jù)庫層面創(chuàng)建一個與關(guān)聯(lián)表的主鍵相對應(yīng)的字段.
如果顯式地將其命名為author_detail_id, 那么創(chuàng)建表格的時候則為author_detail_id_id.
2.4 一對多關(guān)系
在Django ORM中, 通過ForeignKey字段來實(shí)現(xiàn)一對多關(guān)系.
其中一個模型對象(通常稱為'一'方)可以關(guān)聯(lián)到多個其他模型對象(通常稱為'多'方).
這種關(guān)系在數(shù)據(jù)庫中通常通過外鍵實(shí)現(xiàn), 但在ORM框架中, 它會被抽象化為模型之間的關(guān)聯(lián).
ForeignKey字段被添加到'多'方模型中, 指向'一'方模型的主鍵.
這意味著'多'方模型中的每個實(shí)例都可以關(guān)聯(lián)到'一'方模型中的一個特定實(shí)例,
但'一'方模型的一個實(shí)例可以關(guān)聯(lián)到'多'方模型中的零個, 一個或多個實(shí)例.
示例: 定義兩個Django模型: Book(書籍)和Publish(出版社), 其中一個出版社可以出版多本書:
# index的models.py
from django.db import models
# 出版社表(被關(guān)聯(lián)表)
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
# 書籍表(關(guān)聯(lián)表)
class Book(models.Model):
# id字段(自動添加)
# 書籍名稱
title = models.CharField(max_length=32, verbose_name='書籍名稱')
# 外鍵: 關(guān)聯(lián)出版社表的主鍵
Publish = models.ForeignKey(Publish, on_delete=models.CASCADE, verbose_name='出版社id')
def __str__(self):
return self.title
# 刪除之前的遷移文件和數(shù)據(jù)庫文件
# 生成遷移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
index\migrations\0001_initial.py
- Create model Publish
- Create model Book
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
Applying index.0001_initial
...
# 先創(chuàng)建被關(guān)聯(lián)表
(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 # 默認(rèn)情況下, Django的EmailField使用VARCHAR254
);
args=None
# 后創(chuàng)建關(guān)聯(lián)表
(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=()
# 沒有級聯(lián)設(shè)置的語句
使用Navicat工具的逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)系.
2.5 多對多關(guān)系
在Django中, 多對多(Many-to-Many)關(guān)系是通過在模型(Model)之間使用ManyToManyField字段來定義的.
Django會自動為這種關(guān)系創(chuàng)建一個中間表(也稱為關(guān)聯(lián)表或連接表), 用于存儲兩個模型實(shí)例之間的關(guān)系.
也可以手動定義這個中間表, 以便在這個關(guān)系上添加額外的字段或約束.
多對多關(guān)系外鍵可以定義在任一模型上, 或者可以使用第三個模型(通過through參數(shù))來明確指定多對多關(guān)系的中間表.
實(shí)例: 定義兩個模型: 作者表(Author), Book(書籍表), 一個作者可以寫多本書, 一個書也可以有多個作者.
2.5.1 自動創(chuàng)建關(guān)聯(lián)表
# 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='書籍名稱')
# 書籍價格(共八位, 小數(shù)占兩位)
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
# 刪除之前的遷移文件和數(shù)據(jù)庫文件
# 生成遷移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
index\migrations\0001_initial.py
- Create model Author
- Create model Book
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
Applying index.0001_initial...
# 創(chuàng)建作者表
(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
# 創(chuàng)建書籍表
(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
# 創(chuàng)建關(guān)聯(lián)表
(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
# 為外鍵字段設(shè)置唯一
(0.000)
ALTER TABLE `index_book_publish`
ADD CONSTRAINT `index_book_publish_book_id_author_id_46ca4a02_uniq`
UNIQUE (`book_id`, `author_id`);
args=()
# 在第三張?zhí)砑油怄I綁定書籍表
(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=()
# 在第三張?zhí)砑油怄I綁定作者表
(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工具的逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)系.
使用ManyToManyField定義一個模型字段時, 如: author = models.ManyToManyField(to='Author'),
這個字段確實(shí)不會在直接關(guān)聯(lián)的表中(比如Book表)創(chuàng)建一個物理列.
相反, Django會創(chuàng)建一個額外的表(中間表)來存儲這種多對多關(guān)系.
這個中間表通常會有兩個外鍵列, 分別指向兩個相關(guān)模型(在這個例子中是Book和Author)的主鍵.
但是后續(xù)的可以通過Book實(shí)例上的author屬性來添加, 刪除或查詢與書籍相關(guān)聯(lián)的作者.
對于ManyToManyField, Django會自動為創(chuàng)建一個中間表來管理兩個模型之間的多對多關(guān)系.
表名是由Django根據(jù)兩個模型的名稱和它們所屬的應(yīng)用的名稱來生成的.
生成的表名通常會是
當(dāng)使用ManyToManyField自動創(chuàng)建的第三張表(中間表)時, 存在一些限制, 特別是關(guān)于級聯(lián)刪除(CASCADE)的設(shè)置.
Django的ManyToManyField默認(rèn)不提供直接的級聯(lián)刪除設(shè)置(也不需要).
2.5.2 手動動創(chuàng)建關(guān)聯(lián)表
如果需要控制級聯(lián)刪除或更新行為, 可以通過自定義中間表來實(shí)現(xiàn).
在自定義中間表中, 可以定義Django模型的外鍵, 并顯式地設(shè)置on_delete參數(shù)來控制.
注意: 由于ManyToManyField的特殊性, 通常不會直接在自定義中間表的外鍵上設(shè)置級聯(lián)刪除, 因為這可能會導(dǎo)致意外的數(shù)據(jù)丟失.
相反, 可能會在自定義的模型方法中處理這些邏輯, 或者在保存或刪除模型實(shí)例時,
使用Django的信號(如pre_delete或post_save)來執(zhí)行自定義的清理或更新操作.
在數(shù)據(jù)庫設(shè)計中, 多對多的關(guān)系可以通過兩個一對多的關(guān)系來實(shí)現(xiàn).
這種實(shí)現(xiàn)方式通常涉及引入一個額外的表, 用于存儲兩個主表之間的多對多關(guān)系.
這種方式可以在關(guān)聯(lián)表中定義一個額外的字段.
手動動創(chuàng)建關(guān)聯(lián)表特點(diǎn):
- 完全手動: 開發(fā)者需要完全手動定義三張表的結(jié)構(gòu), 并在代碼中顯式地處理所有與多對多關(guān)系相關(guān)的操作, 如添加, 刪除和查詢關(guān)聯(lián)記錄.
- 無ORM支持: 由于中間表不是通過ManyToManyField與through參數(shù)指定的,
Django的ORM不會自動為這種手動創(chuàng)建的多對多關(guān)系提供快捷方法或跨表查詢支持.
默認(rèn)創(chuàng)建的中間表遵循:
而Python中類的定義通常是大駝峰名稱, 例'BookAuthor'的格式(推薦).
實(shí)例: 定義三個模型: Book(書籍表), Author(作者表), BookAuthor(關(guān)聯(lián)表, 先不使用下劃線分隔看看效果),
在關(guān)聯(lián)表中設(shè)置外鍵分別關(guān)聯(lián)書籍表與作者表.
# index的models.py
from django.db import models
# 書籍表
class Book(models.Model):
# id字段(自動添加)
# 書籍名稱
title = models.CharField(max_length=32, verbose_name='書籍名稱')
# 書籍價格(共八位, 小數(shù)占兩位)
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
# 定義關(guān)聯(lián)表
class BookAuthor(models.Model):
# id字段(自動添加)
# 外鍵(關(guān)聯(lián)書籍表的id)
book = models.ForeignKey(Book, on_delete=models.DO_NOTHING) # 不推薦設(shè)置on_delete
# 外鍵(關(guān)聯(lián)作者表的id)
author = models.ForeignKey(Author, on_delete=models.DO_NOTHING)
# 記錄創(chuàng)建時間
creation_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.book} --> {self.author}'
# 刪除之前的遷移文件和數(shù)據(jù)庫文件
# 生效遷移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
index\migrations\0001_initial.py
- Create model Author
- Create model Book
- Create model BookAuthor
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
Applying index.0001_initial...
# 創(chuàng)建作者表
(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
# 創(chuàng)建書籍表
(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
# 創(chuàng)建關(guān)聯(lián)表
(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
# 為關(guān)聯(lián)表設(shè)置外鍵關(guān)聯(lián)作者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=()
# 為關(guān)聯(lián)表設(shè)置外鍵關(guān)聯(lián)書籍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工具的逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)系.
2.5.3 半自動創(chuàng)建關(guān)聯(lián)表
半自動方式指的是通過Django的ManyToManyField字段的through參數(shù)指定一個自定義的中間表(第三張表)來管理兩個模型之間的多對多關(guān)系.
半自動創(chuàng)建關(guān)聯(lián)表特點(diǎn):
- 部分自動化: Django仍然會處理一些與多對多關(guān)系相關(guān)的基本操作, 如添加, 刪除和查詢關(guān)聯(lián)記錄.
- 自定義中間表: 開發(fā)者可以自定義中間表, 并在其中添加額外的字段以存儲關(guān)于兩個模型之間關(guān)系的額外信息.
- ORM支持: 由于通過ManyToManyField與through參數(shù)指定了自定義的中間表, 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='書籍名稱')
# 書籍價格(共八位, 小數(shù)占兩位)
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='書籍價格')
# 書籍出版時間
publish_date = models.DateTimeField(auto_now_add=True, verbose_name='書籍出版時間')
# 定義多對多關(guān)系, 通過自定義的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
# 定義關(guān)聯(lián)表
class BookAuthor(models.Model):
# id字段(自動添加)
# 外鍵(關(guān)聯(lián)書籍表的id)
book = models.ForeignKey(Book, on_delete=models.DO_NOTHING) # 不推薦設(shè)置on_delete
# 外鍵(關(guān)聯(lián)作者表的id)
author = models.ForeignKey(Author, on_delete=models.DO_NOTHING)
# 記錄創(chuàng)建時間
creation_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.book} --> {self.author}'
在這個例子中, Book模型通過authors字段與Author模型建立了多對多關(guān)系, 并通過through='BookAuthor'指定了自定義的中間BookAuthor.
這樣, Django就會使用BookAuthor表來存儲Book和Author之間的關(guān)聯(lián)信息, 并允許在這個表中添加任何額外的字段.
# 刪除之前的遷移文件和數(shù)據(jù)庫文件
# 生效遷移文件
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字段
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
Applying index.0001_initial...
# 創(chuàng)建作者表
(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
# 創(chuàng)建書籍表
(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
# 創(chuàng)建關(guān)聯(lián)表
(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
# 為關(guān)聯(lián)表設(shè)置外鍵關(guān)聯(lián)作者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=()
# 為關(guān)聯(lián)表設(shè)置外鍵關(guān)聯(lián)書籍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工具的逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)系.
注意: 在Book模型中定義了一個ManyToManyField字段來關(guān)聯(lián)Author模型時, 這個字段并不會直接在數(shù)據(jù)庫的book表中創(chuàng)建一個外鍵列.
但是后續(xù)的可以通過Book實(shí)例上的authors屬性來添加, 刪除或查詢與書籍相關(guān)聯(lián)的作者.
2.6 db_constraint 參數(shù)
在Django的ORM中, OneToOneField是一個特殊的字段類型, 用于定義兩個模型之間的一對一關(guān)系.
這種關(guān)系意味著對于第一個模型中的每一個實(shí)例, 第二個模型中最多只能有一個實(shí)例與之關(guān)聯(lián), 反之亦然.
OneToOneField字段有一個可選的參數(shù)db_constraint, 這個參數(shù)控制著是否在數(shù)據(jù)庫中為這一對一關(guān)系創(chuàng)建外鍵約束.
外鍵約束是數(shù)據(jù)庫層面用來保證數(shù)據(jù)完整性的一個機(jī)制, 它可以確保關(guān)聯(lián)的數(shù)據(jù)在數(shù)據(jù)庫中始終保持一致和有效.
db_constraint參數(shù):
- 默認(rèn)值: True.
- 可選值: True或False.
當(dāng)db_constraint=True時(默認(rèn)值), Django會在數(shù)據(jù)庫中為這一對一關(guān)系創(chuàng)建一個外鍵約束.
這意味著, 如果嘗試將一個OneToOneField字段指向一個不存在的關(guān)聯(lián)模型實(shí)例, 數(shù)據(jù)庫將拒絕這個操作,從而保證了數(shù)據(jù)的完整性和一致性.
當(dāng)db_constraint=False時, Django不會在數(shù)據(jù)庫中創(chuàng)建這個外鍵約束.
這意味著, 可以在數(shù)據(jù)庫中為OneToOneField字段設(shè)置一個不存在的關(guān)聯(lián)模型實(shí)例的ID, 而不會觸發(fā)數(shù)據(jù)庫的錯誤.
這在某些特定的, 需要繞過Django ORM來操作數(shù)據(jù)庫的場景下可能是有用的, 但通常不推薦這樣做, 因為它會破壞數(shù)據(jù)的完整性和一致性.
使用場景:
- 保持?jǐn)?shù)據(jù)一致性: 在大多數(shù)情況下, 應(yīng)該保持db_constraint=True(默認(rèn)值), 以確保數(shù)據(jù)庫的數(shù)據(jù)完整性和一致性.
- 特殊需求: 如果需要在Django ORM之外以某種方式操作數(shù)據(jù)庫, 并且需要繞過外鍵約束, 那么可以將db_constraint設(shè)置為False.
但請注意, 這樣做需要非常小心, 因為它可能會引入難以調(diào)試的數(shù)據(jù)一致性問題.
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之間有一個一對一關(guān)系, 但由于db_constraint=False, 數(shù)據(jù)庫層面不會強(qiáng)制這一關(guān)系的外鍵約束.
這意味著, 可以在數(shù)據(jù)庫中為Restaurant實(shí)例的place字段設(shè)置一個不存在的Place實(shí)例的ID, 而不會觸發(fā)數(shù)據(jù)庫錯誤.
然而, 這通常不是一個好的做法, 因為它可能會破壞數(shù)據(jù)的完整性和一致性.
2.7 db_table參數(shù)
默認(rèn)情況下, ManyToManyField字段會根據(jù)模型的名稱和應(yīng)用的名稱自動生成一個表名.
但是, 如果想要使用不同的表名, 可以通過db_table參數(shù)來自定義表名.
db_table參數(shù)接受一個字符串值, 該字符串是你想要用于數(shù)據(jù)庫表的名稱.
請注意, 這個名稱應(yīng)該與數(shù)據(jù)庫后端支持的命名規(guī)則相匹配.
* 自定義的模型可以通過定義Meta類的db_table屬性設(shè)置表名.
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='書籍名稱')
# 書籍價格(共八位, 小數(shù)占兩位)
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'
# 刪除之前的遷移文件和數(shù)據(jù)庫文件
# 生效遷移文件
PS D:\MyDjango> Python manage.py makemigrations
Migrations for 'index':
index\migrations\0001_initial.py
- Create model Author
- Create model Book
# 執(zhí)行遷移
PS D:\MyDjango> Python manage.py migrate
Applying index.0001_initial...
# 創(chuàng)建作者表
(0.015)
CREATE TABLE `author` (
`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY,
`name` varchar(32) NOT NULL,
`age` integer NOT NULL
);
args=None
# 創(chuàng)建書籍表
(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
# 創(chuàng)建關(guān)聯(lián)表
(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
# 為關(guān)聯(lián)表添加唯一約束
(0.000)
ALTER TABLE `book_to_author`
ADD CONSTRAINT `book_to_author_book_id_author_id_f80a4f35_uniq`
UNIQUE (`book_id`, `author_id`);
args=()
# 創(chuàng)建外鍵關(guān)聯(lián)書籍表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=()
# 創(chuàng)建外鍵關(guān)聯(lián)書籍表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工具查看模型表.
3. 多表操作策略
3.1 操作描述
在數(shù)據(jù)庫和ORM框架中, 處理一對多, 一對一時, 和單表的增刪改查(CRUD)操作一樣, 單獨(dú)為多對多提供一些特殊方法.
詳細(xì)描述如下:
* 1. 在一對多關(guān)系中, 通常有一個'父'表和一個或多個'子'表.
例如, 一個作者(Author)可以有多本書(Book).
在這種關(guān)系中, 增刪改操作通常會直接針對各個表進(jìn)行, 但在關(guān)聯(lián)數(shù)據(jù)時需要特別注意.
- 增加: 在子表中添加新記錄時, 需要指定外鍵字段以指向父表中的正確記錄.
- 刪除: 刪除父表中的記錄時, 需要決定如何處理子表中的相關(guān)記錄(級聯(lián)刪除, 設(shè)置為NULL或拋出錯誤).
- 修改: 修改父表或子表中的記錄時, 通常不需要特殊的ORM方法, 除非需要更新外鍵指向.
* 2. 一對一關(guān)系通常用于需要將大量數(shù)據(jù)分割到不同表中的情況, 但兩個表之間的記錄是緊密相關(guān)的.
例如, 用戶(User)和他們的用戶詳情(UserProfile)可能是一對一關(guān)系.
增加, 刪除, 修改: 這些操作與一對多關(guān)系類似, 但更側(cè)重于確保兩個表之間的數(shù)據(jù)一致性.
* 3. 多對多關(guān)系表示兩個表中的記錄可以相互關(guān)聯(lián).
例如, 一個作者可以寫多本書, 而一本書也可以被多個作者共同著作.
在ORM中, 這種關(guān)系通常通過中間表(也稱為關(guān)聯(lián)表或聯(lián)結(jié)表)來實(shí)現(xiàn), 還提供了特定的方法來簡化關(guān)系的管理, 如下:
- .add(): 向多對多關(guān)系中添加一個新的關(guān)聯(lián). 例如, 將一個作者添加到一本書的作者列表中.
添加關(guān)聯(lián): Book.author.add(author) # 將某個作者添加到書籍的作者列表中.
- .set([]): 設(shè)置多對多關(guān)系的完整列表, 這通常會先清除現(xiàn)有的關(guān)聯(lián), 然后添加新的關(guān)聯(lián).
設(shè)置新的關(guān)聯(lián)列表: Book.Book.set([author1, author2]) # 設(shè)置書籍的作者列表為作者1和作者2.
- .remove(): 從多對多關(guān)系中移除一個特定的關(guān)聯(lián).
移除關(guān)聯(lián): Book.author.remove(author) # 從書籍的作者列表中移除某個作者.
- .clear(): 清除多對多關(guān)系中的所有關(guān)聯(lián).
清除所有關(guān)聯(lián): Book.author.clear() # 移除書籍的作者列表中的所有作者.
在ORM中進(jìn)行多表操作時, 需要注意事務(wù)的管理. 確保在需要時開啟事務(wù), 并在操作完成后提交事務(wù), 以保證數(shù)據(jù)的一致性和完整性.
3.2 測試模型創(chuàng)建
下面有一張'書籍詳細(xì)信息匯總表', 信息如下:
書籍名稱價格出版時間出版社名稱出版社郵箱出版社地址作者名稱作者年齡作者手機(jī)號碼作者地址編程基礎(chǔ)50.002022-01-01上海出版社123@qq.com上海kid18110北京文學(xué)經(jīng)典65.002021-05-10上海出版社123@qq.com上海kid18110北京文學(xué)經(jīng)典65.002021-05-10上海出版社123@qq.com上海qq19112上海科幻小說集70.002023-03-15北京出版社456@qq.com北京kid18110北京科幻小說集70.002023-03-15北京出版社456@qq.com北京qq19112上海
由于'文學(xué)經(jīng)典'和'科幻小說集'兩本書在關(guān)聯(lián)表中都有兩位作者(kid和qq), 因此這兩本書在結(jié)果表中會出現(xiàn)兩次, 每次對應(yīng)一個作者.
通過將相關(guān)數(shù)據(jù)拆分到不同的表中, 并使用外鍵建立表之間的關(guān)系, 可以避免這種數(shù)據(jù)冗余.
可以將上面的表格拆分為以下幾個部分:
* 1. 作者表.
* 2. 作者詳情表.
* 3. 出版社表.
* 4. 書籍表.
* 5. 書籍-作者關(guān)聯(lián)表.
3.2.1 定義作者表模型
作者表(Authors): 存儲作者的基本信息,
如: 作者id(BIGINT), 作者名稱(VARCHAR), 年齡(INT)和作者詳情ID(INT)(手機(jī)號碼和地址存儲在另一個表中).
id(作者id)name(作者名稱)age(作者年齡)1kid182qq193qaq20
# index的models.py
from django.db import models
# 定義作者表
class Author(models.Model):
# 作者id(自動創(chuàng)建)
# 作者名稱(不定長字符串, 但是有一個寬度現(xiàn)在)
name = models.CharField(max_length=32, verbose_name='作者名稱')
# 作者年齡
age = models.IntegerField(verbose_name='作者年齡')
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.name
3.2.2 定義作者詳情表模型(一對一)
作者詳情表(AuthorDetails): 存儲作者的詳細(xì)聯(lián)系信息,
如: 作者詳情表id(BIGINT), 作者手機(jī)號碼(VARCHAR)和作者地址(VARCHAR).
id(作者詳情id)phone(作者手機(jī)號碼)addr(作者地址)author_id(作者id)1110北京12112上海23119深圳3
# 作者詳情表
class AuthorDetail(models.Model):
# 作者id(自動創(chuàng)建)
# 作者手機(jī)號碼
phone = models.CharField(max_length=11, verbose_name='作者手機(jī)號碼')
# 作者地址
addr = models.CharField(max_length=64, verbose_name='作者地址')
# 外鍵, 綁定作者表id, 并設(shè)置級聯(lián)刪除
author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE)
# 對象的字符串表現(xiàn)形式
def __str__(self):
return f'{self.id}'
3.2.3 定義出版社表模型
出版社表(Publishers): 存儲出版社的基本信息,
如: 出版社id(BIGINT), 出版社名稱(VARCHAR), 出版社地址(VARCHAR).
id(出版社id)name(出版社名稱)addr(出版社地址)email(出版社郵箱)1上海出版社上海123@qq.com2北京出版社北京456@qq.com
# 出版社表
class Publisher(models.Model):
# 出版社id(自動創(chuàng)建)
# 出版社名稱
name = models.CharField(max_length=32, verbose_name='出版社名稱')
# 出版社地址
addr = models.CharField(max_length=64, verbose_name='出版社地址')
# 出版社郵箱(默認(rèn)情況下, Django的EmailField 使用較長的 VARCHAR 254)
email = models.EmailField(verbose_name='出版社郵箱')
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.name
3.2.4 定義書籍表模型(一對多)
書籍表(Books): 存儲書籍的基本信息,
如: 書籍ID(BIGINT), 書籍名稱(VARCHAR), 書籍價格(DECIMAL), 書籍出版時間(DATE).
id(書籍id)title(書籍名稱)price(書籍價格)publish_date(書籍出版時間)publish_id(出版社id)1編程基礎(chǔ)50.002022-01-0112文學(xué)經(jīng)典65.002021-05-1013科幻小說集70.002023-03-152
# 書籍表
class Book(models.Model):
# 書籍id(自動創(chuàng)建)
# 書籍名稱
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, 并設(shè)置級聯(lián)刪除
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
# 外鍵, 使用半自動方式創(chuàng)建第三張表管理多對多表關(guān)系
author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.title
一些開發(fā)者傾向于在外鍵字段名中使用復(fù)數(shù)形式, 特別是當(dāng)該字段代表一個集合或列表時.
這種做法的邏輯是, 外鍵通常指向另一個表的一個或多個記錄, 因此使用復(fù)數(shù)形式可以直觀地表示這一點(diǎn).
3.2.5 定義書籍-作者關(guān)聯(lián)表模型(多對多)
* 5. 書籍-作者關(guān)聯(lián)表(Book_Authors): 存儲書籍和作者之間的多對多關(guān)系.
如: 關(guān)聯(lián)表id(BIGINT), 書籍id(INT), 作者id(INT).
id(關(guān)聯(lián)表id)book_id(書籍表id)author_id(作者表id)111221322431532
# 書籍-作者關(guān)聯(lián)表
class BookAuthor(models.Model):
# 關(guān)聯(lián)表id(自動創(chuàng)建)
# 外鍵, 綁定書籍表id, 并設(shè)置級聯(lián)刪除的級別為什么都不做
book = models.ForeignKey(to='Book', on_delete=models.DO_NOTHING)
# 外鍵, 綁定作者表id, 并設(shè)置級聯(lián)刪除的級別為什么都不做
author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING)
# 對象的字符串表現(xiàn)形式
def __str__(self):
return f'{self.book} --> {self.author}'
3.2.6 模型的完整代碼
# index的models.py
from django.db import models
# 定義作者表
class Author(models.Model):
# 作者id(自動創(chuàng)建)
# 作者名稱(不定長字符串, 但是有一個寬度現(xiàn)在)
name = models.CharField(max_length=32, verbose_name='作者名稱')
# 作者年齡
age = models.IntegerField(verbose_name='作者年齡')
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.name
# 作者詳情表
class AuthorDetail(models.Model):
# 作者id(自動創(chuàng)建)
# 作者手機(jī)號碼
phone = models.CharField(max_length=11, verbose_name='作者手機(jī)號碼')
# 作者地址
addr = models.CharField(max_length=64, verbose_name='作者地址')
# 外鍵, 綁定作者表id, 并設(shè)置級聯(lián)刪除
author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE)
# 對象的字符串表現(xiàn)形式
def __str__(self):
return f'{self.id}'
# 出版社表
class Publisher(models.Model):
# 出版社id(自動創(chuàng)建)
# 出版社名稱
name = models.CharField(max_length=32, verbose_name='出版社名稱')
# 出版社地址
addr = models.CharField(max_length=64, verbose_name='出版社地址')
# 出版社郵箱(默認(rèn)情況下, Django的EmailField 使用較長的 VARCHAR 254)
email = models.EmailField(verbose_name='出版社郵箱')
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.name
# 書籍表
class Book(models.Model):
# 書籍id(自動創(chuàng)建)
# 書籍名稱
title = models.CharField(max_length=32, verbose_name='書籍名稱')
# 書籍出版時間
publish_date = models.DateField(verbose_name='書籍出版時間', )
# 外鍵, 綁定出版社id, 并設(shè)置級聯(lián)刪除
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
# 外鍵, 使用半自動方式創(chuàng)建第三張表管理多對多表關(guān)系
author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.title
# 書籍-作者關(guān)聯(lián)表
class BookAuthor(models.Model):
# 關(guān)聯(lián)表id(自動創(chuàng)建)
# 外鍵, 綁定書籍表id, 并設(shè)置級聯(lián)刪除的級別為什么都不做
book = models.ForeignKey(to='Book', on_delete=models.DO_NOTHING)
# 外鍵, 綁定作者表id, 并設(shè)置級聯(lián)刪除的級別為什么都不做
author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING)
# 對象的字符串表現(xiàn)形式
def __str__(self):
return f'{self.book} --> {self.author}'
3.2.7 數(shù)據(jù)遷移
# 生成遷移文件
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 # 多對多的外鍵, 不會創(chuàng)建對應(yīng)的字段, 但可以通過這個字段進(jìn)行一些操作
- Add field publisher_id to book # 一對多的外鍵
- Create model AuthorDetail
# 執(zhí)行遷移:
PS D:\MyDjango> python manage.py migrate
Applying index.0001_initial...
# 創(chuàng)建作者表
(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
# 創(chuàng)建書籍表
(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
# 創(chuàng)建出版社表
(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
# 創(chuàng)建書籍-作者關(guān)聯(lián)表
(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
# 為書籍表創(chuàng)建外鍵字段綁定出版社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=[]
# 創(chuàng)建作者詳情表
(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
# 為作者-書籍表創(chuàng)建外鍵綁定作者表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=()
# 為作者-書籍表創(chuàng)建外鍵綁定書籍表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=()
# 為作者詳情表創(chuàng)建外鍵綁定作者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工具查看創(chuàng)建的模型表.
使用Navicat工具逆向數(shù)據(jù)庫到模型, 查看模型之間的關(guān)聯(lián).
4. 創(chuàng)建記錄
多表操作插入數(shù)據(jù)描述:
一對一: 在創(chuàng)建子表記錄時, 需要指定與主表記錄相關(guān)聯(lián)的外鍵字段.
一對多: 在創(chuàng)建'多'端的記錄時, 需要指定與'一'端記錄相關(guān)聯(lián)的外鍵字段.
多對多: 在創(chuàng)建記錄時, 通常不需要直接操作第三張表, 而是通過ORM提供的API來添加或刪除關(guān)聯(lián)關(guān)系.
4.1 外鍵字段使用說明
在Django中, 外鍵字段(如: author)和攜帶'_id'后綴的外鍵字段(如: author_id)在處理外鍵關(guān)系時代表了不同的方式和概念.
* 攜帶'_id'后綴的外鍵字段介紹(不涉及關(guān)聯(lián)表):
- 直接操作數(shù)據(jù)庫列: 當(dāng)看到如: author_id時, 這通常意味著直接引用數(shù)據(jù)庫表中的外鍵列.
然而, 在Django的ORM中, 通常不會直接這樣做, 因為Django的ORM抽象了這些底層的數(shù)據(jù)庫操作.
- 在特定情況下使用: 在某些情況下, 如自定義查詢或使用Django的RawQuerySet時, 可能會遇到author_id.
此外, 如果正在與Django生成的數(shù)據(jù)庫表直接交互(例如, 使用SQL語句或數(shù)據(jù)庫管理工具), 也會看到author_id列.
- 不推薦在ORM中使用: 在Django的ORM代碼中, 通常不推薦直接使用author_id,
因為這樣做會繞過Django ORM提供的所有好處, 如自動關(guān)聯(lián)查詢, 數(shù)據(jù)驗證等.
- 異常提示: 如果author_id的值是一個實(shí)例對象會拋出TypeError類型錯誤, 例:
TypeError: Field 'id' expected a number but got
* 不攜帶_id后綴的外鍵字段介紹(涉及關(guān)聯(lián)表):
- Django ORM中的外鍵字段: 在Django模型中, 如: author, 是一個外鍵字段, 它代表了與Author模型的關(guān)聯(lián).
當(dāng)訪問這個字段時, 得到的是一個指向Author模型實(shí)例的引用.
- 自動處理關(guān)聯(lián): Django ORM會自動處理author字段與Author模型之間的關(guān)聯(lián).
這意味著, 當(dāng)通過author字段設(shè)置或獲取值時, Django會處理所有的數(shù)據(jù)庫查詢和關(guān)聯(lián).
- 推薦的使用方式: 在Django ORM代碼中, 應(yīng)該始終使用author這樣的外鍵字段來引用關(guān)聯(lián)的對象.
這樣, 可以利用Django ORM提供的所有功能和優(yōu)勢.
- 異常提示: 如果author的值是一個id值會拋出ValueError值錯誤, 例:
ValueError: Cannot assign "3": "AuthorDetail.author" must be a "Author" instance.
4.2 作者表數(shù)據(jù)(基本表)
id(作者id)name(作者名稱)age(作者年齡)1kid182qq193qaq20
# 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
# 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 作者表詳情表數(shù)據(jù)(一對一)
在Django中, 處理數(shù)據(jù)庫模型時, 特別是涉及到外鍵(ForeignKey)關(guān)系時, 有兩種主要的方式來創(chuàng)建關(guān)聯(lián)對象.
* 方式1: 外鍵綁定id值(不推薦).
在這種方式中, 直接在創(chuàng)建AuthorDetail對象時, 通過author_id(攜帶_id)字段來指定與之關(guān)聯(lián)的Author對象的ID.
* 方式2: 外鍵是一個實(shí)例(推薦).
在這種方式中, 首先創(chuàng)建一個Author對象, 然后在創(chuàng)建AuthorDetail對象時, 將這個Author對象作為外鍵字段author(不攜帶_id)的值.
這是Django中處理外鍵關(guān)系的推薦方式, 因為它更加直觀, 且避免了直接操作數(shù)據(jù)庫ID的需要.
id(作者詳情id)phone(作者手機(jī)號碼)addr(作者地址)author_id(作者id)1110北京12112上海23119深圳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(外鍵綁定一個實(shí)例)
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)
# 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 出版社表數(shù)據(jù)(基本表)
id(出版社id)name(出版社名稱)addr(出版社地址)email(出版社郵箱)1上海出版社上海123@qq.com2北京出版社北京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值自動創(chuàng)建
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)
# 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 書籍表數(shù)據(jù)(一對多)
id(書籍id)title(書籍名稱)price(書籍價格)publish_date(書籍出版時間)publish(出版社id)1編程基礎(chǔ)50.002022-01-0112文學(xué)經(jīng)典65.002021-05-1013科幻小說集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
# 因為數(shù)據(jù)分批錄入的, 這里外鍵使用id值.
book_row1 = Book.objects.create(title='編程基礎(chǔ)', 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='文學(xué)經(jīng)典', 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)
# ORM日志:
(0.000)
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`)
VALUES ('編程基礎(chǔ)', '50.00', '2022-01-01', 1);
args=['編程基礎(chǔ)', '50.00', '2022-01-01', 1]
(0.015)
INSERT INTO `index_book` (`title`, `price`, `publish_date`, `publisher_id`)
VALUES ('文學(xué)經(jīng)典', '65.00', '2021-05-10', 1);
args=['文學(xué)經(jīng)典', '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 書籍-作者關(guān)聯(lián)表數(shù)據(jù)(多對多)
id(關(guān)聯(lián)表id)book_id(書籍表id)author_id(作者表id)111221322431532
# 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
# 因為數(shù)據(jù)分批錄入的, 這里外鍵使用id值.
book_author_row1 = BookAuthor.objects.create(book_id=1, author_id=1)
print(book_author_row1)
# ORM日志
# 插入第一條數(shù)據(jù)
(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,)
# 省略...
# 關(guān)聯(lián)表對象的字符串表現(xiàn)形式為:
def __str__(self):
return f'{self.book} --> {self.author}'
# 在打印關(guān)聯(lián)表對象的時候self.book和self.author, 會分別執(zhí)行查詢語句獲取模型對象.
# 修改關(guān)聯(lián)表對象的字符串表現(xiàn)形式為
def __str__(self):
return f'{self.book_id} --> {self.author_id}'
# 在打印關(guān)聯(lián)表對象的時候不會執(zhí)行查詢語句
# 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)
# ORM日志:
(0.016) INSERT INTO `index_bookauthor` (`book_id`, `author_id`) VALUES (3, 3); args=[3, 3]
4.7 add方法(多對多)
在Django的ORM中, 為多對多關(guān)系的模型提供一個add()方法, 它允許將一個或多個對象添加到關(guān)聯(lián)表中.
這個方法使得在Django中管理多對多關(guān)系變得簡單而直觀.
注意事項: 在使用add方法時, 如果嘗試添加的對象已經(jīng)存在于多對多關(guān)系中, Django會忽略這些重復(fù)項, 并且不會引發(fā)錯誤.
現(xiàn)在有兩個模型, Book和Author, 它們之間通過多對多關(guān)系連接, 并且在Book模型中定義了一個author字段.
可以使用add()方法將一個或多個Author對象(或id值)添加到Book對象的author列表中.
* author字段雖然不是一個真正的Python列表, 但Django的ORM提供了類似列表的操作來管理這種多對多關(guān)系.
# 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
# 添加單個對象(為編程基礎(chǔ)書籍添加一個作者)
# 先獲取作者對象
author = Author.objects.filter(name='qaq').first()
# 后獲取書籍對象
book = Book.objects.filter(title='編程基礎(chǔ)').first()
# 將author添加到book的author列表中
res = book.author.add(author)
print(res)
# 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` = '編程基礎(chǔ)'
ORDER BY `index_book`.`id` ASC
LIMIT 1;
args=('編程基礎(chǔ)',)
# 先查詢后插入
(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)
可以一次性添加多個對象到多對多關(guān)系中, 只需將對象作為列表或查詢集傳遞給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
# 創(chuàng)建Book實(shí)例
book_row = Book.objects.create(title='玄幻小說集', price=88.88,
publish_date=datetime.strptime('2024-05-1', '%Y-%m-%d'),
publisher_id=2)
# 獲取多個Author實(shí)例
author_row1 = Author.objects.get(name='kid')
author_row2 = Author.objects.get(name='qq')
# 為書籍添加多個作者
book_row.author.add(author_row1, author_row2)
通過create創(chuàng)建對象返回的實(shí)例或使用get()方法獲取實(shí)例, 通過外鍵字段使用add()方法會提示.
例: book.author.add(author) PyCharm會出現(xiàn)提示(不用管):
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.
# 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方法也接受主表的主鍵值作為參數(shù), 使用方式如下:
# 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
# 創(chuàng)建Book實(shí)例
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)
# 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)
如果嘗試添加的對象已經(jīng)存在于多對多關(guān)系中, Django會忽略這些重復(fù)項, 并且不會引發(fā)錯誤.
# 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
# 創(chuàng)建Book實(shí)例
book_row = Book.objects.filter(title='天文選集').first()
# 為書籍添加多個作者
res = book_row.author.add(1, 2, 3)
print(res)
# 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=('天文選集',)
# 不執(zhí)行插入數(shù)據(jù)而是執(zhí)行查詢
(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中, 當(dāng)查詢一對多或多對多關(guān)系時, 默認(rèn)情況下, 如果訪問的是一個關(guān)聯(lián)對象集合(例如, 一個作者的所有書籍),
Django會自動返回一個RelatedManager實(shí)例, 這個實(shí)例類似于一個查詢集(QuerySet), 但它是專門用于管理關(guān)聯(lián)對象的.
可以在這個RelatedManager實(shí)例上調(diào)用.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()) #
5.1 正/反向查詢
在Django ORM多表查詢中最常見的兩種查詢方式是正向查詢與反向查詢.
這兩種查詢方式主要涉及到數(shù)據(jù)庫表之間的關(guān)聯(lián)關(guān)系, 特別是外鍵關(guān)系.
5.1.1 正向查詢
正向查詢: 是指由外鍵所在表(從表或子表)查詢關(guān)聯(lián)的主表對象或主表字段的過程.
簡單來說, 就是從一個包含外鍵的表中, 通過外鍵字段來查詢與之關(guān)聯(lián)的另一個表(主表或父表)的數(shù)據(jù).
特點(diǎn): 查詢方向是從外鍵所在的表(從表)到被關(guān)聯(lián)的表(主表)
可以通過外鍵字段名直接訪問關(guān)聯(lián)的主表對象或字段.
現(xiàn)有兩個模型, Book(書籍)和Author(作者)是多對多關(guān)系, 其中Book模型中包含一個指向Author模型的外鍵字段author.
正向查詢: 則是從Book模型出發(fā), 通過author外鍵字段查詢關(guān)聯(lián)的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)
# 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 反向查詢
反向查詢: 是指由主表對象查詢與之關(guān)聯(lián)的從表對象或字段的過程.
即, 從一個不包含外鍵的表中, 通過某種方式查詢與之關(guān)聯(lián)的包含外鍵的表(從表或子表)的數(shù)據(jù).
可以通過Django ORM自動生成的_set屬性或自定義的related_name屬性來進(jìn)行反向查詢.
如果不使用related_name, Django會默認(rèn)使用模型名小寫加_set作為反向查詢的屬性名.
特點(diǎn): 查詢方向是從主表到從表.
繼續(xù)上面的例子, 如果想從Author模型出發(fā), 查詢所有由'kid'編寫的書籍, 就需要進(jìn)行反向查詢.
# 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() # 反向查詢, 使用默認(rèn)名稱book_set
print(books)
# 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 反向關(guān)聯(lián)名稱
如果在定義外鍵時指定了related_name(反向關(guān)聯(lián)名稱), 則可以使用該名稱進(jìn)行反向查詢.
修改Book模型中的外鍵參數(shù), related_name='authored_books':
# index的models.py
# 書籍表
class Book(models.Model):
# 書籍id(自動創(chuàng)建)
# 書籍名稱
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, 并設(shè)置級聯(lián)刪除
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社id')
# 外鍵, 使用半自動方式創(chuàng)建第三張表管理多對多表關(guān)系
author = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))
# 對象的字符串表現(xiàn)形式
def __str__(self):
return self.title
# 生成遷移文件
PS D:\MyDjango> python manage.py makemigrations
Migrations for 'index':
index\migrations\0002_alter_book_author.py
- Alter field author on book -- 修改字段
# 執(zhí)行遷移
PS D:\MyDjango> python manage.py migrate
Applying index.0002_alter_book_author...
...
使用related_name參數(shù)設(shè)置的值來進(jìn)行方向查詢:
# 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參數(shù)定義的名稱
print(books)
# 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,)
總結(jié):
正向查詢和反向查詢是Django ORM中處理表之間關(guān)聯(lián)關(guān)系的兩種基本方式。
正向查詢通過外鍵字段名進(jìn)行, 而反向查詢則可以通過Django自動生成的_set屬性或自定義的related_name屬性來實(shí)現(xiàn).
5.2 字段查找
5.2.1 雙下劃線語法
在Django中, __(雙下劃線)在查詢集(QuerySets)中扮演著特殊的角色,
尤其是在使用filter(), exclude(), annotate(), order_by()等方法時.
雙下劃線允許執(zhí)行更復(fù)雜的查詢, 比如跨關(guān)系查詢, 執(zhí)行數(shù)據(jù)庫聚合函數(shù)等.
當(dāng)看到類似:age__gt(字段__查詢操作符), author__name(關(guān)聯(lián)的模型__關(guān)聯(lián)的模型主表字段)這樣的表達(dá)式時,
這里的__前后的值分別代表不同的含義:
* __前面的值: 通常指的是Django模型(Model)中的一個字段名,
或者是一個關(guān)系字段(如: ForeignKey, ManyToManyField等)所關(guān)聯(lián)的模型名(當(dāng)進(jìn)行跨模型查詢時).
在這個例子(author__name)中, author是當(dāng)前模型中的一個字段名, 它指向另一個模型(Author模型),
通常是通過ForeignKey或其他關(guān)系字段實(shí)現(xiàn)的.
* __ 后面的值: 指定了想要對__前面的字段進(jìn)行的操作或查詢條件.
這可以是數(shù)據(jù)庫中的一個操作符, 比如gt(大于), lt(小于)等.
在這個特定的例子(author__name)中, name是Author模型中的一個字段名.
這里的author__name是在執(zhí)行跨模型查詢, 意思是查詢當(dāng)前模型中author字段關(guān)聯(lián)的Author模型,
并在這個關(guān)聯(lián)的Author模型上進(jìn)一步查詢其name字段.
Django特有的'雙下劃線'(__)語法, 可以使得開發(fā)者能夠輕松地在數(shù)據(jù)庫層面實(shí)現(xiàn)復(fù)雜的查詢邏輯.
5.2.2 跨表字段查詢
在Django的ORM中, 跨表字段查詢允許根據(jù)與主模型相關(guān)聯(lián)的其他模型中的字段來過濾查詢結(jié)果.
5.2.2.1 正向使用
示例: 現(xiàn)在有兩個模型: Author和Book, 他們之間是多對多的關(guān)系靠關(guān)聯(lián)進(jìn)行聯(lián)系.
如果想要查詢所有由特定作者(比如名字為'kid'的作者)所寫的書籍, 可以使用:
Book.objects.filter(bookauthor__author__name='kid'), 這類查詢表達(dá)式用于執(zhí)行跨表查詢.
代碼解釋: Book(書籍表)先連接bookauthor(關(guān)聯(lián)表)再連接author(作者表), 最后過濾出來author(作者表)中name字段值為'kid'的數(shù)據(jù).
# 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)
# 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表使用跨表字段查詢的時候可以省略掉中間表部分, 它會自動處理關(guān)聯(lián)表.
# 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)
# 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表使用跨表字段查詢的時候不可以省略掉中間表部分, 需要手動動處理關(guān)聯(lián)表.
示例: 如果想要查詢所有由特定作者(比如名字為'kid'的作者)所寫的書籍, 可以使用: bookauthor__book__title查詢表達(dá)式執(zhí)行跨表查詢.
# 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)
(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)
django.core.exceptions.FieldError: Cannot resolve keyword 'book' into field.
Choices are: age, authordetail, authored_books, bookauthor, id, name
django.core.exceptions.FieldError: 無法將關(guān)鍵字'book'解析到字段中.
選項包括: age, authordetail, authored_books, bookauthor, id, name.
其中:
authordetail是作者詳情表.
bookauthor是關(guān)聯(lián)表.
authored_books是書籍表的外鍵字段設(shè)置的名稱, 這個名稱被related_name參數(shù)控制.
總結(jié): 子表可以通過外鍵字連接主表, 主表可以通過主表的名稱連接主表(對于多對多, 則使用外鍵的字段反向連接的名稱連接子表).
使用外鍵的字段反向連接的名稱連接子表, 例: 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)
(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=('天文選集',)
在進(jìn)行多表查詢時, 需要注意查詢的性能. 避免使用過多的JOIN操作, 合理使用索引和查詢優(yōu)化技術(shù), 以提高查詢效率.
5.2.2.3 獲取關(guān)聯(lián)字段
如果只需要關(guān)聯(lián)對象的某些字段(而不是完整的對象), 可以使用values或values_list方法來減少返回的數(shù)據(jù)量.
這些方法允許你指定一個或多個字段, 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:
# 注意: 這將為每個作者名稱創(chuàng)建一個條目, 如果一本書有多個作者
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)
# ORM日志:
(0.000)
SELECT `index_book`.`title`, `index_author`.`name`
FROM `index_book`
LEFT OUTER JOIN `index_bookauthor` # 左外連接, 左表的記錄為基礎(chǔ)表, 右表的記錄為補(bǔ)充表
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
LEFT OUTER JOIN `index_author` # 左外連接
ON (`index_bookauthor`.`author_id` = `index_author`.`id`);
args=()
5.3 聯(lián)表查詢
Django ORM為聯(lián)表查詢提供了兩個高性能查詢方法: select_related和prefetch_related.
它們的主要目的是減少數(shù)據(jù)庫查詢的次數(shù), 尤其是在處理具有外鍵關(guān)系的模型時.
然而, 它們各自的工作原理和適用場景有所不同.
5.3.1 select_related方法
select_related方法: 用于對一對一(OneToOneField)和外鍵(ForeignKey, 一對多)關(guān)系進(jìn)行預(yù)加載.
select_related的參數(shù)可以是外鍵字段的名稱, 反向查詢時則使用外鍵字段related_name設(shè)置的名稱(默認(rèn)為關(guān)聯(lián)表的模型名稱小寫).
在查詢一個模型時, 如果這個模型中包含了指向其他模型的外鍵, 并且需要在后續(xù)操作中頻繁訪問這些外鍵關(guān)聯(lián)的對象,
那么使用select_related可以減少數(shù)據(jù)庫查詢的次數(shù).
工作原理: select_related通過JOIN語句進(jìn)行拼表操作, 并一次性獲取關(guān)聯(lián)對象的數(shù)據(jù).
返回值: 返回值是當(dāng)前模型的查詢集, 但每個查詢集實(shí)例都包含了其相關(guān)聯(lián)表中的實(shí)例的數(shù)據(jù).
使用場景: 當(dāng)需要訪問關(guān)聯(lián)對象的數(shù)據(jù)時, 并且關(guān)聯(lián)的數(shù)據(jù)量不是非常大, 這時使用select_related可以顯著提高性能.
5.4.1.1 正向使用
示例: 現(xiàn)在有兩個模型: Author(書籍表)和AuthorDetail(作者詳情表), 其中AuthorDetail通過OneToOneField關(guān)聯(lián)到Author.
訪問子模型的字段可以直接通過點(diǎn)操作符(.)來訪問這些實(shí)例的屬性(即模型的字段), 例: author_detail.phone.
訪問主實(shí)例的數(shù)據(jù)需要通過外鍵字段進(jìn)行訪問, 例: 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
# 將作者詳情表和作者表的拼接結(jié)果返回
author_details = AuthorDetail.objects.select_related('author').all()
for author_detail in author_details:
# 可以獲取兩張表中的所有字段
print(author_detail.author.name, author_detail.phone) # 這里不會觸發(fā)額外的數(shù)據(jù)庫查詢
(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方法時提供子模型的小寫名稱即可.
訪問主模型的字段可以直接通過點(diǎn)操作符(.)來訪問這些實(shí)例的屬性(即模型的字段), 例: author.phone.
訪問主實(shí)例的數(shù)據(jù)需要通過子表名稱進(jìn)行訪問, 例: 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
# 參數(shù)為關(guān)聯(lián)表的模型名稱小寫
authors = Author.objects.select_related('authordetail').all()
for author in authors:
print(author.name, author.authordetail.phone)
(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=()
如果參數(shù)寫錯了會提示可用的參數(shù).
# 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
# 參數(shù)為關(guān)聯(lián)表的模型名稱原名, 會報錯
authors = Author.objects.select_related('AuthorDetail').all()
for author in authors:
print(author.name, author.authordetail.phone)
django.core.exceptions.FieldError:
select_related中給出的字段名稱無效: 'AuthorDetail' 選項包括: authordetail(模型名稱小寫).
可以通過外鍵字段的related_name參數(shù)設(shè)置名稱(默認(rèn)名稱是模型名稱小寫, 使用related_name參數(shù)定義名稱后, 默認(rèn)就是無存在了!).
# index.models.py
# 作者詳情表
class AuthorDetail(models.Model):
# 作者id(自動創(chuàng)建)
# 作者手機(jī)號碼
phone = models.CharField(max_length=11, verbose_name='作者手機(jī)號碼')
# 作者地址
addr = models.CharField(max_length=64, verbose_name='作者地址')
# 外鍵, 綁定作者表id, 并設(shè)置級聯(lián)刪除
author = models.OneToOneField(to='Author', verbose_name='作者id', on_delete=models.CASCADE,
related_name='author_detail')
# 對象的字符串表現(xiàn)形式
def __str__(self):
return f'{self.id}'
# 生成遷移文件
PS D:\MyDjango> python manage.py makemigrations
Migrations for 'index':
index\migrations\0003_alter_authordetail_author.py
- Alter field author on authordetail
# 執(zhí)行遷移
PS D:\MyDjango> python manage.py migrate
...
Author主模型拼接子模型, 在使用select_related方法時使用外鍵參數(shù)related_name設(shè)置的名稱.
訪問相關(guān)表字段的時候也是使用related_name設(shè)置的名稱.
# 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
# 參數(shù)為關(guān)聯(lián)表的模型名稱原名, 會報錯
authors = Author.objects.select_related('author_detail').all()
for author in authors:
print(author.name, author.author_detail.phone)
# 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)和反向外鍵(即, 當(dāng)從被關(guān)聯(lián)模型訪問關(guān)聯(lián)模型時)關(guān)系進(jìn)行預(yù)加載.
與select_related類似, prefetch_related也旨在減少數(shù)據(jù)庫查詢的次數(shù), 但處理多對多關(guān)系時, 它使用不同的策略.
工作原理: prefetch_related通過執(zhí)行兩個單獨(dú)的查詢來完成工作:
一個查詢用于獲取主對象列表, 另一個查詢用于獲取所有相關(guān)的對象, 并將它們分組以匹配主對象.
然后, Django將這些相關(guān)對象(可能有多個)附加到主對象上, 這樣就可以像訪問普通屬性一樣訪問它們, 而無需觸發(fā)額外的數(shù)據(jù)庫查詢.
使用場景:當(dāng)需要訪問大量關(guān)聯(lián)對象, 或者關(guān)聯(lián)的數(shù)據(jù)量很大時, prefetch_related是更好的選擇.
因為它避免了使用JOIN, 這可能在處理大量數(shù)據(jù)時導(dǎo)致性能問題.
5.3.2.1 正向使用
現(xiàn)在有兩個模型: Book(書籍表)和Author(作者表), 他們之間是多對多的關(guān)系靠關(guān)聯(lián)進(jìn)行聯(lián)系. Book通過ManyToManyField關(guān)聯(lián)到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:
# 因為是多對多關(guān)系, 所以 book.authors是一個QuerySet
authors_names = [author.name for author in book.author.all()]
print(f"書籍<<{book.title}>>的作者有: {', '.join(authors_names)}")
# 獲取所有書籍的記錄
(0.000)
SELECT `index_book`.`id`, `index_book`.`title`, `index_book`.`price`, `index_book`.`publish_date`, `index_book`.`publisher_id`
FROM `index_book`; args=()
# # 先將作者表與關(guān)聯(lián)表拼接, 后面通過書籍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方法的參數(shù)使用外鍵related_name參數(shù)設(shè)置的名稱.
# 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:
# 因為是多對多關(guān)系, 所以 author.authored_books是一個QuerySet
book_name = [book.title for book in author.authored_books.all()]
print(f"作者<<{author.name}>> 著作的書籍: {', '.join(book_name)}")
# ORM日志
# 獲取所有作者表的記錄
(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age` FROM `index_author`;
args=()
# 先將書籍表與關(guān)聯(lián)表拼接, 后面通過作者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()方法實(shí)現(xiàn)的, 它允許對查詢集(QuerySet)中的對象進(jìn)行分組,
并對每個分組應(yīng)用聚合函數(shù)來計算統(tǒng)計值.
5.4.1 聚合函數(shù)
在Django ORM中, 聚合函數(shù)允許對數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行統(tǒng)計計算, 如: 計算總數(shù), 平均值, 最大值, 最小值以及總和等.
這些聚合函數(shù)是在django.db.models模塊中定義的, 并且可以通過annotate()或aggregate()方法在查詢集(QuerySet)上調(diào)用.
以下是一些常用的Django ORM聚合函數(shù):
* 1. Count: 計算數(shù)量.
這是最常用的聚合函數(shù)之一, 用于計算某個字段在查詢集中的非空值的數(shù)量.
* 2. Sum: 計算總和.
這個函數(shù)用于計算查詢集中某個數(shù)值字段的總和.
* 3. Avg: 計算平均值.
這個函數(shù)用于計算查詢集中某個數(shù)值字段的平均值.
* 4. Max 和 Min: 分別用于計算查詢集中某個字段的最大值和最小值.
使用聚合函數(shù)時, 通常會指定一個或多個字段作為這些聚合操作的基礎(chǔ), 這些字段是直接定義在模型中的字段.
5.4.2 aggregate方法
aggregate是Django ORM中的一個方法, 用于對查詢集(QuerySet)中的一組值執(zhí)行計算, 并返回一個包含聚合值的字典.
這個方法允許開發(fā)者對數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行匯總計算, 如: 計算平均值, 最大值, 最小值, 總和以及計數(shù)等,
而無需在Python代碼中手動處理這些數(shù)據(jù).
aggregate方法的特點(diǎn):
- 聚合函數(shù): aggregate方法可以與Django提供的聚合函數(shù)一起使用.
- 返回類型: aggregate方法返回一個字典, 其中包含了聚合計算的結(jié)果.
字典的鍵是聚合函數(shù)的名稱(或者自定義的別名), 值是計算得到的聚合值.
- 終止子句: aggregate是QuerySet的一個終止子句, 意味著在調(diào)用aggregate方法后, 將不能再對返回的字典進(jìn)行進(jìn)一步的鏈?zhǔn)讲樵儾僮?
使用示例: 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')}
# ORM日志:
(0.000) SELECT AVG(`index_book`.`price`) AS `price__avg` FROM `index_book`; args=()
在這個例子中, aggregate方法接受一個聚合函數(shù)Avg('price')作為參數(shù), 并返回了一個包含平均價格(鍵為'price__avg')的字典.
自定義鍵名: 如果不喜歡Django自動生成的鍵名(如: 'price__avg'), 可以將聚合函數(shù)的結(jié)果賦值給一個變量名, 這個變量名也就是別名.
# 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')}
# ORM日志:
(0.000) SELECT AVG(`index_book`.`price`) AS `price__avg` FROM `index_book`; args=()
5.4.3 annotate方法
annotate()方法是Django ORM中一個非常強(qiáng)大的工具, 它允許在查詢集(QuerySet)上添加額外的注解(Annotation),
這些注解可以是聚合函數(shù)的結(jié)果, 也可以是表達(dá)式的結(jié)果.
這些注解在查詢執(zhí)行時動態(tài)地計算, 并且可以作為查詢集的每個對象的一個屬性來訪問.
現(xiàn)在有兩個模型: Book(書籍表)和Author(作者表), 他們之間是多對多的關(guān)系靠關(guān)聯(lián)進(jìn)行聯(lián)系. Book通過ManyToManyField關(guān)聯(lián)到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對象添加作者數(shù)量.
# annotate: 為書籍記錄添加一個author_count的字段, 值為Count('author').
# Count('author'): 使用Count函數(shù)統(tǒng)計外鍵author對象的數(shù)量.
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}個作者.")
在這個例子中, Count('author')是一個聚合函數(shù), 它計算每個書籍(Bokk 對象)通過其author外鍵關(guān)系所關(guān)聯(lián)的Author對象的數(shù)量.
然后, 這個數(shù)量被作為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` # 設(shè)置別名
FROM `index_book`
LEFT OUTER JOIN `index_bookauthor` # 左外連接, author_count的值可以為0
ON (`index_book`.`id` = `index_bookauthor`.`book_id`)
GROUP BY `index_book`.`id` # 默認(rèn)按id分組
ORDER BY NULL; # 排序為NULL
args=()
反向查詢使用外鍵related_name參數(shù)設(shè)置的名稱.
# 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對象添加著作的書籍?dāng)?shù)量.
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}本著作.")
在這個例子中, Count('book')是一個聚合函數(shù), 它計算每個作者(Author 對象)通過其book外鍵關(guān)系所關(guān)聯(lián)的Book對象的數(shù)量.
然后, 這個數(shù)量被作為book_count注解添加到每個Author對象上.
# ORM日志:
(0.000)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`,
COUNT(`index_bookauthor`.`book_id`) AS `book_count` # 設(shè)置別名
FROM `index_author` LEFT OUTER JOIN `index_bookauthor` # 左外連接, book_count的值可以為0
ON (`index_author`.`id` = `index_bookauthor`.`author_id`)
GROUP BY `index_author`.`id` # 默認(rèn)按id分組
ORDER BY NULL; # 排序為NULL
args=()
在同一個查詢中使用多個聚合函數(shù), 并為它們分別添加注解.
# 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
# 計算每個作者的書籍?dāng)?shù)量和平均價格
authors_with_stats = Author.objects.annotate(
book_count=Count('authored_books'), # 統(tǒng)計書籍?dāng)?shù)量: 通過外鍵獲取書籍的數(shù)量
avg_price=Avg('authored_books__price'), # 計算平均價格: 通過外鍵獲取書籍的價格
)
# 遍歷查詢集并訪問注解
for author in authors_with_stats:
print(f"{author.name}有{author.book_count}本書, 平均價格為{author.avg_price:.2f}.")
在這個例子中, Count('book') 和 Avg('book__price') 都是聚合函數(shù), 它們分別為每個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=()
在上例的基礎(chǔ)上加上排序.
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
# 計算每個作者的書籍?dāng)?shù)量和平均價格
authors_with_stats = Author.objects.annotate(
book_count=Count('authored_books'), # 統(tǒng)計書籍?dāng)?shù)量: 通過外鍵獲取書籍的數(shù)量
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}.")
# 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()結(jié)合使用來指定分組依據(jù).
- values()方法允許指定一個或多個字段, Django會根據(jù)這些字段的值對查詢集進(jìn)行分組, 并對每個分組應(yīng)用聚合函數(shù).
- values_list()方法在功能上與values()類似, 但它返回的是一個元組列表而不是字典列表.
然而, 在進(jìn)行聚合操作時, 通常不需要直接使用values_list(), 因為聚合操作的結(jié)果本身就是一種聚合后的數(shù)據(jù)表示(如計數(shù), 總和等),
它們自然是以字典(或類似字典的結(jié)構(gòu))的形式返回的, 以便能夠方便地訪問每個字段的值.
* 分組后顯示的字段名稱為方法的參數(shù)名.
* 必須先分組再使用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
# 要按出版年份分組, 并計算每個年份的書籍?dāng)?shù)量
# 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)
# ORM日志:
(0.000)
SELECT EXTRACT(YEAR FROM `index_book`.`publish_date`), # 字段的名稱為values方法的參數(shù)名稱
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 子查詢
子查詢是一個在另一個查詢內(nèi)部執(zhí)行的查詢, 它可以返回單個值, 一行或多行多列.
5.5.1 OuterRef函數(shù)
OuterRef函數(shù)是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函數(shù)
authors_with_books = Author.objects.filter(
pk__in=Book.objects.filter(
author=OuterRef('pk') #
).values('author')
).distinct()
print(authors_with_books)
# 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函數(shù)
Subquery函數(shù)允許在Django ORM的查詢中嵌入一個子查詢, 這個子查詢可以是一個完整的查詢集(QuerySet),
但會被限制為只返回一個值(對于標(biāo)量子查詢)或一行(對于行子查詢).
Subquery的使用相對復(fù)雜, 并且通常需要與OuterRef()一起使用.
當(dāng)創(chuàng)建一個Subquery時, 它本身是一個獨(dú)立的查詢集(QuerySet), 但可能希望這個查詢集能夠基于外部查詢的某些字段來過濾或計算.
這時, OuterRef()就派上用場了, 可以將OuterRef('field_name')傳遞給子查詢的過濾條件, 以引用外部查詢中名為field_name的字段.
注意事項:
- 確保子查詢只返回一個值.
在使用Subquery時, 需要確保子查詢對于外部查詢中的每一行都只返回一個值.
這通常通過values()與切片([:1])來確保單個字段信息返回.
- 雖然OuterRef和Subquery提供了強(qiáng)大的查詢能力, 但它們也可能導(dǎo)致查詢性能下降, 特別是在處理大量數(shù)據(jù)時.
示例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函數(shù)
authors_with_books = Author.objects.filter(
pk__in=author_id # 使用子查詢
).distinct()
print(authors_with_books)
(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函數(shù)會限制只返回一個值或一行, 否則會報錯, 例如:
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函數(shù)
authors_with_books = Author.objects.filter(
pk__in=Subquery(author_id) # 使用子查詢
).distinct()
print(authors_with_books)
在使用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] # 限制只返回一個字段和一條數(shù)據(jù)
# 使用OuterRef函數(shù)
authors_with_books = Author.objects.filter(
pk__in=Subquery(author_id) # 使用子查詢
).distinct()
print(authors_with_books)
在學(xué)習(xí)MySQL的時候就知道了, 單層子查詢的LIMIT不能搭配IN操作符.
當(dāng)只需要檢查存在性而不是檢索實(shí)際數(shù)據(jù)時, 可以使用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)
(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函數(shù)
authors_with_books = Author.objects.filter(
pk__in=Subquery(author_id) # 使用子查詢
).distinct()
print(authors_with_books)
# 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') # 先設(shè)置過濾的外部查詢關(guān)聯(lián)字段
).values('author').annotate( # 后通過外鍵分組計算最高價格
max_price=Max('price')
).values('max_price')[:1] # 限制只返回一個字段和一條數(shù)據(jù)
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}")
第一個values是分組, 第二個values是限制返回的信息只有一個字段, 必須限制.
(0.015)
SELECT `index_author`.`id`, `index_author`.`name`, `index_author`.`age`,
( # 書籍表拼接關(guān)聯(lián)表
SELECT MAX(U0.`price`) AS `max_price`
FROM `index_book` U0 # 設(shè)置別名
INNER JOIN `index_bookauthor` U1 # 設(shè)置別名
ON (U0.`id` = U1.`book_id`) # 拼表條件
WHERE U1.`author_id` = `index_author`.`id` # 使用外部表的字段進(jìn)行過濾
GROUP BY U1.`author_id` # 分組
ORDER BY NULL
LIMIT 1
) AS `max_book_price`
FROM `index_author`;
args=()
示例 3: 使用子查詢來過濾結(jié)果, 獲取價格高于所有作者最貴書籍平均價格的書籍.
# 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}")
# 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 修改實(shí)例對象數(shù)據(jù)
修改模型實(shí)例的數(shù)據(jù)通常與單表操作非常相似.
當(dāng)修改一個模型實(shí)例的屬性時, 實(shí)際上是在內(nèi)存中操作這個對象的副本.
要將這些更改持久化到數(shù)據(jù)庫中, 需要調(diào)用一個方法(如: save())來提交這些更改即可.
操作步驟: 1.查找需要修改的實(shí)例對象, 2.修改實(shí)例對象, 3.持久化.
示例: 為作者'kid'的所有書籍打9折進(jìn)行銷售.
# 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) # 修改的記錄數(shù)量
# 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 一對一修改綁定對象
修改一對一的外鍵關(guān)聯(lián): 通常先需要獲取到次表的實(shí)例, 然后通過這個實(shí)例來訪問和修改其關(guān)聯(lián)的主表實(shí)例.
現(xiàn)在有兩個模型, Author(作者表, 主)和AuthorDetail(作者詳情表, 次), 其中AuthorDetail通過OneToOneField與Author關(guān)聯(lián).
先獲取次表(AuthorDetail)的實(shí)例, 在修改外鍵字段關(guān)聯(lián)的主表實(shí)例, 由于是一對一關(guān)系, 不能使用已經(jīng)被綁定用戶的實(shí)例.
# 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
# 獲取一個已經(jīng)存在的 AuthorDetail 實(shí)例
author_detail_1 = AuthorDetail.objects.get(id=1)
# 由于是一對一關(guān)系, 不能使用已經(jīng)存在的用戶, 只能創(chuàng)建一個新的的 Author 實(shí)例
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)
# 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 一對多修改綁定對象
修改一對多的外鍵關(guān)聯(lián): 通常會在'多'端的對象上修改外鍵字段, 指向'一'端的對象.
這里有一些步驟和示例來說明如何進(jìn)行:
* 1. 獲取'一'端的實(shí)例: 首先, 需要獲取到與多個'多'端實(shí)例相關(guān)聯(lián)的那個'一'端的實(shí)例.
* 2. 獲取'多'端的實(shí)例: 然后, 可以使用這個'一'端的實(shí)例來查詢所有與之相關(guān)聯(lián)的'多'端的實(shí)例.
* 3. 在'多'端的修改外鍵: 最后, 可以遍歷這些查詢到的'多'端實(shí)例, 并對它們進(jìn)行修改.
# 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是'多'端
# 獲取'一'端的實(shí)例
publisher_1 = Publisher.objects.get(id=1)
# 獲取'多'端的實(shí)例
book = Book.objects.filter(title='天文選集').first()
# 在'多'端修改外鍵值
book.publisher = publisher_1 # 直接在Book對象上設(shè)置author屬性
book.save()
# 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 多對多修改綁定對象
對于對于多對多關(guān)系ORM提供了set()方法用于替換一個對象與另一個對象集合之間的所有關(guān)聯(lián).
它會先刪除當(dāng)前對象與舊關(guān)聯(lián)對象之間的所有關(guān)系, 然后建立與新對象集合之間的關(guān)系.
# 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
# 創(chuàng)建一本書
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
# 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已經(jīng)存在, 插入也不會報錯
args=(1, 2)
在上面的例子中, set()方法接收一個可迭代對象(如列表或查詢集), 其中包含要與之建立關(guān)系的Author實(shí)例.
它會自動處理數(shù)據(jù)庫的更新, 包括刪除任何現(xiàn)有的且不在新列表中的關(guān)系, 并添加任何新的關(guān)系.
這是處理多對多關(guān)系時非常有用的一個功能, 因為它允許你以原子方式更新整個關(guān)系集, 而不需要手動刪除舊的關(guān)系和添加新的關(guān)系.
7. 刪除記錄
刪除記錄時, 可以直接調(diào)用對象的delete()方法.
需要注意的是, 如果數(shù)據(jù)庫配置了外鍵約束(如ON DELETE CASCADE), 刪除某個記錄可能會導(dǎo)致與之相關(guān)聯(lián)的其他記錄也被刪除.
作者詳情表(次) 一對一 作者表(主): 刪除作者表中的記錄, 作者詳情表中的關(guān)聯(lián)數(shù)據(jù)一同刪除.
出版社(一) 一對多 書籍表(多): 刪除出版社表中的記錄, 書籍表表中的關(guān)聯(lián)數(shù)據(jù)一同刪除(可能是多條數(shù)據(jù)).
作者表(多) 多對多 書籍表(多): 刪除關(guān)聯(lián)表中的記錄, 作者表與書籍表的記錄默認(rèn)不刪除.
由于多個表之間存在關(guān)聯(lián), 所有從多對多的關(guān)聯(lián)表開始入手, 否則刪除作者表, 出版社表, 都無法成功.
7.1 多對多刪除記錄
在Django ORM中, 處理多對多關(guān)系的刪除主要是通過操作模型實(shí)例來完成的, 而不是直接操作數(shù)據(jù)庫表.
使用remove(), clear()和set()方法可以靈活地管理多對多關(guān)系中的關(guān)聯(lián).
7.1.1 remove()方法
remove()方法: 用于從多對多關(guān)系中移除一個特定的關(guān)聯(lián)對象.
注意: remove()接受一個或多個模型實(shí)例作為參數(shù), 而不是ID.
如果嘗試移除一個不存在的關(guān)聯(lián), 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()) #
# 獲取作者實(shí)例
author = Author.objects.filter(name='kid').first()
print(author) # kid
# 將kid作者從天文選集的作者列表中刪除
res = book.author.remove(author)
print(res) # None
# 再次查看天文選集的所有作者
print(book.author.all()) #
# 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的實(shí)例
author = Author.objects.filter(name='kid').first()
print(author) # kid
# 查詢作者kid著作的所有書籍
print(author.authored_books.all()) #
# 獲取文學(xué)經(jīng)典的書籍對象
book = Book.objects.filter(title='文學(xué)經(jīng)典').first()
print(book)
# 將文學(xué)經(jīng)典從作者的著作列表中移除
res = author.authored_books.remove(book)
print(res) # None
# 查看文學(xué)經(jīng)典的作者列表
print(book.author.all()) #
# 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()方法: 用于刪除一個實(shí)例的所有多對多關(guān)聯(lián).
# 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()) #
# 將所有作者從天文選集的作者列表中刪除
res = book.author.clear()
print(res) # None
# 查看天文選集的所有作者
print(book.author.all()) #
# 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的實(shí)例
author = Author.objects.filter(name='kid').first()
# 查詢作者kid著作的所有書籍
print(author.authored_books.all()) #
# 清除作者的所有著作
res = author.authored_books.clear()
print(res)
# 查詢作者kid著作的所有書籍
print(author.authored_books.all()) #
# ORM日志:
(0.000)
DELETE FROM `index_bookauthor` WHERE `index_bookauthor`.`author_id` = 1;
args=(1,)
7.1.3 set()方法
雖然set()方法本身不直接用于刪除關(guān)聯(lián), 但可以通過傳遞一個空的可迭代對象(如空列表)來間接地清除所有關(guān)聯(lián).
* set()方法會先刪除現(xiàn)有的關(guān)聯(lián), 再添加新的關(guān)聯(lián).
# 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
# 獲取編程基礎(chǔ)的書籍對象
book = Book.objects.filter(title='編程基礎(chǔ)').first()
# 查看編程基礎(chǔ)的所有作者
print(book.author.all()) #
# 將所有作者從編程基礎(chǔ)的作者列表中刪除
res = book.author.set([])
print(res) # None
# 查看編程基礎(chǔ)的所有作者
print(book.author.all()) #
# 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的實(shí)例
author = Author.objects.filter(name='qq').first()
# 查詢作者qq著作的所有書籍
print(author.authored_books.all()) #
# 清除作者的所有著作
res = author.authored_books.set([])
print(res)
# 查詢作者qq著作的所有書籍
print(author.authored_books.all()) #
# 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 一對一刪除記錄
一對一關(guān)系通常通過OneToOneField實(shí)現(xiàn).
如果設(shè)置了on_delete參數(shù)為models.CASCADE, 則當(dāng)刪除主記錄時, 相關(guān)聯(lián)的記錄也會被自動刪除.
* 多對多中關(guān)聯(lián)表中的關(guān)聯(lián)的實(shí)例不能直接刪除, 需要先刪除關(guān)聯(lián). 一對一不會出現(xiàn)這樣的約束, 先刪誰都可以.
# 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時, 相關(guān)聯(lián)的AuthorDetail也會被刪除
Author.objects.filter(name='blue').delete()
# 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時, 相關(guān)聯(lián)的author不受影響
AuthorDetail.objects.filter(id=2).delete()
# ORM日志:
(0.000) DELETE FROM `index_authordetail` WHERE `index_authordetail`.`id` = 2; args=(2,)
反向查詢獲取到的實(shí)例是次表的, 刪除次表的記錄對主表沒有任何影響.
# 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的實(shí)例
author = Author.objects.filter(id=3).first()
print(author)
# 反向查詢刪除次表的數(shù)據(jù), 主表不受影響
author.author_detail.delete()
# ORM日志:
(0.000) DELETE FROM `index_authordetail` WHERE `index_authordetail`.`id` IN (3); args=(3,)
7.2 一對多刪除記錄
一對多關(guān)系通常通過ForeignKey實(shí)現(xiàn).
如果設(shè)置了on_delete參數(shù)為models.CASCADE, 則當(dāng)刪除主記錄時, 相關(guān)聯(lián)的記錄也會被自動刪除.
刪除次表的記錄對主表沒有任何影響.
# 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時, 相關(guān)聯(lián)的book也會被刪除
res = Publisher.objects.filter(name='上海出版社').delete()
print(res) # (3, {'index.Book': 2, 'index.Publisher': 1}) 刪除了三條記錄Book表中兩條, Publisher表中一條
# 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,)
柚子快報邀請碼778899分享:26.8 Django多表操作
推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。