1. 程式人生 > >9 Django 的模型層2

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)