模型層之多表操做

建立模型

假定下面這些概念,字段和關係python

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

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

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

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

爲了存儲出版社的郵箱,地址,在 Book 表後面加字段mysql

這樣會有大量重複的數據,浪費空間git

一對多:一個出版社對應多本書(關聯信息建在多的一方,也就是 Book 表中)sql

一旦肯定表關係是一對多,在多對應的表中建立關聯字段django

多對多:一本書有多個做者,一個做者出多本書app

一旦肯定表關係是多對多,建立第三張關係表(中間表,中間表就三個字段,本身的 id,書籍 id 和做者 id)函數

一對一:對做者詳細信息的擴展3d

一旦肯定是一對一的關係,在兩張表中的任意一張表中創建關聯字段+Uniquecode

在 models 建立以下模型orm

from django.db import models

# Create your models here.

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32, null=True)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    # 一對多的關係一旦確立,關聯關係寫在多的一方
    # on_delete:級聯刪除
    publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE)
    # 多對多的關係須要建立第三張表(自動建立第三張表)
    authors = models.ManyToManyField(to='Author')

    def __str__(self):
        return self.title

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # unique=True 惟一性約束
    author_detail = models.OneToOneField(to='AuthorDetail', to_field='nid', on_delete=models.CASCADE)
    # author_detail = models.ForeignKey(to='AuthorDatail',to_field='nid',unique=True,on_delete=models.CASCADE)

class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=16, unique=True)
    addr = models.CharField(max_length=16)


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    # EmailField :郵箱格式,EmailField用在admin中
    email = models.EmailField()

生成的表以下:

添加表記錄

一對多

# 方式一:
publish_obj = models.Book.objects.get(nid=1)
book_obj = models.Book.objects.create(name="圖解HTTP", price="49", publish_date="2014-11-12", publish=publish_obj)
  
# 方式二,推薦:
book_obj = models.Book.objects.create(name="圖解HTTP", price="49", publish_date="2014-11-12", publish_id=1)

多對多

# 當前生成的書籍對象
book_obj = models.Book.objects.create(name="圖解HTTP", price="49", publish_date="2014-11-12", publish_id=1)
# 爲書籍綁定的做者對象
author_obj1 = models.Author.objects.filter(name="上野宣").first()
author_obj2 = models.Author.objects.filter(name="於均良").first()
# 綁定多對多關係,即向關係表book_authors中添加紀錄
book_obj.authors.add(author_obj1, author_obj2)
# 或者
book_obj.authors.add(1, 2)
# 方式二
# 當前生成的書籍對象
book_obj = models.Book.objects.create(name="C Primer Plus", price="60", publish_date="2005-2-12", publish_id=1)
# 綁定多對多關係
book_obj.authors.add(*[3, 4])
# 解除綁定
book_obj = models.Book.objects.filter(nid=2).first()    # 找到book的id爲2的書
book_obj.authors.remove(2)  # 解除做者id爲2的綁定關係
book_obj.authors.clear()    # 解除全部做者的綁定關係
# 解除再綁定:清空全部的綁定關係,再設置一個
book_obj.authors.clear()
book_obj.authors.add(1)
# 另外一種方法
book_obj.set(1) # 等同於上面的兩步操做

基於對象的跨表查詢

  • 正向:關聯關係在當前表中,從當前表去另外一個表
  • 反向:關聯關係不在當前表中,從當前表去另外一個表

一對一查詢

正向查詢按字段

# 查詢上野宣做者的地址
shangyexuan = models.Author.objects.filter(name="上野宣").first()
print(shangyexuan.author_detail.addr)

反向查詢按表名小寫

# 查詢地址是日本的做者名字
address = models.AuthorDetail.objects.filter(addr="日本").first()
# address.author是做者對象
print(address.author.name)

一對多查詢

正向查詢按字段:正向查詢只會查出一個

# 查詢《圖解HTTP》這本書的出版社名字
book_obj = models.Book.objects.filter(title="圖解HTTP").first()
print(book_obj.publish.name)

反向查詢按表名小寫_set.all():返回結果是 queryset 對象

# 查詢人民郵電出版社出版的全部書
publish_obj = models.Publish.objects.filter(name="人民郵電出版社").first()
books = publish_obj.book_set.all()  
for book in books:
    print(book.title)

all() 拿出的是全部書的 queryset 對象,還能夠進一步篩選

# 查詢人民郵電出版社出版的書名以「IP」結尾的書
publish_obj = models.Publish.objects.filter(name="人民郵電出版社").first()
books = publish_obj.book_set.all().filter(title__endswith="IP")
for book in books:
    print(book.title)

多對多查詢

正向查詢按字段.all():正向查詢必定會查出多個

# 查詢《圖解HTTP》這本書的全部做者
book_obj = models.Book.objects.filter(title="圖解HTTP").first()
authors = book_obj.authors.all()
for author in authors:
    print(author.name)

反向查詢按表名小寫_set.all():返回結果是 queryset 對象

# 查詢上野宣寫的全部書
author_obj = models.Author.objects.filter(name="上野宣").first()
books = author_obj.book_set.all()
for book in books:
    print(book.title)

基於雙下劃線的跨表查詢

一對一查詢

# 查詢上野宣做者的地址
ret = models.Author.objects.filter(name="上野宣").values("author_detail__addr")
print(ret)
# 查詢地址是日本的做者名字
ret = models.AuthorDetail.objects.filter(addr="日本").values('author__name')
print(ret)

一對多查詢

# 查詢《圖解HTTP》這本書的出版社名字
ret = models.Book.objects.filter(title="圖解HTTP").values('publish__name')
print(ret)
# 查詢人民郵電出版社出版的全部書的名字
ret = models.Publish.objects.filter(name="人民郵電出版社").values('book__title')
print(ret)

多對多查詢

# 查詢《圖解HTTP》這本書的全部做者
ret = models.Book.objects.filter(title="圖解HTTP").values('authors__name')
print(ret)
# 查詢上野宣寫的全部書
ret = models.Author.objects.filter(name="上野宣").values('book__title')
print(ret)
# 查詢人民出版社出版過的全部書籍的名字以及做者的姓名
# 方式一:
ret = models.Book.objects.filter(publish__name="人民郵電出版社").values('title', 'authors__name')

# 方式二
ret = ret = models.Publish.objects.filter(name="人民郵電出版社").values('book__title', 'book__authors__name')

無論跨了多少張表,它們的查詢效率都是同樣的。由於 ORM 中,不管是多少張表,都是先把這些錶鏈接起來,再去進行查詢。可使用 print(ret.query) 查看 SQL 語句:

補充:

  • related_name:反向查詢時,若是定義了 related_name,則用 related_name 替換表名,例如:
publish = models.ForeignKey(to='Publish', to_field='nid', on_delete=models.CASCADE, related_name='bookList')
# 查詢人民郵電出版社出版過的全部書籍的名字和價格(一對多)
# 反向查詢再也不按表名book,而是 related_name:bookList

ret = Publish.objects.filter(name="人民郵電出版社").values('bookList__title', 'bookList__price')
  • related_query_name:反向查詢時,若是定義了 related_query_name,則用 related_query_name 替換字段名
  • 不建議替換修改

聚合查詢

aggregate(*args, **kwargs)

# 計算全部圖書的平均價格
from django.db.models import Avg
ret = models.Book.objects.all().aggregate(Avg('price'))
print(ret)      # {'price__avg': 59.0}

aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。若是想要爲聚合值指定一個名稱,能夠向聚合子句提供它。

ret = models.Book.objects.aggregate(average_price=Avg('price'))
print(ret)      # {'average_price': 59.0}

能夠向 aggregate() 子句中添加多個參數

from django.db.models import Avg, Max, Min

ret = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
# {'price__avg': 59.0, 'price__max': Decimal('89.00'), 'price__min': Decimal('29.00')}

分組查詢

annotate(*args, **kwargs)

# 統計每一本書做者個數
book_list = models.Book.objects.all().annotate(author_num=Count('authors'))
for book in book_list:
    print(book.title, str(book.author_num)+'個做者')
# 或者   
book_list = models.Book.objects.annotate(author_num=Count("authors")).values('title', 'author_num')
print(book_list)
# 統計每個出版社的最便宜的書
ret = models.Publish.objects.annotate(cheap_book=Min("book__price")).values('book__title', 'book__price')
print(ret)
# 統計每一本以圖解開頭的書籍的做者個數
ret = models.Book.objects.filter(title__startswith="圖解").annotate(author_num=Count("authors")).values('title', 'author_num')
print(ret)
# 統計不止一個做者的圖書
ret = models.Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('title', 'author_num')
print(ret)
# 根據一本圖書做者數量的多少對查詢集 QuerySet進行排序
ret = models.Book.objects.annotate(author_num=Count("authors")).order_by('author_num').values('title', 'author_num')
print(ret)

F查詢

F() 的實例能夠在查詢中引用字段,來比較同一個 model 實例中兩個不一樣字段的值。Django 支持 F() 對象之間以及 F() 對象和常數之間的加減乘除和取模的操做。

首先將 Book 表中添加兩條字段:

class Book(models.Model):
    # ...
    # 閱讀數
    read_num = models.IntegerField(default=0)
    # 評論數
    commit_num = models.IntegerField(default=0)
    # ...

而後再進行相關查詢:

from django.db.models import F, Q

# 把《圖解HTTP》這本書的評論數加1
ret = models.Book.objects.filter(title="圖解HTTP").update(commit_num=F('commit_num')+1)
# 查詢評論數大於閱讀數的書籍
ret = models.Book.objects.filter(commit_num__gt=F('read_num')).values('title', 'commit_num', 'read_num')
print(ret)

Q查詢

filter() 等方法中的關鍵字參數查詢都是一塊兒進行 AND 操做的。 若是須要執行更復雜的查詢(例如 OR 語句),可使用 Q 查詢。

# 查詢書名爲《圖解HTTP》或價格爲29的書籍
ret = models.Book.objects.filter(Q(title='圖解HTTP') | Q(price=29)).values('title')
print(ret)

Q 查詢可使用 &| 操做符組合起來。當一個操做符在兩個 Q 對象上使用時,它產生一個新的 Q 對象。

上面的查詢等同於 SQL 語句:

mysql> select app01_book.title from app01_book where(app01_book.title="圖解HTTP" or app01_book.price=29);
+-----------------+
| title           |
+-----------------+
| 圖解HTTP        |
| 追風箏的人       |
+-----------------+
2 rows in set (0.00 sec)

Q 對象可使用 ~ 操做符取反,查詢函數也能夠混合使用 Q 對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或 Q 對象)都將 AND 在一塊兒。可是,若是出現 Q 對象,它必須位於全部關鍵字參數的前面。例如:

# 查詢名字不是追風箏的人,而且價格大於50的書
ret = models.Book.objects.filter(~Q(title='追風箏的人'), price__gt=50).values('title')
print(ret)

外鍵補充

在實際開發中,外鍵一般不用

  • 約束性太強
  • 查詢效率會變低
  • db_constraint=False , 這樣寫在 orm 建立表的時候,外鍵就沒了

創建外鍵約束,包括 unique,都是爲了避免寫髒數據

相關文章
相關標籤/搜索