1. 程式人生 > 實用技巧 >6.模型-多表

6.模型-多表

目錄

一、建立模型

作者模型:一個作者有姓名和年齡。

作者詳細資訊模型:作者的詳細資訊放到詳情表,包含生日,手機號,家庭住址等資訊。作者詳情模型和作者模型之間是一對一的關係(one-to-one)

出版商模型:出版商有名稱,所在城市以及email。

書籍模型: 書籍有書名和出版日期,一本書可能會有多個作者,一個作者也可以寫多本書,所以作者和書籍的關係就是多對多的關聯關係(many-to-many);一本書只應該由一個出版商出版,所以出版商和書籍是一對多關聯關係(one-to-many)。

1、建立一對一關係

一對一的這個關係欄位寫在兩個表的任意一個表裡面都可以。OneToOneField方法的三個引數:

  • to="AuthorDetail",建立一對一關係的另一張表的表名
  • to_field="nid",建立一對一關係的另一張表的主鍵,如果是mysql預設生成的id可以不寫這引數
  • on_delete=“ ”,可選值有兩個,models.CASCADE是預設級聯,models.SET_NULL是不級聯。這在django2.0以上版本需要自己手動設定,以下預設級聯。
# 作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    # authorDetail = models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE)
    # authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)
    authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.SET_NULL)
    # 就是foreignkey+unique,不需要自己寫引數,並且orm會自動給這個欄位名字拼上一個_id,資料庫中欄位名稱為authorDetail_id

class AuthorDetail(models.Model):
    birthday = models.DateField()
    telephone = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)

2、建立一對多關係

一對多關係的相關引數:

  • to=“ ” 指向表,
  • to_field=“ ” 指向你關聯的欄位,不寫這個,預設會自動關聯主鍵欄位,
  • related_name = " " 反向操作時,使用的欄位名,用於代替原反向查詢時的 '表名_set'
  • related_query_name = " " 反向查詢操作時,使用的連線字首,用於替換表名,
  • on_delete=“ ” 級聯刪除

建立一對多的關係,外來鍵欄位建立在多的一方,欄位publish如果是外來鍵欄位,那麼它自動是int型別

注意:多對多的表關係,orm的manytomany自動幫我們建立第三張表,手動建立的第三張表進行orm操作的時候,很多關於多對多關係的表之間的orm語句方法無法使用,如果你想刪除某張表,你只需要將這個表登出掉,然後執行那兩個資料庫同步指令就可以了,自動就刪除了。

# 出版社和書籍一對多
class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()    # charfield
    
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # 一對多
    publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
    
   # 多對多
    authors = models.ManyToManyField(to='Author',) 
    # 建立下面的第三張表 
    # class BookToAuthor(models.Model):
    # book_id = models.ForeignKey(to='Book')
    # author_id = models.ForeignKey(to='Author')

外來鍵欄位名稱不需要寫成publish_id,orm在翻譯foreignkey的時候會自動給你這個欄位拼上一個_id,這個欄位名稱在資料庫裡面就自動變成了publish_id。

3、建立多對多關係

多對多關係的相關引數:

  • to=" " 關聯的表
  • related_name=“ ” 同Foreignkey欄位
  • related_query_name=“ ” 同Foreignkey欄位
  • through=“ ” 手動建立第三張表的時候指定第三張表的表名
  • through_fields=“ ” 設定關聯的欄位
  • db_table=" " 預設建立第三張表時,資料庫中表的名稱。

與Author表建立多對多的關係,ManyToManyField可以建在兩個模型中的任意一個,自動建立第三張表,

注意:檢視book表的時候,看不到這個欄位,因為這個欄位就是建立第三張表的意思,不是建立欄位的意思,所以只能說這個book類裡面有authors這個欄位屬性。

注意:不管是一對多還是多對多,寫to這個引數的時候,最後後面的值是個字串,不然你就需要將你要關聯的那個表放到這個表的上面

4、建立管理資料表的超級使用者

createsuperuser

輸入建立的使用者名稱和密碼就可以,郵箱可以直接不用填直接回車

在admin.py檔案中新增對應的模型,使用admin.site.register()方法,這樣就能在瀏覽器中直接管理資料表了。

from django.contrib import admin 
from app01 import models

admin.site.register(models.Book)
admin.site.register(models.Publish)
admin.site.register(models.Author)
admin.site.register(models.AuthorDetail)

訪問http://127.0.0.1:8000/admin/ 即可進入登陸介面

檢視已建立的超級使用者

from django.contrib.auth.models import User
user = User.objects.filter(is_superuser = True)
print(user)

二、新增表記錄

1、增

一對一增加

方式一:

先建立外來鍵連線到那張表的資料,外來鍵在Author表中,即先建立AuthorDetail表的資料再將其作為引數傳給Author表的authorDetail。

new_author_detail = models.AuthorDetail.objects.create(
        birthday='1991-02-09',
        telephone='1245236',
        addr='浙江杭州'
    )
models.Author.objects.create(
         name='小明',
         age='20',
         authorDetail=new_author_detail,
    )

方式二:

obj = models.AuthorDetail.objects.filter(addr='北京').first()
    models.Author.objects.create(
        name='王濤',
        age='28',
        # mysql欄位
        authorDetail_id=obj.id,
    )

一對多增加

obj = models.Publish.objects.get(id=2)
    models.Book.objects.create(
        title='王兄和李兄的故事',
        publish='人民出版社',
        price=3,
        # publishs=models.Publish.objects.get(id=1),
        publishs_id=obj.id,
    )

多對多增加

# 方式一
    # 給nid為1的書籍新增id為1和2的作者
    book_obj = models.Book.objects.get(nid=1)
    # * 打散傳參
    book_obj.authors.add(*[1, 2])
# 方式二
    nid為的這本書新增id為1和3的兩個作者
    author1 = models.Author.objects.get(id=1)
    author2 = models.Author.objects.get(id=3)
    book_obj = models.Book.objects.get(nid=5)
    book_obj.authors.add(*[author1, author2])

2、刪除

一對一、一對多刪除和單表刪除一樣

一對一 表一外來鍵關聯到表二,表一刪除,不影響表二,表2刪除會影響表一

models.AuthorDetail.objects.get(id=2).delete()

一對多

models.Publish.objects.get(id=1).delete()
models.Book.objects.get(nid=1).delete()

多對多刪除 刪除第三張表

    book_obj = models.Book.objects.get(id=6)
    # book.objects.remove(6)
    # 5和6是欄位author_id的值
    # book_obj.authors.remove(*[5, 6])
    # 先清除再重置
    book_obj.author.clear()
    book_obj.authors.add(*[1, ])
    book_obj.authors.set('1')
    book_obj.authors.set(['5', '6'])    # 刪除後更新

3、更新

  models.Author.objects.filter(id=5).update(
        name='老張',
        age='16',
        # authorDetail=models.AuthorDetail.objects.get(id=3),
        authorDetail_id=4
    )

三、查詢

1、基於表物件的跨表查詢操作

類似於子查詢,

正向查詢:通過關係屬性所在的表(類)去查關聯的另一張表的資料

反向查詢:與正向相反

一對一跨表查詢

正向查詢:Authorobj.authorDetail,物件.關聯屬性名稱。

# 查詢老李的電話
author_obj = models.Author.objects.filter(name='老李').first()
print(author_obj.authorDetail.telephone)

反向查詢:AuthorDetailobj.author,物件.小寫類名。

# 查詢電話為18764512的作者是誰
author_detail_obj = models.AuthorDetail.objects.get(telephone='18764512')
print(author_detail_obj.author.name)

一對多跨表查詢

正向查詢:book_obj.publishs 物件.屬性

# 查詢三國那些事這本書的出版社是哪個
book_obj = models.Book.objects.get(title='三國那些事')
print(book_obj.publishs.name)

反向查詢:publish_obj.book_set.all() 物件.表名小寫_set

# 三國出版社出版了哪些書
pub_obj = models.Publish.objects.get(name='三國出版社')
print(pub_obj.book_set.all())

2、基於雙下劃線的跨表操作

正向查詢按欄位,反向查詢按表名小寫用來告訴ORM引擎join哪張表,一對一、一對多、多對多都是一個寫法,注意,寫orm查詢的時候,哪個表在前哪個表在後都沒問題,因為走的是join連表操作。

總結:過濾條件,已知條件的在哪張表中,以那張表去查詢就是正向

反向:表名小寫__連表

正向:屬性名__連表

一對一

# 方式一   正向
    obj1 = models.Author.objects.filter(name='老曹').values('authorDetail__telephone')
    print(obj1)
    # 方式二   反向
    obj2 = models.AuthorDetail.objects.filter(author__name='老曹').values('telephone', 'author__age')
    print(obj2)
# 2、查詢電話為1452341526的人是誰
    # 正向
    obj3 = models.Author.objects.filter(authorDetail__telephone='1452341526').values('name')
    print(obj3)
    # 反向
    obj4 = models.AuthorDetail.objects.filter(telephone='1452341526').values('author__name')
    print(obj4)

一對多

# 查詢三國那些事的出版社
    obj5 = models.Book.objects.filter(title='三國那些事').values('publishs__name')
    print('obj5', obj5)
    obj6 = models.Publish.objects.filter(book__title='三國那些事').values('name')
    print('obj6', obj6)

多對多

# 正向查詢 按欄位:authors:
queryResult=Book.objects
            .filter(authors__name="yuan")
            .values_list("title")

    # 反向查詢 按表名:book
    queryResult=Author.objects
              .filter(name="yuan")
              .values_list("book__title","book__price")

進階:連續跨表

# 人民出版社 出版的書的名字及作者名字
    obj1 = models.Book.objects.filter(publishs__name='人民出版社').values('title', 'authors__name')
    print('obj1', obj1)
    obj2 = models.Publish.objects.filter(name='人民出版社').values('book__title', 'book__authors__name')
    print('obj2', obj2)

    # 號碼以1開頭的作者出版過的書籍名稱及出版社名稱
    obj3 = models.AuthorDetail.objects.filter(telephone__startswith='1').\
        values('author__book__title', 'author__book__publishs__name')
    print('obj3', obj3)

3、聚合查詢、分組查詢

聚合

aggregate(*args, **kwargs)

from ajango.db.models import Avg,Max,Min,Sum,Count
# 計算所有書籍的平均價格和最大值
obj = models.Book.objects.all().aggregate(Avg('price'), m=Max('price'))
print(obj['m']-2)

aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的識別符號,值是計算出來的聚合值。鍵的名稱是按照欄位和聚合函式的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。

分組

annotate()為呼叫的QuerySet中每一個物件都生成一個獨立的統計值(統計方法用聚合函式)。

# 統計每個出版社出版的書籍的平均價格
    ret1 = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
    # select avg(price) from app01_book group by publishs_id;
    print(ret1)
    ret2 = models.Publish.objects.annotate(a=Avg('book__price'))
    print('ret2', ret2)

4、F查詢和Q查詢

構造的過濾器都只是將欄位值與某個常量做比較。Django 提供 F() 來做這樣的比較。F() 的例項可以在查詢中引用欄位,來比較同一個 model 例項中兩個不同欄位的值。Django 支援 F() 物件之間以及 F() 物件和常數之間的加減乘除和取模的操作。

F查詢

from django.db.models import F
# F查詢
# good>comment+10的
ret1 = models.Book.objects.filter(good__gt=F('comment')+10)
print('ret1', ret1)     # <QuerySet [<Book: 老李的家事>, <Book: 三國那些事>, <Book: 老曹的床頭故事>]>
# 書籍表中的所有書的價格都加上20
models.Book.objects.all().update(price=F('price')+20)

Q查詢

from django.db.models import Q
Q(title__startswith='Py')

Q 物件可以使用&(與)|(或)、~(非) 操作符組合起來。當一個操作符在兩個Q 物件上使用時,它產生一個新的Q 物件。

bookList``=``Book.objects.``filter``(Q(authors__name``=``"yuan"``)|Q(authors__name``=``"egon"``))

等同於下面的SQL WHERE 子句:

WHERE name ``=``"yuan"` `OR name ``=``"egon"

四、執行原生的sql語句

執行原始的sql查詢

ret = models.Publish.objects.raw('select * from app01_publish;')

執行自定義sql

不需要將查詢結果對映成模型,或者我們需要執行DELETE、 INSERT以及UPDATE操作。在這些情況下,我們可以直接訪問資料庫,完全避開模型層。

from django.db import connection, connections
cursor = connection.cursor()  # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
ret = cursor.fetchone()

五、練習

#1 查詢每個作者的姓名以及出版的書的最高價格
    ret = models.Author.objects.values('name').annotate(max_price=Max('book__price'))
    print(ret) #注意:values寫在annotate前面是作為分組依據用的,並且返回給你的值就是這個values裡面的欄位(name)和分組統計的結果欄位資料(max_price)
    # ret = models.Author.objects.annotate(max_price=Max('book__price')).values('name','max_price')#這種寫法是按照Author表的id欄位進行分組,返回給你的是這個表的所有model物件,這個物件裡面包含著max_price這個屬性,後面寫values方法是獲取的這些物件的屬性的值,當然,可以加雙下劃線來連表獲取其他關聯表的資料,但是獲取的其他關聯表資料是你的這些model物件對應的資料,而關聯獲取的資料可能不是你想要的最大值對應的那些資料
# 2 查詢作者id大於2作者的姓名以及出版的書的最高價格
    ret = models.Author.objects.filter(id__gt=2).annotate(max_price=Max('book__price')).values('name','max_price')#記著,這個values取得是前面呼叫這個方法的表的所有欄位值以及max_pirce的值,這也是為什麼我們取關聯資料的時候要加雙劃線的原因
    print(ret)

#3 查詢作者id大於2或者作者年齡大於等於20歲的女作者的姓名以及出版的書的最高價格
    # ret = models.Author.objects.filter(Q(id__gt=2)|Q(age__gte=20),sex='female').annotate(max_price=Max('book__price')).values('name','max_price')
#4 查詢每個作者出版的書的最高價格 的平均值
    # ret = models.Author.objects.values('id').annotate(max_price=Max('book__price')).aggregate(Avg('max_price')) #{'max_price__avg': 555.0} 注意,aggregate是queryset的終止句,得到的是字典
    # ret = models.Author.objects.annotate(max_price=Max('book__price')).aggregate(Avg('max_price')) #{'max_price__avg': 555.0} 注意,aggregate是queryset的終止句,得到的是字典

#5 每個作者出版的所有書的最高價格以及最高價格的那本書的名稱(通過orm玩起來就是個死題,需要用原生sql)
    '''
    select title,price from (select app01_author.id,app01_book.title,app01_book.price from app01_author INNER JOIN app01_book_authors on app01_author.id=
app01_book_authors.author_id INNER JOIN app01_book on app01_book.id=
app01_book_authors.book_id ORDER BY app01_book.price desc) as b  GROUP BY id
'''

六、ORM的坑