Django模型層:多表查詢
一 建立模型
例項:我們來假定下面這些概念,欄位和關係
作者模型:一個作者有姓名和年齡。
作者詳細模型:把作者的詳情放到詳情表,包含生日,手機號,家庭住址等資訊。作者詳情模型和作者模型之間是一對一的關係(one-to-one)
出版商模型:出版商有名稱,所在城市以及email。
書籍模型: 書籍有書名和出版日期,一本書可能會有多個作者,一個作者也可以寫多本書,所以作者和書籍的關係就是多對多的關聯關係(many-to-many);一本書只應該由一個出版商出版,所以出版商和書籍是一對多關聯關係(one-to-many)。
Book id title price publish phpView Code100 人民出版社 python 200 老男孩出版社 go 100 人民出版社 java 300 人民出版社 為了儲存出版社的郵箱,地址,在第一個表後面加欄位 Book id title price publish email addr php 100 人民出版社 111 北京 python 200 老男孩出版社 222 上海 go 100 人民出版社 111 北京 java300 人民出版社 111 北京 這樣會有大量重複的資料,浪費空間 #################################################################################### 一對多:一個出版社對應多本書(關聯資訊建在多的一方,也就是book表中) Book id title price publish_id php 100 1 python 200 1 go 100 2 java300 1 Publish id name email addr 人民出版社 111 北京 沙河出版社 222 沙河 總結:一旦確定表關係是一對多:在多對應的表中建立關聯欄位(在多的表裡建立關聯欄位) ,publish_id 查詢python這本書的出版社的郵箱(子查詢) select publish_id from Book where title=“python” select email from Publish where id=1 #################################################################################### 多對多:一本書有多個作者,一個作者出多本書 Book id title price publish_id php 100 1 python 200 1 go 100 2 java 300 1 Author id name age addr alex 34 beijing egon 55 nanjing Book2Author id book_id author_id 2 1 2 2 3 2 總結:一旦確定表關係是多對多:建立第三張關係表(建立中間表,中間表就三個欄位,自己的id,書籍id和作者id) : id book_id author_id # alex出版過的書籍名稱(子查詢) select id from Author where name='alex' select book_id from Book2Author where author_id=1 select title from Book where id =book_id #################################################################################### 一對一:對作者詳細資訊的擴充套件(作者表和作者詳情表) Author id name age ad_id(UNIQUE) alex 34 1 egon 55 2 AuthorDetail id addr gender tel gf_name author_id(UNIQUE) beijing male 110 小花 1 nanjing male 911 槓娘 2 總結: 一旦確定是一對一的關係:在兩張表中的任意一張表中建立關聯欄位+Unique ==================================== Publish Book Author AuthorDetail Book2Author CREATE TABLE publish( id INT PRIMARY KEY auto_increment , name VARCHAR (20) ); CREATE TABLE 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 publish(id) ); CREATE TABLE authordetail( id INT PRIMARY KEY auto_increment , tel VARCHAR (20) ); CREATE TABLE author( id INT PRIMARY KEY auto_increment , name VARCHAR (20), age INT, authordetail_id INT UNIQUE , FOREIGN KEY (authordetail_id) REFERENCES authordetail(id) ); CREATE TABLE book2author( id INT PRIMARY KEY auto_increment , book_id INT , author_id INT , FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (author_id) REFERENCES author(id) ) 分析如下
注意:關聯欄位與外來鍵約束沒有必然的聯絡(建管理欄位是為了進行查詢,建約束是為了不出現髒資料)
在Models建立如下模型
class Book(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) publish_date = models.DateField() # 閱讀數 # reat_num=models.IntegerField(default=0) # 評論數 # commit_num=models.IntegerField(default=0) publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE) authors=models.ManyToManyField(to='Author') def __str__(self): return self.name class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() author_detail = models.OneToOneField(to='AuthorDatail',to_field='nid',unique=True,on_delete=models.CASCADE) class AuthorDatail(models.Model): nid = models.AutoField(primary_key=True) telephone = models.BigIntegerField() birthday = models.DateField() addr = models.CharField(max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField()
生成的表如下:
注意事項:
- 表的名稱
myapp_modelName
,是根據 模型中的元資料自動生成的,也可以覆寫為別的名稱 id
欄位是自動新增的- 對於外來鍵欄位,Django 會在欄位名上新增"_id" 來建立資料庫中的列名
- 這個例子中的
CREATE TABLE
SQL 語句使用PostgreSQL 語法格式,要注意的是Django 會根據settings 中指定的資料庫型別來使用相應的SQL 語句。 - 定義好模型之後,你需要告訴Django _使用_這些模型。你要做的就是修改配置檔案中的INSTALL_APPSZ中設定,在其中新增
models.py
所在應用的名稱。 - 外來鍵欄位 ForeignKey 有一個 null=True 的設定(它允許外來鍵接受空值 NULL),你可以賦給它空值 None 。
二 新增表記錄
一對多的
方式1: publish_obj=Publish.objects.get(nid=1) book_obj=Book.objects.create(title="",publishDate="2012-12-12",price=100,publish=publish_obj) 方式2: book_obj=Book.objects.create(title="",publishDate="2012-12-12",price=100,publish_id=1)
核心:book_obj.publish與book_obj.publish_id是什麼?
關鍵點: 一 book_obj.publish=Publish.objects.filter(id=book_obj.publish_id).first() 二 book_obj.authors.all() 關鍵點:book.authors.all() # 與這本書關聯的作者集合 book.id=3 book_authors id book_id author_ID 3 1 3 2 author id name alex egon book_obj.authors.all() -------> [alex,egon]
多對多
# 當前生成的書籍物件 book_obj=Book.objects.create(title="追風箏的人",price=200,publishDate="2012-11-12",publish_id=1) # 為書籍繫結的做作者物件 yuan=Author.objects.filter(name="yuan").first() # 在Author表中主鍵為2的紀錄 egon=Author.objects.filter(name="alex").first() # 在Author表中主鍵為1的紀錄 # 繫結多對多關係,即向關係表book_authors中新增紀錄 book_obj.authors.add(yuan,egon) # 將某些特定的 model 物件新增到被關聯物件集合中。 ======= book_obj.authors.add(*[])
book = Book.objects.filter(name='紅樓夢').first() egon=Author.objects.filter(name='egon').first() lqz=Author.objects.filter(name='lqz').first() # 1 沒有返回值,直接傳物件 book.authors.add(lqz,egon) # 2 直接傳作者id book.authors.add(1,3) # 3 直接傳列表,會打散 book.authors.add(*[1,2]) # 解除多對多關係 book = Book.objects.filter(name='紅樓夢').first() # 1 傳作者id book.authors.remove(1) # 2 傳作者物件 egon = Author.objects.filter(name='egon').first() book.authors.remove(egon) #3 傳*列表 book.authors.remove(*[1,2]) #4 刪除所有 book.authors.clear() # 5 拿到與 這本書關聯的所有作者,結果是queryset物件,作者列表 ret=book.authors.all() # print(ret) # 6 queryset物件,又可以繼續點(查詢紅樓夢這本書所有作者的名字) ret=book.authors.all().values('name') print(ret) # 以上總結: # (1) # book=Book.objects.filter(name='紅樓夢').first() # print(book) # 在點publish的時候,其實就是拿著publish_id又去app01_publish這個表裡查資料了 # print(book.publish) # (2)book.authors.all()
核心:book_obj.authors.all()是什麼?
多對多關係其它常用API:
book_obj.authors.remove() # 將某個特定的物件從被關聯物件集合中去除。 ====== book_obj.authors.remove(*[]),可以指定解除關聯物件的id值 book_obj.authors.clear() #清空被關聯物件集合,即與書這個物件相關聯的作者的繫結關係都被清除 book_obj.authors.set() #先清空再設定,在重新設定繫結關係
三 基於物件的跨表查詢
一對一查詢(Author 與 AuthorDetail)
正向查詢(按欄位:authorDetail):
egon=Author.objects.filter(name="egon").first() #拿到作者物件 print(egon.authorDetail.telephone) #按照作者表中的authorDetail欄位進行查詢
反向查詢(按表名:author):
# 查詢所有住址在北京的作者的姓名 authorDetail_list=AuthorDetail.objects.filter(addr="beijing") #先拿到地址為北京的左右作者的一個物件 for obj in authorDetail_list: print(obj.author.name) #通過作者詳細資訊點小寫表名就可以拿到作者這個物件 ''' A表Author(關聯自動段) B表 AuthorDetail # 正向查詢 A--->B 關聯欄位再A,A去查詢B表,這叫正向查詢,按欄位來查 # 反向查詢 B--》A 關聯欄位再A,B去查詢A表,這叫反向查詢,按表明小寫 '''
# 一對一正向查詢 # lqz的手機號 lqz=Author.objects.filter(name='lqz').first() tel=lqz.author_detail.telephone print(tel) # 一對一反向查詢 # 地址在北京的作者姓名 author_detail=AuthorDatail.objects.filter(addr='北京').first() name=author_detail.author.name print(name)View Code
一對多查詢(publish與book)
正向查詢(按欄位:publish)
# 查詢主鍵為1的書籍的出版社所在的城市 book_obj=Book.objects.filter(pk=1).first() # book_obj.publish 是主鍵為1的書籍物件關聯的出版社物件 print(book_obj.publish.city)
反向查詢(按表名:book_set)
publish=Publish.objects.get(name="蘋果出版社") #publish.book_set.all() : 與蘋果出版社關聯的所有書籍物件集合 book_list=publish.book_set.all() for book_obj in book_list: print(book_obj.title) ''' A表book(關聯自動段) B表 publish # 正向查詢 A--->B 關聯欄位再A,A去查詢B表,這叫正向查詢,按欄位來查 # 反向查詢 B--》A 關聯欄位再A,B去查詢A表,這叫反向查詢,按表明小寫_set '''
# 一對多正向查詢 book=Book.objects.filter(name='紅樓夢').first() print(book.publish)#與這本書關聯的出版社物件 print(book.publish.name) # 一對多反向查詢 # 人民出版社出版過的書籍名稱 pub=Publish.objects.filter(name='人民出版社').first() ret=pub.book_set.all() print(ret)View Code
多對多查詢 (Author 與 Book)
正向查詢(按欄位:authors):
# 眉所有作者的名字以及手機號 book_obj=Book.objects.filter(title="眉").first() # authors=book_obj.authors.all() # for author_obj in authors: print(author_obj.name,author_obj.authorDetail.telephone) #
反向查詢(按表名:book_set):
# 查詢egon出過的所有書籍的名字 author_obj=Author.objects.get(name="egon") # book_list=author_obj.book_set.all() # #與egon作者相關的所有書籍 for book_obj in book_list: print(book_obj.title) # ''' A表book(關聯自動段) B表 publish # 正向查詢 A--->B # 反向查詢 B-->A 總結:一對一 正向:按欄位 反向:按表名小寫 一對多 正向:按欄位 反向:按表名小寫_set 多對多 正向:按欄位 反向:按表名小寫_set '''
# 正向查詢----查詢紅樓夢所有作者名稱 book=Book.objects.filter(name='紅樓夢').first() ret=book.authors.all() print(ret) for auth in ret: print(auth.name) # 反向查詢 查詢lqz這個作者寫的所有書 author=Author.objects.filter(name='lqz').first() ret=author.book_set.all() print(ret)View Code
注意:
你可以通過在 ForeignKey() 和ManyToManyField的定義中設定 related_name 的值來覆寫 FOO_set 的名稱。例如,如果 Article model 中做一下更改:
publish = ForeignKey(Book, related_name='bookList')
那麼接下來就會如我們看到這般:
# 查詢 人民出版社出版過的所有書籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 與人民出版社關聯的所有書籍物件集合
四 基於雙下劃線的跨表查詢
Django 還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯絡。要做跨關係查詢,就使用兩個下劃線來連結模型(model)間關聯欄位的名稱,直到最終連結到你想要的model 為止。
''' 正向查詢按欄位,反向查詢按表名小寫用來告訴ORM引擎join哪張表 '''
一對一查詢
# 查詢alex的手機號 # 正向查詢----按欄位 (Author--->authordetail) Author表中有一個authordetail關聯欄位 ret=Author.objects.filter(name="alex").values("authordetail__telephone")# # 反向查詢---按表名小寫(authordetail--->Author)通過表名小寫author拿到作者的的name欄位 ret=AuthorDetail.objects.filter(author__name="alex").values("telephone")#
# 查詢lqz的手機號 # 正向查 ret=Author.objects.filter(name='lqz').values('author_detail__telephone') print(ret) # 反向查 ret= AuthorDatail.objects.filter(author__name='lqz').values('telephone') print(ret)View Code
一對多查詢
# 練習: 查詢蘋果出版社出版過的所有書籍的名字與價格(一對多) # 正向查詢 按欄位:publish queryResult=Book.objects .filter(publish__name="蘋果出版社")#通過BOOK中的publish關聯欄位,拿到出版社的名字 .values_list("title","price") #values_list中傳的引數是我們要查詢的欄位 # 反向查詢 按表名:book queryResult=Publish.objects .filter(name="蘋果出版社") #出版社有name欄位,所以可以直接拿到name欄位 .values_list("book__title","book__price")#查詢的本質一樣,就是select from的表不一樣,Book表中的title欄位要通過book表名小寫__來拿到title欄位
# 正向查詢按欄位,反向查詢按表名小寫 # 查詢紅樓夢這本書出版社的名字 # select * from app01_book inner join app01_publish # on app01_book.publish_id=app01_publish.nid ret=Book.objects.filter(name='紅樓夢').values('publish__name') print(ret) ret=Publish.objects.filter(book__name='紅樓夢').values('name') print(ret)View Code
多對多查詢
# 練習: 查詢alex出過的所有書籍的名字(多對多) # 正向查詢 按欄位:authors: queryResult=Book.objects .filter(authors__name="yuan") #BOOK表中有關聯欄位author,所以可以通過關聯欄位author__來拿到作者的名字 .values_list("title")#正向是Book表,所以可以直接拿到書的標題title # 反向查詢 按表名:book queryResult=Author.objects .filter(name="yuan")#Author可以直接拿到作者的名字欄位name .values_list("book__title","book__price")#因為關聯欄位在Book表中,所以通過表名BOOk小寫__才能拿到Book表中的title和price欄位
# 正向查詢按欄位,反向查詢按表名小寫 # 查詢紅樓夢這本書出版社的名字 # select * from app01_book inner join app01_publish # on app01_book.publish_id=app01_publish.nid ret=Book.objects.filter(name='紅樓夢').values('publish__name') print(ret) ret=Publish.objects.filter(book__name='紅樓夢').values('name') print(ret) # sql 語句就是from的表不一樣 # -------多對多正向查詢 # 查詢紅樓夢所有的作者 ret=Book.objects.filter(name='紅樓夢').values('authors__name') print(ret) # ---多對多反向查詢 ret=Author.objects.filter(book__name='紅樓夢').values('name') ret=Author.objects.filter(book__name='紅樓夢').values('name','author_detail__addr') print(ret)View Code
總結:
''' 總結:用__告訴orm,要連線那個表 一對一: 正向:按欄位 反向:按表名小寫 一對多: 正向:按欄位 反向:按表名小寫 多對多: 正向:按欄位 反向:按表名小寫 '''
進階練習(連續跨表)
# 練習: 查詢人民出版社出版過的所有書籍的名字以及作者的姓名 # 正向查詢 queryResult=Book.objects .filter(publish__name="人民出版社") .values_list("title","authors__name") # 反向查詢 queryResult=Publish.objects .filter(name="人民出版社") .values_list("book__title","book__authors__age","book__authors__name") # 練習: 手機號以151開頭的作者出版過的所有書籍名稱以及出版社名稱 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="151") .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="151") .values("book__title","book__publish__name")
# ----進階練習,連續跨表 # 查詢手機號以33開頭的作者出版過的書籍名稱以及書籍出版社名稱 # author_datail author book publish # 基於authorDatail表 ret=AuthorDatail.objects.filter(telephone__startswith='33').values('author__book__name','author__book__publish__name') print(ret) # 基於Author表 ret=Author.objects.filter(author_detail__telephone__startswith=33).values('book__name','book__publish__name') print(ret) # 基於Book表 ret=Book.objects.filter(authors__author_detail__telephone__startswith='33').values('name','publish__name') print(ret) # 基於Publish表 ret=Publish.objects.filter(book__authors__author_detail__telephone__startswith='33').values('book__name','name') print(ret)View Code
related_name
publish = ForeignKey(Blog, related_name='bookList') #
# 練習: 查詢人民出版社出版過的所有書籍的名字與價格(一對多) # 反向查詢 不再按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name="人民出版社") .values_list("bookList__title","bookList__price")
注意: 反向查詢時,如果定義了related_name ,則用related_name替換表名
五 聚合查詢與分組查詢
聚合
aggregate(*args, **kwargs)
# 計算所有圖書的平均價格 from django.db.models import Avg # Book.objects.all().aggregate(Avg('price'))# #{'price__avg': 34.35}
aggregate()是QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的識別符號,值是計算出來的聚合值。鍵的名稱是按照欄位和聚合函式的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。
Book.objects.aggregate(average_price=Avg('price'))# #{'average_price': 34.35}
如果你希望生成不止一個聚合,你可以向aggregate()子句中新增另一個引數。所以,如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢:
from django.db.models import Avg, Max, Min Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))# #{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
# 查詢所有書籍的平均價格 from django.db.models import Avg,Count,Max,Min ret=Book.objects.all().aggregate(Avg('price')) # {'price__avg': 202.896} # 可以改名字 ret=Book.objects.all().aggregate(avg_price=Avg('price')) # 統計平均價格和最大價格 ret=Book.objects.all().aggregate(avg_price=Avg('price'),max_price=Max('price')) # 統計最小价格 ret = Book.objects.all().aggregate(avg_price=Avg('price'), min_price=Min('price')) # 統計個數和平均價格 ret = Book.objects.all().aggregate(avg_price=Avg('price'), max_price=Max('price'),count=Count('price')) ret = Book.objects.all().aggregate(avg_price=Avg('price'), max_price=Max('price'),count=Count('nid')) print(ret)View Code
分組
###################################--單表分組查詢--####################################################### 查詢每一個部門名稱以及對應的員工數 emp: id name age salary dep 1 alex 12 2000 銷售部 2 egon 22 3000 人事部 3 wen 22 5000 人事部 sql語句: select dep,Count(*) from emp group by dep; ORM: emp.objects.values("dep").annotate(c=Count("id")) # 示例:查詢每一個部門的名稱,以及平均薪水 # select dep,Avg(salary) from app01_emp group by dep from django.db.models import Avg, Count, Max, Min # ret=Emp.objects.values('dep').annotate(Avg('salary')) # 重新命名 ret=Emp.objects.values('dep').annotate(avg_salary=Avg('salary')) print(ret) 總結:單表分組查詢orm語法:單表模型.objects.values('group by 的欄位').annotate(聚合函式('統計的欄位')) 查詢每一個省份名稱以及對應的員工數 Emp.objects.values(