1. 程式人生 > >Django - ORM 資料庫操作 - 多表操作

Django - ORM 資料庫操作 - 多表操作

目錄

一、建立多表關係結構

二、一對多 and 一對一 表關係的記錄操作

1、 增(兩種方式)

方式一、create方式建立,指定欄位名傳輸資料

方式二、獲取外來鍵表物件id,給類定義的關係變數,賦值查詢到的物件

2、改(兩種方式)

方式一、獲取物件,修改後save方法儲存

方式二、update方法更新

三、多對多 表關係的記錄操作

1、增 add()

2、刪除 remove()

3、清空 clear()

4、先清空後建立 set() -- 必須傳值列表

 四、基於物件的跨表查詢

1、一對一的跨表查詢

1)正向查詢(類內聯絡變數名查詢)

2)反向查詢(表名小寫查詢)

2、一對多的跨表查詢

1)正向查詢(類內聯絡變數名查詢)

2)反向查詢(表名小寫_set.all())

3、多對多的跨表查詢

1)正向查詢(類內聯絡變數名.all())

2)反向查詢(表名小寫_set.all())

4、連續跨表查詢

五、基於雙下劃線的查詢:聯絡表__聯絡表內欄位名

1、一對一的查詢

2、一對多的查詢

3、多對多的查詢

4、連續跨表查詢

六、聚合查詢(aggregate() + 聚合函式(Avg,Max,Min…))

七、分組查詢(annotate() + 聚合函式(Avg,Max,Min…))

- filter、annotate、values的使用順序,及不同

八、F查詢(對同一個model例項中的兩個不同欄位進行比較)

- models新增(book標下的閱讀數字段和評論數字段)

 - 同一個model實力類中的欄位比較

- 欄位值的修改(加減乘除取模等操作)

九、Q查詢(操作複雜的邏輯運算(與或非))


一、建立多表關係結構

總結:

  • 一對一關係: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)