Django - ORM 資料庫操作 - 多表操作
阿新 • • 發佈:2018-11-22
目錄
方式二、獲取外來鍵表物件id,給類定義的關係變數,賦值查詢到的物件
六、聚合查詢(aggregate() + 聚合函式(Avg,Max,Min…))
七、分組查詢(annotate() + 聚合函式(Avg,Max,Min…))
- filter、annotate、values的使用順序,及不同
八、F查詢(對同一個model例項中的兩個不同欄位進行比較)
- models新增(book標下的閱讀數字段和評論數字段)
一、建立多表關係結構
總結:
- 一對一關係:OneToOneField
- 一對多關係:ForeignKey
- 多對多關係(自動生成中間表):ManyToManyField
- 用了OneToOneField和ForeignKey,模型表的外來鍵欄位,預設末尾新增_id
- 表的名字 myapp_modelName 自動生成,但是可以覆寫為其他名字。
- 外來鍵欄位 ForeignKey 有一個 null=True 的設定(它允許外來鍵接受空值 NULL),你可以賦給它空值 None 。
- 主鍵欄位使用primary_key,呼叫時可以使用簡寫‘pk’
from django.db import models # 出版社資料表 class Publish(models.Model): # id如果不寫,會自動生成,名字叫nid,並且自增 id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) addr = models.CharField(max_length=64) email = models.EmailField() # 作者表,與作者詳細資訊表建立一對一的關係(OneToOneField) class Author(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) # 數字型別 sex = models.IntegerField() # 可以用 ForeignKey(如下),但是得設定唯一性約束,會報警告,不建議用,建議用OneToOneField # authordetail= models.ForeignKey(unique=True) # to='AuthorDetail' 使用引號,表存在即可;不適用引號,資料表類必須在此類之前定義,才能被找到 # 與表 AuthorDetail 的 id欄位建立一對一的關係 authordetail = models.OneToOneField(to='AuthorDetail', to_field='id') def __str__(self): return self.name # 作者資訊詳細表,被作者表建立一對一的關係 class AuthorDetail(models.Model): id = models.AutoField(primary_key=True) phone = models.CharField(max_length=32) addr = models.CharField(max_length=64) # 書籍表,和作者表建立多對多的關係(ManyToManyField) - 自動生成中間表 book_authors # 與出版社表建立一對多關係(ForeignKey) class Book(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) # 一對多 publish = models.ForeignKey(to=Publish, to_field='id') # 多對多; to 後不加引號,指引類必須在此類前定義 authors = models.ManyToManyField(to=Author) # 重寫此類被print的輸出內容 def __str__(self): return self.name
二、一對多 and 一對一 表關係的記錄操作
注意:一對多和一對一的資料記錄操作方式相同
1、 增(兩種方式)
方式一、create方式建立,指定欄位名傳輸資料
# creat建立資料,將值傳輸給表內欄位名publish_id ret=Book.objects.create(name='紅樓夢',price=34.5,publish_id=1)
方式二、獲取外來鍵表物件id,給類定義的關係變數,賦值查詢到的物件
# 獲取出版社表,id為1的物件 publish=Publish.objects.get(id=1) # 同上獲取 出版社表物件 pk是主鍵,通過主鍵查詢 publish=Publish.objects.get(pk=1) # 獲取出版社表物件,主鍵id=2的第一條資料物件,因為filter返回queryset物件 publish = Publish.objects.filter(pk=2).first() # creat物件,將獲取的出版社表物件賦予 book類內提前建立表關係的變數名 # publish = models.ForeignKey(to=Publish, to_field='id') ret = Book.objects.create(name='西遊記', price=34.5, publish=publish) print(ret.name)
2、改(兩種方式)
方式一、獲取物件,修改後save方法儲存
# 獲取book物件 book=Book.objects.get(pk=1) # book.publish=出版社物件 --- 同下 book.publish_id=2 book.save()
方式二、update方法更新
book=Book.objects.filter(pk=1).update(publish = 出版社物件) book=Book.objects.filter(pk=1).update(publish_id = 1)
三、多對多 表關係的記錄操作
1、增 add()
# 為紅樓夢這本書新增一個叫n1,n2的作者 # 獲取author表內的資料物件 n1 對應id = 1,n2 對應id =2 n1=Author.objects.filter(name='n1').first() n2=Author.objects.filter(name='n2').first() # 獲取書籍物件 book=Book.objects.filter(name='紅樓夢').first() # add 新增單個物件 # book類.authore變數(建立多對多聯絡的變數).add(物件1) book.authors.add(n1) # add 新增多個物件 # book類.authore變數(建立多對多聯絡的變數).add(物件1,物件2) book.authors.add(n1,n2) # add 新增作者id # book類.authore變數(建立多對多聯絡的變數).add(準確內容) book.authors.add(1,2)
2、刪除 remove()
#可以傳物件,可以傳id,可以傳多值(,分割),不建議混用 book.authors.remove(n1) book.authors.remove(2) book.authors.remove(1,2)
3、清空 clear()
book.authors.clear()
4、先清空後建立 set() -- 必須傳值列表
# set,先清空,再新增 # 必須傳一個列表 -- 列表內資料 -- [id, 物件,] book.authors.set([n1,]) # 不允許*[n1,]進行傳值 # 打散了傳過去了,相當於book.authors.set(n1) book.authors.set(*[lqz,])
四、基於物件的跨表查詢
總結:
- 表A類內-關聯a 建立與 表B-id欄位的關係
- 正向查詢:通過表A查詢表B內欄位內容
- 反向查詢:通過表B查詢表A內欄位內容
- 基於物件的查詢,是子查詢也就是多次查詢
1、一對一的跨表查詢
1)正向查詢(類內聯絡變數名查詢)
# 查詢n1作者的手機號 # 獲取指定作者物件 author=Author.objects.filter(name='n1').first() # author.authordetail -- 上方指定物件.類內聯絡變數名 -- 作者表對應詳細資訊表的物件 # author類內的authordetail聯絡變數名 authordetail=author.authordetail # 通過物件獲取物件內欄位資訊 print(authordetail.phone)
2)反向查詢(表名小寫查詢)
# 查詢地址是 :山東 的作者名字 # 獲取AuthorDetail詳情表內的指定物件 authordetail=AuthorDetail.objects.filter(addr='山東').first() # authordetail.author -- 上方指定物件.關聯表名小寫 -- 獲取關聯作者物件 author=authordetail.author print(author.name)
2、一對多的跨表查詢
1)正向查詢(類內聯絡變數名查詢)
# 查詢紅樓夢這本書的出版社郵箱 # 獲取指定書籍物件 book=Book.objects.filter(name='紅樓夢').first() # book.publish -- 上方獲取指定物件.類內聯絡名 --就是出版社物件 pulish=book.publish # 出版社物件.欄位名 print(pulish.email)
2)反向查詢(表名小寫_set.all())
# 查詢地址是北京 的出版社出版的圖書 # 獲取指定地址物件 publish=Publish.objects.filter(addr='北京').first() # publish.book_set.all() 拿出所有的圖書 # 獲取指定物件 建立聯絡的表內容 books=publish.book_set.all() # 統計一下條數 books=publish.book_set.all().count() print(books)
3、多對多的跨表查詢
1)正向查詢(類內聯絡變數名.all())
# 查詢紅樓夢這本書所有的作者 # 獲取指定書本的物件 book=Book.objects.filter(name='紅樓夢').first() # 獲取指定物件聯絡表的所有資料 指定物件.類內聯絡變數名 book.authors.all() #是所有的作者,是一個queryset物件,可以繼續點 print(book.authors.all())
2)反向查詢(表名小寫_set.all())
# 查詢n1寫的所有書 # 獲取n1物件 n1=Author.objects.filter(name='n1').first() # n1.聯絡表名小寫_set.all() 獲取對應聯絡表內資料 books=n1.book_set.all() print(books)
4、連續跨表查詢
# 查詢紅樓夢這本書所有的作者的手機號 # 獲取物件 book=Book.objects.filter(name='紅樓夢').first() # 獲取所有聯絡表物件 authors=book.authors.all() # 迴圈取聯絡表物件 for author in authors: # 獲取聯絡表物件對應詳細資訊表物件 authordetail=author.authordetail print(authordetail.phone) ''' book表 -- authors表 -- authordetail表 '''
五、基於雙下劃線的查詢:聯絡表__聯絡表內欄位名
總結:
- 三者表聯絡,對於基於上下劃線的查詢本質不變:跨表使用下劃線
- 從純生sql程式碼來看,只是from的表不同
1、一對一的查詢
# 查詢n1的手機號(一個作者對應一個具體資訊) # 正向查詢(作者表物件,跨表字段) ret=Author.objects.filter(name="n1").values("authordetail__telephone") # 反向查詢(作者詳細資訊跨表物件,本地欄位) ret=AuthorDetail.objects.filter(author__name="n1").values("telephone")
2、一對多的查詢
# 查詢出版社為北京出版社出版的所有圖書的名字,價格(一個出版社對應多個圖書資訊) # 正向 (出版社表物件跨表字段) ret=Publish.objects.filter(name='北京出版社').values('book__name','book__price') print(ret) # 反向 (圖書表跨表物件的本地欄位) ret=Book.objects.filter(publish__name='北京出版社').values('name','price') print(ret) # 查詢北京出版社出版的價格大於19的書 (一個出版社對應多個圖書資訊) # 正向(出版社表物件的跨表字段) ret=Publish.objects.filter(name='北京出版社',book__price__gt=19).values('book__name','book__price') print(ret)
3、多對多的查詢
# 查詢紅樓夢的所有作者名字 (一本圖書可能對應多個作者) # 正向(圖書表物件,跨表字段) ret=Book.objects.filter(name='紅樓夢').values('authors__name') print(ret) # 反向(作者表跨表物件,本地欄位) ret=Author.objects.filter(book__name='紅樓夢').values('name') print(ret) # 查詢圖書價格大於30的所有作者名字(一本圖書對應多個作者) # 正向(圖書表物件,跨表字段) ret=Book.objects.filter(price__gt=30).values('authors__name') print(ret)
4、連續跨表查詢
# 基於雙下劃綫的 “連續跨表” # 查詢北京出版社出版過的所有書籍的名字以及作者的姓名 # 出版社表 - 書籍表 - 作者表 # 正向(出版社表物件,書本跨表字段,書本下作者跨表字段) ret=Publish.objects.filter(name='北京出版社').values('book__name','book__authors__name') print(ret) # 反向(書本表跨表出版社物件,本地書名欄位,跨表作者表字段) ret=Book.objects.filter(publish__name='北京出版社').values('name','authors__name') print(ret) # 手機號以151開頭的作者出版過的所有書籍名稱以及出版社名稱 # 作者詳細資訊表(手機號) - 作者表 - 書籍表(書名) - 出版社表(出版社表) # 正向(作者資訊表物件,作者表-書籍表字段,作者表-書籍表-出版社表字段) ret=AuthorDetail.objects.filter(phone__startswith='13').values('author__book__name','author__book__publish__name') print(ret) # 反向(書籍表跨表 作者表-詳細資訊表 物件,本地欄位,跨表出版社欄位) ret=Book.objects.filter(authors__authordetail__phone__startswith='13').values('name','publish__name') print(ret)
六、聚合查詢(aggregate() + 聚合函式(Avg,Max,Min…))
總結:
- aggregate():QuerySet 的一個終止子句,它返回一個包含一些鍵值對的字典。
- 鍵的名稱是聚合值的識別符號(自動生成或者手動命名),值是計算出來的聚合值。
- key(自動生成):‘欄位名_聚合函式名’
- value:聚合函式計算返回值
# 匯入所需 聚合函式 from django.db.models import Avg,Count,Max,Min,Sum # 計算所有圖書的平均價格 ret=Book.objects.all().aggregate(Avg('price')) print(ret) # {'price__avg': 202.896} # 計算圖書的最高價格 (自定義命名key) ret=Book.objects.all().aggregate(my_max = Max('price')) print(ret) # {'my_max': Decimal('99.00')} # 計算圖書的最高價格,最低價格,平均價格,總價 ret=Book.objects.all().aggregate(Max('price'),Min('price'),Avg('price'),Sum('price')) print(ret) # {'price__max':Decimal('81.20'),'price__min':Decimal('12.99'),'price__avg':34.35,'price__sum': Decimal('121.00'),}
七、分組查詢(annotate() + 聚合函式(Avg,Max,Min…))
總結:
annotate()為呼叫的QuerySet中每一個物件都生成一個獨立的統計值(統計方法用聚合函式)。
跨表分組查詢本質就是將關聯表join成一張表,再按單表的思路進行分組查詢。
# 統計每一本書作者個數 ret=Book.objects.all().annotate(c=Count('authors')) print(ret) # 返回的是一個queryset物件 # <QuerySet [<Book:紅樓夢>,<Book:西遊記>,<Book:水滸傳>]> # 迴圈取出列表內物件 for r in ret: print(r.name,'-->',r.c) # 紅樓夢--> 1 # 紅樓夢--> 2 # 紅樓夢--> 3 ret=Book.objects.all().annotate(c=Count('authors')).values('name','c') print(ret) # 返回queryset物件 # <QuerySet [{'name':紅樓夢,'c':1},{'name':西遊記,'c':2},{'name':水滸傳,'c':3}]> # 統計每一個出版社的最便宜的書(以誰group by分組 就以誰為基表) # 出版社表跨表物件(雙下劃線的使用) ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','m') print(ret) # <QuerySet [{'name':北京出版社,'m':Decimal('1.00')},]> ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','book__name','m') print(ret) # <QuerySet [{'name':北京出版社,'book_name':'紅樓夢','m':Decimal('1.00')},]>
- filter、annotate、values的使用順序,及不同
總結:
- filter:在前表示where條件刪選,在後表示having
- values:在前表示group by分組,在後表示取對應欄位的值
- annotate:取分組
# 統計每一本以py開頭的書籍的、作者個數 # filter:where篩選,annotate:join,values:取值 ret1=Book.objects.all().filter(name__startswith='py').annotate(c=Count('authors')).values('name','c') print(ret1) # <QuerySet [{'name':'python','c':2}]> # 統計每一本以py開頭的書籍的作者個數--套用模板 # valuse:group by分組,fitter:having限定條件,annotate:join,values:取值 ret2=Book.objects.all().values('name').filter(name__startswith='py').annotate(c=Count('authors')).values('name','c') print(ret2) # <QuerySet [{'name':'python','c':2}]> # 查詢各個作者出的書的總價格 ret=Author.objects.all().values('name').annotate(s=Sum('book__price')).values('name','s') ret=Author.objects.all().annotate(s=Sum('book__price')).values('name','s') print(ret) # <QuerySet [{'name':'n1','s':Decimal('123')}]> # 查詢名字叫n1作者書的總價格 ,pk同主鍵 ret=Author.objects.all().values('pk').filter(name='n1').annotate(s=Sum('book__price')).values('name','s') print(ret) # 查詢所有作者寫的書的總價格大於30 ret=Author.objects.all().values('pk').annotate(s=Sum('book__price')).filter(s__gt=2).values('name','s') ret=Author.objects.all().annotate(s=Sum('book__price')).filter(s__gt=30).values('name','s') print(ret) # 統計不止一個作者的圖書 ret = Book.objects.all().values('pk').annotate(c=Count('authors')).filter(c__gt=1).values('name', 'c') # ret = Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('name','author_num') print(ret)
八、F查詢(對同一個model例項中的兩個不同欄位進行比較)
總結:
- F() 的例項可以在查詢中引用欄位,來比較同一個 model 例項中兩個不同欄位的值。
- 支援F() 物件之間以及 F() 物件和常數之間的加減乘除和取模的操作
- 支援F()對欄位值的修改操作
- models新增(book標下的閱讀數字段和評論數字段)
class Book(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32,db_index=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 閱讀數 reat_num=models.IntegerField(default=0) # 評論數 commit_num=models.IntegerField(default=0) publish = models.ForeignKey(to=Publish, to_field='id',on_delete=models.CASCADE) authors = models.ManyToManyField(to=Author) # test=models.PositiveSmallIntegerField() def __str__(self): return self.name
- 同一個model實力類中的欄位比較
from django.db.models import F # 查詢評論數大於閱讀數的書 # 錯誤方式 # ret = Book.objects.all().filter(commit_num__gt=reat_num) # 正確方式 評論數 >(gt) 閱讀數 ret = Book.objects.all().filter(commit_num__gt=F('reat_num')) print(ret)
- 欄位值的修改(加減乘除取模等操作)
from django.db.models import F # 把所有書的評論數加1 # 錯誤方式1 # ret = Book.objects.all().update(commit_num += 1) # 錯誤方式2 # ret = Book.objects.all().update(commit_num=commit_num + 1) # 正確方式 ret = Book.objects.all().update(commit_num=F('commit_num') + 1) print(ret) # 把python這本書的閱讀數減5 ret = Book.objects.all().filter(name='python').update(reat_num=F('reat_num') - 5) print(ret)
九、Q查詢(操作複雜的邏輯運算(與或非))
總結:
- and (&):Q(欄位名1=‘查詢名1’) & Q(欄位名1=‘查詢名1’)
- or(|):Q(欄位名1=‘查詢名1’) | Q(欄位名1=‘查詢名1’)
- not(~):Q(欄位名1=‘查詢名1’) ~ Q(欄位名1=‘查詢名1’)
- 當一個操作符在兩個Q 物件上使用時,它產生一個新的Q 物件。
- 可以組合& 和| 操作符以及使用括號進行分組來編寫任意複雜的Q 物件。
from django.db.models import Q # 查詢作者名字是n1或者名字是n2的書 # 錯誤方式1(使用的是and) # ret=Book.objects.all().filter(authors__name='n1',authors__name='n2') # 錯誤方式 同上 # ret = Book.objects.all().filter(Q(authors__name='n1') & Q(authors__name='n2')) # print(ret) # 正確方式 ret=Book.objects.all().filter(Q(authors__name='n1')|Q(authors__name='n2')) print(ret) # 查詢作者不是n1的書(~ 非) ret=Book.objects.filter(~Q(authors__name='n1')) print(ret) # 構建很複雜的邏輯,需要用括號來區分優先順序 ret = Book.objects.filter((Q(name='紅樓夢') & Q(price__gt=100)) | Q(pk__gt=2)) print(ret)