在講解MySQL
時,咱們提到,把應用程序的全部數據都放在一張表裏是極不合理的 。python
好比咱們開發一個員工管理系統,在數據庫裏只建立一張員工信息表,該表有四個字段:工號、姓名、部門名、部門職能描述,此時若公司有1萬名員工,但只有3個部門,由於每一名員工後都須要跟着部門信息(部門名、部門職能),因此將會致使部門信息出現大量重複、浪費空間。mysql
解決方法就是將數據存放於不一樣的表中,而後基於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_book
django
出版社表:app01_publish
app
做者表:app01_author
設計
做者詳細信息表:app01_authordetail
code
app01_book
與app01_publish
orm
找關係
左表(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 );
app01_author
與app01_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 );
app01_book
與app01_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 );
上述三個例子中生成的表以下
模型類以下
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類必須事先定義
!!強調!!:上圖所示的表名、字段名都是mysql
中的真實表/物理表,而咱們下述所示全部操做,都是經過模型類來操做物理表,例如不管增刪改查,所使用的字段名都模型類中的字段
按照上圖所示,因爲foreign key
的關係,咱們須要事先往app01_publish
與app01_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')
按照上圖所示,插入時會涉及到多張表,咱們一樣分三種狀況來介紹
app01_book
與app01_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)
app01_author
與app01_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)
app01_book
與app01_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
# 一、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
模型的一個很是重要的概念:在使用模型類進行多表關聯查詢時,若是肯定兩張表存在關聯關係,那麼在選取一個表做爲起始(爲了後續描述方便,咱們將其簡稱爲「基表」)後,能夠跨表引用來自另一張中的字段值,這存在正向與反向之分
若是關聯字段存在於基表中,稱之爲正向查詢,不然,稱之爲反向查詢
例如表模Book
與Publish
,關聯字段存在於Book
中
# 當以Book爲基表時,稱之爲正向查詢 Book(基表)-------正向---------->Publish # 當以Publish爲基表時,稱之爲反向查詢 Book<-------反向----------Publish(基表)
使用原生sql
進行多表關聯查詢時無非兩種方式:子查詢、join
連表查詢,ORM
裏一樣有兩種查詢方式(嚴格依賴正向、反向的概念)
Author
與AuthorDetail
)正向查詢,按關聯字段: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
Book
與Publish
)正向查詢,按關聯字段:publish
# 需求:查詢葵花寶典的出版社名字 # 一、先取書籍對象 book_obj=Book.objects.filter(title='葵花寶典').first() # 二、正向查詢:根據書籍對象下的關聯字段publish取到出版社 print(book_obj.publish.name) # 輸出:北京出版社
反向查詢,按模型名(小寫)_set
:book_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]) # 輸出:['葵花寶典', '菊花寶典', '桃花寶典']
Book
與Author
)正向查詢,按關聯字段,如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]) # 輸出:['玉女心經', '九陰真經', '玉男心經']
> 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']
Author
與AuthorDetail
)正向查詢,按關聯字段+雙下劃線: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'}
Book
與Publish
)正向查詢,按關聯字段+雙下劃線: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': '桃花寶典'}]>
Book
與Author
)正向查詢,按關聯字段+雙下劃線: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': '玉男心經'}]>
> 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) ''' 輸出: ('葵花寶典', '北京出版社') ('菊花寶典', '北京出版社') ('桃花寶典', '北京出版社') ('九陰真經', '大連出版社') '''