Django模型層之多表操做

Django模型層之多表操做

一 、表關係回顧

undefined

在講解MySQL時,咱們提到,把應用程序的全部數據都放在一張表裏是極不合理的 。python

好比咱們開發一個員工管理系統,在數據庫裏只建立一張員工信息表,該表有四個字段:工號、姓名、部門名、部門職能描述,此時若公司有1萬名員工,但只有3個部門,由於每一名員工後都須要跟着部門信息(部門名、部門職能),因此將會致使部門信息出現大量重複、浪費空間。mysql

undefined

解決方法就是將數據存放於不一樣的表中,而後基於foreign key創建表之間的關聯關係。git

細說的話,表之間存在三種關係:多對1、一對1、多對多,那如何肯定兩張表之間的關係呢?按照下述步驟操做便可sql

左表<---------------------------------------------->右表

# 步驟一:先分析
# 分析一、先站在左表的角度
是否左表的多條記錄能夠對應右表的一條記錄

# 分析2:再站在右表的角度去找
是否右表的多條記錄能夠對應左表的一條記錄

# 步驟二:後肯定關係
# 多對一
若是隻有"分析1"成立,那麼能夠肯定兩張表的關係是:左表多對一右表,關聯字段應該建立在左表中,而後foreign key 右表一個字段(一般是id)
若是隻有"分析2"成立,那麼能夠肯定兩張表的關係是:右表多對一左表,關聯字段應該建立在右表中,而後foreign key 左表一個字段(一般是id)

# 一對一
若是"分析1"和"分析2"都不成立,而是左表的一條記錄惟一對應右表的一條記錄,反之亦然。這種狀況很簡單,就是在左表foreign key右表的基礎上,將左表的關聯字段設置成unique便可

# 多對多
若是"分析1"和"分析2"同時成立,則證實這兩張表是一個雙向的多對一,即多對多,須要建立一張單獨的新表來專門存放兩者的關係,關聯字段應該建立在新表中,而後在新表中分別foreign key兩張表的id字段

咱們以一個圖書管理系統爲背景,設計了下述四張表,讓咱們來找一找它們之間的關係數據庫

書籍表:app01_bookdjango

出版社表:app01_publishapp

做者表:app01_author設計

做者詳細信息表:app01_authordetailcode

例 1

app01_bookapp01_publishorm

找關係

左表(app01_book)<------------------------------->右表(app01_publish)

# 步驟一:
# 分析一、先站在左表的角度
左表的多條記錄表明多版本書籍,右表的一條記錄表明一個出版社,多本書籍對應同一個出版社 ✔️

#分析二、再站在右表的角度去找
右表的多條記錄表明多個出版社,左表的一條記錄表明一本書,多個出版社不能出版同一本書 ✘

# 步驟二:後肯定關係
# 多對一
只有"分析1"成立,那麼能夠肯定兩張表的關係是:左表(app01_book)多對一右表(app01_publish),關聯字段應該建立在左表(app01_book)中,而後foreign key 右表(app01_publish)的id字段

sql語句

# 一、因爲foreign key的影響,必須先建立被關聯表
CREATE TABLE app01_publish (
    id INT PRIMARY KEY auto_increment,
    name VARCHAR (20)
);

# 二、才能建立出關聯表
CREATE TABLE app01_book (
    id INT PRIMARY KEY auto_increment,
    title VARCHAR (20),
    price DECIMAL (8, 2),
    pub_date DATE,
    publish_id INT, # 新增關聯字段
    FOREIGN KEY (publish_id) REFERENCES app01_publish (id) 
    ON UPDATE CASCADE ON DELETE CASCADE
);

例 2

app01_authorapp01_authordetail

找關係

左表(app01_author)<------------------------------->右表(app01_authordetail)

一個做者惟一對應一條本身的詳情信息,反之亦然,因此兩張表是一對一的關係。在左表中新增關聯字段並添加unique約束,而後foreign key右表

sql語句

# 一、因爲foreign key的影響,必須先建立被關聯表
CREATE TABLE app01_authordetail (
    id INT PRIMARY KEY auto_increment,
    tel VARCHAR (20)
);

# 二、才能建立出關聯表
CREATE TABLE app01_author (
    id INT PRIMARY KEY auto_increment,
    name VARCHAR (20),
    age INT,
    authordetail_id INT UNIQUE, # 新增關聯字段,並添加惟一性約束unique
    FOREIGN KEY (authordetail_id) REFERENCES app01_authordetail (id) 
    ON UPDATE CASCADE ON DELETE CASCADE
);

例 3

app01_bookapp01_author

找關係

左表(app01_book)<------------------------------->右表(app01_author)

# 步驟一:
# 分析一、先站在左表的角度
左表的多條記錄表明多版本書籍,右表的一條記錄表明一個做者,多本書籍能夠由同一個做者編寫 ✔️

# 分析二、再站在右表的角度去找
右表的多條記錄表明多個做者,左表的一條記錄表明一本書,多個做者能夠合做編寫同一本書 ✔️

# 步驟二:後肯定關係
# 多對多
"分析1"和"分析2"同時成立,證實這兩張表是多對多的關係,須要建立一張單獨的新表來專門存放兩者的關係,關聯字段應該建立在新表中,而後在新表中分別foreign key兩張表的id字段

sql語句

# 一、建立被關聯表一:app01_book,例1中已建立
# 二、建立被關聯表二:app01_author,例2中已建立

# 三、建立新表,存放app01_book於app01_author的關聯關係
CREATE TABLE app01_book_authors (
    id INT PRIMARY KEY auto_increment,
    book_id INT, # 新增關聯字段,用來關聯表app01_book
    author_id INT, # 新增關聯字段,用來關聯表app01_author
    FOREIGN KEY (book_id) REFERENCES app01_book (id) ON UPDATE CASCADE ON DELETE CASCADE,
    FOREIGN KEY (author_id) REFERENCES app01_author (id) ON UPDATE CASCADE ON DELETE CASCADE
);

上述三個例子中生成的表以下

undefined

二 、建立模型

模型類以下

from django.db import models

# 表app01_publish
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)


# 表app01_book
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=20)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    pub_date = models.DateField()

    # 表app01_book多對一表app01_publish,參數to指定模型名,參數to_field指定要關聯的那個字段
    publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE)

    # 咱們本身寫sql時,針對書籍表與做者表的多對關係,須要本身建立新表,而基於django的orm,下面這一行代碼能夠幫咱們自動建立那張關係表
    authors=models.ManyToManyField(to='Author') 
    # 變量名爲authors,則新表名爲app01_book_authors,若變量名爲xxx,則新表名爲app01_book_xxx


# 表app01_author
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()

    # 表app01_author一對一表app01_authordetail
    author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE)


# 表app01_authordetail
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    tel = models.CharField(max_length=20)

強調

在建立關聯時,針對參數to,若是傳入的是字符串(to="模型名"),則模型類的定義不區分前後順序,若是傳入的是模型名(to=Author),則Author類必須事先定義

三 、添加 、刪除 、修改記錄

3.1 添加記錄

undefined

!!強調!!:上圖所示的表名、字段名都是mysql中的真實表/物理表,而咱們下述所示全部操做,都是經過模型類來操做物理表,例如不管增刪改查,所使用的字段名都模型類中的字段

按照上圖所示,因爲foreign key的關係,咱們須要事先往app01_publishapp01_authordetail裏插入記錄

# 一、需求:經過模型Publish往表app01_publish裏插入三家出版社
Publish.objects.create(name='北京出版社')
Publish.objects.create(name='長春出版社')
Publish.objects.create(name='大連出版社')

# 二、需求:經過模型AuthorDetail往表app01_authordetail裏插入三條做者詳情
AuthorDetail.objects.create(tel='18611312331')
AuthorDetail.objects.create(tel='15033413881')
AuthorDetail.objects.create(tel='13011453220')

按照上圖所示,插入時會涉及到多張表,咱們一樣分三種狀況來介紹

  1. 多對一:app01_bookapp01_publish
# 需求:書籍(葵花寶典、菊花寶典、桃花寶典)都是在北京出版社出版的
# 一、先經過模型Publish從出版社表app01_publish查出北京出版社
publish_obj=Publish.objects.filter(name='北京出版社').first()
# 上述代碼也能夠簡寫爲:publish_obj=Publish.objects.get(name='北京出版社')

# 二、再經過模型Book往書籍表app01_book裏插入三本書籍與出版社的對應關係
# 方式一:使用publish參數指定關聯
book_obj1=Book.objects.create(title='葵花寶典',price=2000,pub_date='1985-3-11',publish=publish_obj)

book_obj2=Book.objects.create(title='菊花寶典',price=3000,pub_date='1990-1-21',publish=publish_obj)

book_obj3=Book.objects.create(title='桃花寶典',price=4000,pub_date='1991-1-23',publish=publish_obj)

# 方式二:使用publish_id參數指定關聯
book_obj1=Book.objects.create(title='葵花寶典',price=2000,pub_date='1985-3-11',publish_id=publish_obj.nid)

book_obj2=Book.objects.create(title='菊花寶典',price=3000,pub_date='1990-1-21',publish_id=1) # 在已經出版社id的狀況下,能夠直接指定

book_obj3=Book.objects.create(title='桃花寶典',price=4000,pub_date='1991-1-23',publish_id=1)

# 注意:不管方式一仍是方式二獲得的書籍對象book_obj一、book_obj二、book_obj3
#      均可以調用publish字段來訪問關聯的那一個出版社對象
#      均可以調用publish_id來訪問關聯的那一個出版社對象的nid
print(book_obj1.publish,book_obj1.publish_id) 
print(book_obj2.publish,book_obj2.publish_id) 
print(book_obj3.publish,book_obj3.publish_id)
# 三本書關聯的是同一個出版社,因此輸出結果均相同

參照上述步驟,把剩餘三本書與出版社的對應關係也插入

book_obj1 = Book.objects.create(title='玉女心經', price=5000, pub_date='1988-3-24', publish_id=2)

book_obj2 = Book.objects.create(title='玉男心經', price=3000, pub_date='1985-6-17', publish_id=2)

book_obj3 = Book.objects.create(title='九陰真經', price=6000, pub_date='1983-8-17', publish_id=3)
  1. 一對一:app01_authorapp01_authordetail
# 需求:插入三個做者,並與做者詳情表一一對應
# 因爲做者詳情表咱們已經事先建立好記錄,因此只須要往做者表插入記錄便可
# 方式一:須要事先過濾出做者詳情的對象,而後經過模型Author的字段author來指定要關聯的做者詳情對象(略)

# 方式二:肯定做者詳情對象的id,而後經過模型Author經過字段author_id來指定關聯關係,
Author.objects.create(name='egon',age=18,author_detail_id=1)
Author.objects.create(name='kevin',age=38,author_detail_id=2)
Author.objects.create(name='rose',age=28,author_detail_id=3)
  1. 多對多:app01_bookapp01_author
# 咱們參照物理表app01_book_authors制定需求,須要建立以下關係
# 一、葵花寶典的做者爲:egon、kevin
# 二、菊花寶典的做者爲:egon、kevin、rose
# 三、桃花寶典的做者爲:egon、kevin
# 四、玉女心經的做者爲:kevin、rose
# 五、玉男心經的做者爲:kevin
# 六、九陰真經的做者爲:egon、rose

# 須要建立出上述關係,具體作法以下
# 一、先獲取書籍對象
book_obj1=Book.objects.get(title='葵花寶典')
book_obj2=Book.objects.get(title='菊花寶典')
book_obj3=Book.objects.get(title='桃花寶典')
book_obj4=Book.objects.get(title='玉女心經')
book_obj5=Book.objects.get(title='玉男心經')
book_obj6=Book.objects.get(title='九陰真經')

# 二、而後獲取做者對象
egon=Author.objects.get(name='egon')
kevin=Author.objects.get(name='kevin')
rose=Author.objects.get(name='rose')

# 三、最後依次建立上述關係:在原生SQL中多對多關係涉及到操做第三張關係表,可是在ORM中咱們只須要操做模型類Book下的字段author便可
book_obj1.authors.add(egon,kevin)
book_obj2.authors.add(egon,kevin,rose)
book_obj3.authors.add(egon,kevin)
book_obj4.authors.add(kevin,rose)
book_obj5.authors.add(kevin)
book_obj6.authors.add(egon,rose)

能夠經過書籍對象下的authors字段獲取其所關聯的全部做者對象

book_obj1.authors.all() # 返回一個存有多個做者的queryset

3.2 刪除 、修改記錄

undefined

# 一、book_obj.authors.remove() :將某個特定的對象從被關聯對象集合中去除
# 從菊花寶典的做者集合中去掉做者rose
rose = Author.objects.get(name='rose')
book_obj2 = Book.objects.get(title='菊花寶典')
book_obj2.authors.remove(rose)

# 二、book_obj.authors.clear():清空被關聯對象集合
# 清空菊花寶典所關聯的全部做者
book_obj2 = Book.objects.get(title='菊花寶典')
book_obj2.authors.clear()

# 三、book_obj.authors.set():先清空再從新設置 
# 玉男心經的做者原來爲kevin,要求設置爲egon、rose
egon=Author.objects.get(name='egon')
rose=Author.objects.get(name='rose')

book_obj5 = Book.objects.get(title='玉男心經')

book_obj5.authors.set([egon,rose]) # 多個做者對象放到列表裏

四 、查詢記錄

數據庫操做最經常使用的仍是查詢操做,在介紹ORM下多表關聯查詢時,須要事先記住關於ORM模型的一個很是重要的概念:在使用模型類進行多表關聯查詢時,若是肯定兩張表存在關聯關係,那麼在選取一個表做爲起始(爲了後續描述方便,咱們將其簡稱爲「基表」)後,能夠跨表引用來自另一張中的字段值,這存在正向與反向之分

若是關聯字段存在於基表中,稱之爲正向查詢,不然,稱之爲反向查詢

例如表模BookPublish,關聯字段存在於Book

# 當以Book爲基表時,稱之爲正向查詢
Book(基表)-------正向---------->Publish

# 當以Publish爲基表時,稱之爲反向查詢
Book<-------反向----------Publish(基表)

使用原生sql進行多表關聯查詢時無非兩種方式:子查詢、join連表查詢,ORM裏一樣有兩種查詢方式(嚴格依賴正向、反向的概念)

4.1 基於對象的跨表查詢

undefined

4.1.1 跨兩張表查詢

4.1.1.1 一對一查詢(模型類AuthorAuthorDetail

正向查詢,按關聯字段:author_detail

# 需求:查詢做者egon的手機號
# 一、先取出做者對象
egon=Author.objects.filter(name='egon').first() 
# 二、正向查詢:根據做者對象下的關聯字段author_detail取到做者詳情
print(egon.author_detail.tel) # 輸出:18611312331

反向查詢,按模型名(小寫):author

# 需求:查詢手機號爲'18611312331'的做者名
# 一、先取出做者的詳情對象
tel=AuthorDetail.objects.filter(tel='18611312331').first()
# 二、反向查詢:根據小寫的模型名author取到做者對象
print(tel.author.name) # 輸出:egon
4.1.1.2 多對一查詢(模型類BookPublish

正向查詢,按關聯字段:publish

# 需求:查詢葵花寶典的出版社名字
# 一、先取書籍對象
book_obj=Book.objects.filter(title='葵花寶典').first()
# 二、正向查詢:根據書籍對象下的關聯字段publish取到出版社
print(book_obj.publish.name) # 輸出:北京出版社

反向查詢,按模型名(小寫)_setbook_set

# 需求:查詢北京出版社都出版的全部書籍名字
# 一、先取出出版社對象
publish_obj=Publish.objects.filter(name='北京出版社').first()
# 二、反向查詢:根據book_set取到全部的書籍
book_objs=publish_obj.book_set.all()
print([book_obj.title for book_obj in book_objs]) # 輸出:['葵花寶典', '菊花寶典', '桃花寶典']
4.1.1.3 多對多查詢(模型類BookAuthor

正向查詢,按關聯字段,如authors

# 需求:查詢葵花寶典的全部做者
# 一、先取出書籍對象
book_obj=Book.objects.filter(title='葵花寶典').first()
# 二、正向查詢:根據書籍對象下的關聯字段authors取到全部做者
author_objs=book_obj.authors.all()
print([obj.name for obj in author_objs]) # 輸出:['egon', 'kevin']

反向查詢,按模型名(小寫)_set:如author_set

# 需求:查詢做者rose出版的全部書籍
# 一、先取出做者對象
egon=Author.objects.filter(name='rose').first() 
# 二、反向查詢:根據book_set取到做者對象
book_objs=egon.book_set.all()
print([book_obj.title for book_obj in book_objs]) # 輸出:['玉女心經', '九陰真經', '玉男心經']

4.1.2 連續跨> 2張表查詢

連續跨 > 2張表的操做的套路與上面的案例都是同樣的

# 需求:查詢葵花寶典的做者們的手機號

book_obj=Book.objects.filter(title='葵花寶典').first()
author_objs=book_obj.authors.all()
print([author_obj.author_detail.tel for author_obj in author_objs]) 
# 輸出:['18611312331', '15033413881']

4.2 基於雙下劃線的跨表查詢

4.2.1 跨兩張表查詢

4.2.1.1 一對一查詢(模型類AuthorAuthorDetail

正向查詢,按關聯字段+雙下劃線:author_detail__

# 需求:查詢做者egon的手機號
# 注意values()中的參數是:關聯字段名__要取的那張被關聯表中的字段
res = Author.objects.filter(name='egon').values('author_detail__tel').first()
print(res['author_detail__tel']) # {'author_detail__tel': '18611312331'}

# 注意:基於雙下劃線的跨表查詢會被django的orm識別爲join操做,因此上述代碼至關於以下sql,後續案例均是相同原理,咱們再也不累述
select app01_authordetail.tel from app01_author inner join app01_authordetail on app01_author.author_detail_id = app01_authordetail.nid where app01_author.name = 'egon';

反向查詢,按模型名(小寫)+雙下劃線author__

# 需求:查詢手機號爲'18611312331'的做者名
# 注意values()中的參數是:小寫的模型名__要取的那張被關聯表中的字段
res=AuthorDetail.objects.filter(tel='18611312331').values('author__name').first()
print(res) # {'author__name': 'egon'}

補充:基表決定了正向仍是反向

# 一、針對上例中正向查詢的需求:查詢做者egon的手機號,若是咱們選取的基表是AuthorDetail,那麼就成了反向查詢,應該用反向查詢的語法
res = AuthorDetail.objects.filter(author__name='egon').values('tel').first()
print(res)  # {'tel': '18611312331'}

# 二、針對上例中反向查詢的需求:查詢手機號爲'18611312331'的做者名,若是咱們選取的基表是Author,那麼就成了正向查詢,應該用正向查詢的語法
res=Author.objects.filter(author_detail__tel='18611312331').values('name').first()
print(res) # {'name': 'egon'}
4.2.1.2 多對一查詢(模型類BookPublish

正向查詢,按關聯字段+雙下劃線:publish__

# 需求:查詢葵花寶典的出版社名字
# 注意values()中的參數是:關聯字段名__要取的那張被關聯表中的字段
res=Book.objects.filter(title='葵花寶典').values('publish__name').first()
print(res['publish__name']) # {'publish__name': '北京出版社'}

反向查詢,按模型名(小寫)+雙下劃線:book__

# 需求:查詢北京出版社都出版的全部書籍名字
# 注意values()中的參數是:小寫的模型名__要取的那張被關聯表中的字段
res = Publish.objects.filter(name='北京出版社').values('book__title')
print(res)  # <QuerySet [{'book__title': '葵花寶典'}, {'book__title': '菊花寶典'}, {'book__title': '桃花寶典'}]>

補充:基表決定了正向仍是反向

# 一、針對上例中正向查詢的需求:查詢葵花寶典的出版社名字,若是咱們選取的基表是Publish,那麼就成了反向查詢,應該用反向查詢的語法
res = Publish.objects.filter(book__title='葵花寶典').values('name').first()
print(res)  # {'name': '北京出版社'}

# 二、針對上例中反向查詢的需求:查詢北京出版社都出版的全部書籍名字,若是咱們選取的基表是Book,那麼就成了正向查詢,應該用正向查詢的語法
res=Book.objects.filter(publish__name='北京出版社').values('title')
print(res) # <QuerySet [{'title': '葵花寶典'}, {'title': '菊花寶典'}, {'title': '桃花寶典'}]>
4.2.1.3 多對多查詢(模型類BookAuthor

正向查詢,按關聯字段+雙下劃線:authors__

# 需求:查詢葵花寶典的全部做者
# 注意values()中的參數是:關聯字段名__要取的那張被關聯表中的字段
res=Book.objects.filter(title='葵花寶典').values('authors__name')
print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'kevin'}]>

反向查詢,按模型名(小寫)+雙下劃線:如book__

# 需求:查詢做者rose出版的全部書籍
# 注意values()中的參數是:小寫的模型名__要取的那張被關聯表中的字段
res = Author.objects.filter(name='rose').values('book__title')
print(res) # <QuerySet [{'book__title': '玉女心經'}, {'book__title': '九陰真經'}, {'book__title': '玉男心經'}]>

補充:基表決定了正向仍是反向

# 一、針對上例中正向查詢的需求:查詢葵花寶典的全部做者,若是咱們選取的基表是authors,那麼就成了反向查詢,應該用反向查詢的語法
res=Author.objects.filter(book__title='葵花寶典').values('name')
print(res) # <QuerySet [{'name': 'egon'}, {'name': 'kevin'}]>

# 二、針對上例中反向查詢的需求:查詢做者rose出版的全部書籍,若是咱們選取的基表是Book,那麼就成了正向查詢,應該用正向查詢的語法
res=Book.objects.filter(authors__name='rose').values('title')
print(res) # <QuerySet [{'title': '玉女心經'}, {'title': '九陰真經'}, {'title': '玉男心經'}]>

4.2.2 連續跨> 2張表查詢

連續跨> 2張表的操做的套路與上面的案例都是同樣的,能夠連續接n個雙下劃線,只須要在每次連雙下劃線時,肯定是正向仍是反向便可

# 需求1:查詢北京出版社出版過的全部書籍的名字以及做者的姓名、手機號
# 方式一:基表爲Publish
res=Publish.objects.filter(name='北京出版社').values_list('book__title','book__authors__name','book__authors__author_detail__tel')

# 方式二:基表爲Book
res=Book.objects.filter(publish__name='北京出版社').values_list('title','authors__name','authors__author_detail__tel')

# 循環打印結果均爲
for obj in res:
    print(obj)
'''
輸出:
('葵花寶典', 'egon', '18611312331')
('菊花寶典', 'egon', '18611312331')
('桃花寶典', 'egon', '18611312331')
('葵花寶典', 'kevin', '15033413881')
('菊花寶典', 'kevin', '15033413881')
('桃花寶典', 'kevin', '15033413881')
('菊花寶典', 'rose', '13011453220')
'''



# 需求2:查詢手機號以186開頭的做者出版過的全部書籍名稱以及出版社名稱
# 方式一:基表爲AuthorDetail
res=AuthorDetail.objects.filter(tel__startswith='186').values_list('author__book__title','author__book__publish__name')

# 方式二:基表爲Book
res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('title','publish__name')

# 方式三:基表爲Publish
res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_list('book__title','name')

# 循環打印結果均爲
for obj in res:
    print(obj)
'''
輸出:
('葵花寶典', '北京出版社')
('菊花寶典', '北京出版社')
('桃花寶典', '北京出版社')
('九陰真經', '大連出版社')
'''

undefined

相關文章
相關標籤/搜索