9 Django 的模型層2
一.多表操作
1.建立模型
例項:我們來假定下面這些概念,欄位和關係
作者模型:一個作者有姓名和年齡。
作者詳細模型:把作者的詳情放到詳情表,手機號,家庭住址等資訊。作者詳情模型和作者模型之間是一對一的關係(one-to-one)
出版商模型:出版商有名稱,所在城市以及email。
書籍模型: 書籍有書名和出版日期,一本書可能會有多個作者,一個作者也可以寫多本書,所以作者和書籍的關係就是多對多的關聯關係(many-to-many);一本書只應該由一個出版商出版,所以出版商和書籍是一對多關聯關係(one-to-many)。
(1).模型建立如下:
from django.db import models # Create your models here. #書籍 class Book(models.Model): bid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) pub_date = models.DateField() publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE) authors = models.ManyToManyField(to="Author") # 多對多詳情請看最下方程式碼 #出版社 class Publish(models.Model): pid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) email = models.CharField(max_length=32) #作者 class Author(models.Model): aid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() email = models.CharField(max_length=32) authord = models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE) #作者詳細資訊 class AuthorDetail(models.Model): addr = models.CharField(max_length=32) tel = models.IntegerField() # authord = models.OneToOneField(Author,on_delete=models.CASCADE) # class Author2Book(models.Model): # id = models.AutoField(primary_key=True) # book = models.ForeignKey(to="Book",on_delete=models.CASCADE) # author = models.ForeignKey(to="Author",on_delete=models.CASCADE)
(2).生成如下表:
(3).注意事項:
- 表的名稱myapp_modelname,是根據模型中的元資料自動生成的,也可以覆寫為別的名稱
id
欄位是自動新增的- 對於外來鍵欄位,Django 會在欄位名上新增"_id" 來建立資料庫中的列名
- 這個例子中的
CREATE TABLE
SQL 語句使用MySQL 語法格式,要注意的是Django會根據settings 中指定的資料庫型別來使用相應的SQL語句。 - 定義好模型之後,你需要告訴Django _使用_這些模型。你要做的就是修改配置檔案中的INSTALL_APPSZ中設定,在其中新增
models.py
- 外來鍵欄位 ForeignKey 有一個 null=True 的設定(它允許外來鍵接受空值 NULL),你可以賦給它空值 None 。
2.新增表記錄
操作前先簡單的錄入一些資料:
publish表:
author表:
authordetail表:
(1).一對多
# 一對多的新增方式 # 方式一: # pub_obj = Publish.objects.filter(name = "北京大學出版社").first() # print(pub_obj) # book = Book.objects.create( title="python入門",price=120,pub_date="2011-07-9",publish=pub_obj ) # 方式二: # book = Book.objects.create(title="Java入門",price=111,pub_date="2008-8-9",publish_id=3) #推薦 #示例: # author = Author.objects.create(name="冰紅茶",age=22,email="876",authord_id=3)
核心:book.publish與book.publish_id是什麼?
(2).多對多
#多對多的新增方式 # book = Book.objects.create(title="計算機文化基礎",price=50,pub_date="2004-6-2",publish_id=5) # 方式一: # whh = Author.objects.filter(name = "娃哈哈").first() # shuangww = Author.objects.filter(name = "爽歪歪").first() # book.authors.add(whh,shuangww) #方式二: # book.authors.add(3) # 方式三: # book.authors.add(*[2,4])
核心:book.authors是什麼?
(3).多對多關係其它常用API:
book_obj.authors.remove() # 將某個特定的物件從被關聯物件集合中去除。 ====== book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被關聯物件集合 book_obj.authors.set() #先清空再設定
# 解除繫結 # whh = Author.objects.filter(name='娃哈哈').first() # book = Book.objects.filter(pk = 4).first() # # book.authors.remove(1) # book.authors.clear() #解除再繫結 # book = Book.objects.filter(pk = 5).first() # 方法一: # book.authors.clear() # book.authors.add(1) # 方法二: # book.authors.set("1")
3.基於物件的跨表查詢
正向查詢:關聯屬性所在的表查詢相關聯表記錄 反向查詢:得到一個物件,按表名小寫
(1).一對多查詢(Publish與Book)
""" 正向查詢:按欄位:book.publish Book ---------------------------------> Publish <-------------------------------- 反向查詢:按表名小寫_set.all() 例:pub_obj.book_set.all() """ # 1.查詢python入門這本書出版社的名字和郵箱 #方式一:分步查詢,不用關聯鍵值 # book = Book.objects.filter(title="python入門").first() # pub_obj = Publish.objects.filter(pk = book.publish_id).first() # print(pub_obj.name) # print(pub_obj.email) #方式二:關聯鍵值查詢 # book = Book.objects.filter(title="python入門").first() # print(book.publish) #與book這本書關聯的出版社物件 # print(book.publish.name) #與book這本書關聯的出版社物件的可呼叫方法 # print(book.publish.email) #與book這本書關聯的出版社物件的可呼叫方法 # 2.查詢北京大學出版社出版的所有的書籍的名稱 # pub_obj = Publish.objects.get(name="北京大學出版社") # print(pub_obj.book_set.all()) #輸出一個QuerySet物件 # print(pub_obj.book_set.all().values("title")) #輸出一個QuerySet物件
(2).多對多查詢(Author與Book)
""" 正向查詢:按欄位:book.authors.all() Book ----------------------------------> Author <--------------------------------- 反向查詢:按表名小寫_set.all() 例:bgc.book_set.all() """ # #1.查詢python入門這本書的作者的年齡 # book = Book.objects.filter(title="python入門").first() # ret = book.authors.all().values("age") #與這本書關聯的所有作者的queryset的集合 # print(ret) # #2.查詢冰紅茶出版過的所有的書籍名稱 # bgc = Author.objects.filter(name="冰紅茶").first() # print(bgc.book_set.all().values("title"))
(3).一對一查詢(Author與AuthorDetail)
""" 正向查詢:按欄位:爽歪歪.authord Author ------------------------------> AuthorDetail <----------------------------- 反向查詢:按表名小寫:authord.author """ # #1.查詢爽歪歪的手機號 # shuangww = Author.objects.filter(name="爽歪歪").first() # print(shuangww.authord.tel) # #2.查詢手機號為12345的作者的名字 # authord = AuthorDetail.objects.filter(tel=12345).first() # print(authord.author.name)
4.基於雙下劃線的跨表查詢
Django 還提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯絡。要做跨關係查詢,就使用兩個下劃線來連結模型(model)間關聯欄位的名稱,直到最終連結到你想要的model 為止。
''' 正向查詢按欄位,反向查詢按表名小寫用來告訴ORM引擎join哪張表 '''
#####################基於雙下劃線的跨表查詢(基於join實現的)########################## #KEY: 正向查詢:按欄位,反向查詢:按表名小寫 # 1.查詢python入門這本書出版社的名字 # ret = Book.objects.filter(title="python入門").values("price") # 方式一: # ret = Book.objects.filter(title="python入門").values("publish__name") # print(ret) #方式二: # ret = Publish.objects.filter(book__title="python入門").values("name") # print(ret) # 2.查詢人民出版社出版的所有書籍的名稱 # 方式一: # ret = Publish.objects.filter(name="人民出版社").values("book__title") # print(ret) # 方式二: # ret = Book.objects.filter(publish__name="人民出版社").values("title") # print(ret) # 3.查詢Python高階這本書籍的作者的姓名和年齡 # 方式一: # ret = Book.objects.filter(title="Python高階").values("authors__name","authors__age") # print(ret) # 方式二: # ret = Author.objects.filter(book__title="Python高階").values("name","age") # print(ret) # 4.查詢冰紅茶出版過的所有的書籍名稱 # 方式一: # ret = Book.objects.filter(authors__name="冰紅茶").values('title') # print(ret) # 方式二: # ret = Author.objects.filter(name="冰紅茶").values("book__title") # print(ret) # 5.查詢乳娃娃的手機號 # 方式一: # ret = Author.objects.filter(name="乳娃娃").values("authord__tel") # print(ret) # 方式二: # ret = AuthorDetail.objects.filter(author__name="乳娃娃").values("tel") # print(ret) # 6.查詢手機號為12345的作者的名字 # 方式一: # ret = AuthorDetail.objects.filter(tel=12345).values('author__name') # print(ret) # 方式二: # ret = Author.objects.filter(authord__tel=12345).values("name") # print(ret) #########連續跨表############# # 1.查詢南京大學出版社出版過的所有書籍的名字以及作者的姓名 # 方式一: # ret = Publish.objects.filter(name="南京大學出版社").values("book__title","book__authors__name") # print(ret) # 方式二: # ret = Book.objects.filter(publish__name="南京大學出版社").values("title","authors__name") # print(ret) # 2.手機號以345開頭的作者出版過的所有書籍名稱以及出版社名稱 # 方式一: # ret = Author.objects.filter(authord__tel__startswith=345).values("book__title","book__publish__name") # print(ret) # 方式二: # ret = AuthorDetail.objects.filter(tel__startswith = 345).values("author__book__title","author__book__publish__name") # print(ret) #方式三: # ret = Book.objects.filter(authors__authord__tel__startswith=345).values("title","publish__name") # print(ret)
5.聚合查詢與分組查詢
(1).新增一個新的員工表
class Emp(models.Model): name = models.CharField(max_length=32) age = models.IntegerField(default=20) dep = models.CharField(max_length=32) pro = models.CharField(max_length=32) salary = models.DecimalField(max_digits=8,decimal_places=2) bonus = models.DecimalField(max_digits=8,decimal_places=2)
(2).聚合
aggregate(*args, **kwargs)
from django.db.models import Avg, Max, Min, Sum, Count #聚合 # 1.查詢所有書籍的平均價格 # ret = Book.objects.all().aggregate(priceAvg = Avg("price")) # print(ret) # 2.查詢所有書籍的個數 # ret = Book.objects.all().aggregate(c = Count(1)) # print(ret)
aggregate()是QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的識別符號,值是計算出來的聚合值。鍵的名稱是按照欄位和聚合函式的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。
如果你希望生成不止一個聚合,你可以向aggregate()子句中新增另一個引數。所以,如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢:
# 1.查詢所有書籍的平均價格 # ret = Book.objects.all().aggregate(priceAvg = Avg("price"), price_max = Max("price"), price_min = Min("price")) # print(ret)
(3).分組
#分組 #單表分組查詢 # KEY:annotate()前values哪一個欄位就按哪一個欄位 group by # 1.查詢書籍表每一個出版社id以及對應的書籍個數 # ret = Book.objects.all().values("publish_id").annotate(c = Count(1)) # .all()可以省略 # print(ret) # 2.查詢每一個部門的名稱以及對應的員工的平均薪水 # ret = Emp.objects.values("dep").annotate(avg_salary = Avg("salary")) # print(ret) # 3.查詢每個省份的名稱以及對應的員工的最大年齡 # ret = Emp.objects.values("pro").annotate(max_age = Max('age')) # print(ret) #單表按主鍵查詢無意義 # Emp.objects.values("pk").annotate() #跨表分組查詢 # 1.查詢每一個出版社的名稱以及對應的書籍平均價格 # ret = Publish.objects.values("name").annotate(avg_price=Avg("book__price")) # print(ret) # 2.查詢每一個作者的名字以及出版的書籍的最高價格 # ret = Author.objects.values("pk","name").annotate(max_price=Max("book__price")) # print(ret) # 3.查詢每一個書籍的名稱以及對應的作者的個數 # ret = Book.objects.values("title").annotate(c = Count("authors")) # print(ret)
#########補充############ # 拿上面3題來說 # ret = Book.objects.annotate(c = Count("authors")).values("title","c") # 省略了.all()和.values()預設以book表中每條記錄作為分組物件,返回分組後的book表,但其中包含c欄位 # print(ret) # 1.查詢作者數不止一個的書籍名稱以及作者個數 # ret = Book.objects.annotate(c = Count("authors")).filter(c__gt = 1).values("title","c") # print(ret) # 2.根據一本圖書作者數量的多少查詢集,QuerySet進行排序 # ret = Book.objects.annotate(c = Count("authors")).values("title").order_by("c") # print(ret) # 3.統計每一本以py開頭的書籍的名稱以及作者個數 # ret = Book.objects.filter(title__istartswith="py").annotate(c = Count("authors")).values("title") # print(ret)
annotate()為呼叫的QuerySet中每一個物件都生成一個獨立的統計值(統計方法用聚合函式)
總結 :跨表分組查詢本質就是將關聯表join成一張表,再按單表的思路進行分組查詢。
6.F查詢與Q查詢
F:將兩個欄位的做比較
Q:或,與,非
###############################F 與 Q############################### from django.db.models import F,Q # 1.查詢價格大於100的所有書籍的名稱 # ret = Book.objects.filter(price__gt=100).values("title") # print(ret) # 2.查詢獎金大於等於2倍工資的所有的員工名稱 # ret = Emp.objects.filter(bonus__gte=F("salary")*2).values("name") # print(ret) # 3.給每個員工加1000塊工資 # ret = Emp.objects.update(salary=1000+F("salary")) # print(ret) # 與:& 或:| 非:~ # 4.查詢工資大於5000或者獎金大於5000的員工 #錯誤: # ret = Emp.objects.filter(salary__gt=5000,bonus__gt=5000) #這是"且"查不到值 # print(ret) #<QuerySet []> #正確: # ret = Emp.objects.filter(Q(salary__gt=5000)|Q(bonus__gt=5000)).values("name") # print(ret) # 5.Q的示例用法: # ret = Emp.objects.filter(Q(Q(salary__gt=5000)|~Q(bonus__gt=5000))&Q(age__gt=30)).values("name") # print(ret) # ret = Emp.objects.filter(Q(Q(salary__gt=5000)|~Q(bonus__gt=5000)),age__gt=30).values("name") # print(ret) # ret = Emp.objects.filter(age__gt=30,Q(Q(salary__gt=5000) | ~Q(bonus__gt=5000))).values("name") #錯誤 # print(ret)