Django框架之模型層

Xiao0101發表於2024-04-07

一、前期準備

1、測試指令碼

當你只是想要測試Django中的某一個py檔案內容,那麼你可以不用書寫前後端互動的形式,而是直接寫一個測試指令碼即可

這內容其實就是最外部 manage.py 檔案中的上面幾句話

指令碼程式碼無論是寫在應用下的 tests.py檔案還是自己新建檔案,將內容寫在新檔案中,都會生效

from django.test import TestCase

# Create your tests here.
import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django05.settings')
    import django
    django.setup()
    
	# 在下面書寫我們需要測試的程式碼
    # 即所有的測試程式碼都必須等待環境準備完畢之後才能書寫

2、資料準備

在models.py裡面建立我們需要的資料庫中的表

class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()  # 年月日
    """
    DateField
    DateTimeField
        兩個重要引數
        auto_now: 每次運算元據的時候,該欄位會自動將當前時間更新
        auto_now_add: 在建立資料的時候會自動將當前建立時間記錄下來,之後只要不人為的修改,那麼就一直不變
    """

    def __str__(self):
        return f'物件:{self.name}'

3、配置檔案

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        # 資料庫名字
        'NAME': 'day64',
        # 使用者
        "USER": "root",
        # 密碼
        "PASSWORD": "111111",
        # IP
        "HOST": "127.0.0.1",
        # 埠
        "PORT": 3306,
        # 編碼集
        "CHARSET": "utf8",
    }
}
  • 在專案下的 init.py 中宣告資料庫型別
import pymysql

pymysql.install_as_MySQLdb()

二、單表操作

1、資料的增加

from django.test import TestCase

# Create your tests here.
import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django05.settings')
    import django
    django.setup()
    
	from app01 import models

    models.User.objects.all()

    # register_time (1)支援自己指定傳值
    res = models.User.objects.create(name='zhang', age=67, register_time='2000-1-1')
    # 返回值為物件本身
    print(res)  # User object
    
    # register_time (2)支援傳入日期物件
    import datetime
    # 生成一個當前的時間物件
    ctime = datetime.datetime.now()
    user_obj = models.User(name='quan', age=54, register_time=ctime)
    user_obj.save()

2、資料的刪除

pk會自動查詢到當前表的主鍵欄位,指代的就是當前表的主鍵欄位
用了pk之後,就不需要指代當前表的主鍵欄位到底叫什麼了

  • uid
  • pid
  • sid
  • ...

(1)查詢後直接刪除

res = models.User.objects.filter(pk=2).delete()

(2)先查詢再刪除

user_obj = models.User.objects.filter(pk=1).first()
user_obj.delete()

3、資料的修改

(1)查詢後直接修改

res = models.User.objects.filter(pk=5).update(name="quan")

(2)先查詢再修改

  • get方法返回的直接就是當前資料物件,但是方法不推薦使用
  • 因為一旦資料不存在該方法會直接報錯,而filter則不會,所以我們一般都是用filter
# 不推薦使用 : 如果查詢的資料不存在會直接報錯 ,fileter不會
user_obj = models.User.objects.get(pk=6)
user_obj = models.User.objects.filter(pk=6)
# 呼叫物件更改資料
user_obj.name = "xiao"
user_obj.save()

4、資料的查詢

(1)查詢全部:all

user_obj = models.User.objects.all()

(2)按指定條件過濾:filter和get

  • 方式一:filter
user_obj = models.User.objects.filter(age=18)
# 篩選後得到的物件是一個 QuerySet 物件,裡面會有所有符合新增的資料物件
print(user_obj) # <QuerySet [<User: User object (1)>, <User: User object (2)>]>

# 方法補充
# (1)獲取到當前 QuerySet 物件的第一個物件
print(user_obj.first())
# User object (1)
print(user_obj.last())
# User object (2)
  • 方式二:get
user_obj = models.User.objects.get(pk=1)
# 獲取到的是一個資料物件,只能獲取到唯一條件的資料物件
print(user_obj) # User object (1)

# 如果有多個符合條件的資料會報錯
# app01.models.User.MultipleObjectsReturned: get() returned more than one User -- it returned 2!
# 如果有不符合條件的資料會報錯
# app01.models.User.DoesNotExist: User matching query does not exist.

5、獲取到指定欄位的資料

(1)獲取到一個欄位的資料:values

  • 返回的資料格式為列表套字典 - 本質上是一個 QuerySet 物件 ,而不是真的列表
user_obj = models.User.objects.values("name")
print(user_obj)
# <QuerySet [{'name': 'dream'}, {'name': 'chimeng'}]>

(2)獲取到多個欄位的資料:values_list

  • 返回的資料格式為列表套元祖 - 本質上是一個 QuerySet 物件 ,而不是真的列表
user_obj = models.User.objects.values_list("name","age")
print(user_obj) 
# <QuerySet [('dream', 18), ('chimeng', 18)]>

6、檢視內部SQL語句的方式

(1)方式1

  • queryset物件.query==>檢視orm內部的sql語句
res = models.User.objects.values_list('name', 'age')
print(res.query)  # 檢視內部SQL語句

(2)方式2:所有的sql語句都能檢視

  • 去配置檔案中配置一下即可
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

(3)小結

  • 檢視sql語句的方式,只能用於queryset物件
  • 只有queryset物件才能夠點選query檢視內部的sql語句

7、去重

去重一定要是一模一樣的資料,如果有主鍵,那麼肯定不一樣

所以一定要去除主鍵後再去重

res = models.User.objects.values('name', 'age').distinct()

8、排序

(1)預設升序

user_obj = models.User.objects.order_by('age')
print(user_obj)
# <QuerySet [<User: User object (1)>, <User: User object (2)>, <User: User object (3)>, <User: User object (4)>]>

(2)降序

user_obj = models.User.objects.order_by('-age')
print(user_obj)
# <QuerySet [<User: User object (4)>, <User: User object (3)>, <User: User object (1)>, <User: User object (2)>]>

(3)反轉

  • 反轉的前提是資料已經經過排序過的資料

  • 只能對有序的資料進行反轉

user_obj = models.User.objects.order_by('age').reverse()
print(user_obj)
# <QuerySet [<User: User object (4)>, <User: User object (3)>, <User: User object (1)>, <User: User object (2)>]>

9、統計個數

user_obj = models.User.objects.count()
print(user_obj)
# 4

10、剔除結果

  • 排出在外
  • 將某個資料排出在結果之外
user_obj = models.User.objects.exclude(name="dream")
print(user_obj)
# res = models.User.objects.exclude(user="dream")

11、是否存在

  • 是否存在
  • 返回的是布林值
  • 用處不大,因為資料本身就有布林值的狀態
user_obj = models.User.objects.filter(name="dream").exists()
print(user_obj)
# True

12、總結:必知必會十三條

# 以下方法的前提均為 models.模型表名.objects 之後的方法

1. all()      查詢所有資料
2. filter()   帶有過濾條件的查詢
  我們在利用資料的主鍵欄位篩選資料的時候,可以不考慮主鍵欄位叫什麼,直接用pk代替
3. get()      直接拿資料物件,但是條件不存在直接報錯
4. first()    拿queryset裡面第一個元素
5. last()     拿queryset裡面最後一個元素

6. value()    可以指定獲取的資料欄位   select name,age from ...  返回的結果是列表套字典  <QuerySet [{'name': 'xiao', 'age': 18}, {'name': 'quandsb', 'age': 54}]>
7. values_list()    返回的結果像是列表套元組  <QuerySet [('xiao', 18), ('quandsb', 54)]>

8. distinct()
    res = models.User.objects.values('name', 'age').distinct()
    去重一定要是一模一樣的資料,如果有主鍵,那麼肯定不一樣
9. reverse()  反轉的前提是資料已經排過序了
10. order_by()  排序
    res = models.User.objects.order_by('age')  # 預設升序
    res = models.User.objects.order_by('-age')  # 降序
11. count()    統計當前資料的個數
12. exclude()    排除在外
13. exists()    

# 補充:檢視當前ORM語句的SQL查詢語句
# 注意可以使用此方法的必須是 QuerySet 物件
.query

三、神奇的雙下劃線查詢

1、條件大於

  • 年齡大於35歲的資料
res = models.User.objects.filter(age__gt=35)
print(res)

2、條件小於

  • 年齡小於35歲的資料
res = models.User.objects.filter(age__lt=35)
print(res)

3、條件大於等於

年齡大於等於35歲的資料

res = models.User.objects.filter(age__gte=35)
print(res)

4、條件小於等於

  • 年齡小於等於35歲的資料
res = models.User.objects.filter(age__lte=35)
print(res)

5、或條件

  • 年齡是18或者32或者40
res = models.User.objects.filter(age__in=(18, 32, 40))
print(res)

6、兩個條件之間

  • 年齡是18-40之間
  • 首尾都要
res = models.User.objects.filter(age__range=(18, 40))
print(res)

7、模糊查詢

  • 查詢出名字中含有 n 的資料 -- 模糊查詢
res = models.User.objects.filter(name__contains='n')
print(res)
  • 查詢出名字中含有 N 的資料,預設區分大小寫
res = models.User.objects.filter(name__contains='N')
print(res)
  • 查詢出名字中含有 N 的資料,忽略大小寫
res = models.User.objects.filter(name__icontains='N')
print(res)

8、以指定條件開頭/結尾

  • 以什麼開頭/結尾
res = models.User.objects.filter(name__startswith='d')
print(res)
  • 以什麼結尾
res = models.User.objects.filter(name__endswith='m')
print(res)

9、查詢時間日期

  • 查詢出注冊時間是2020年1月份的資料/年/月/日
res = models.User.objects.filter(register_time__month='1')
print(res)
res = models.User.objects.filter(register_time__year='2020')
print(res)
res = models.User.objects.filter(register_time__day='28')
print(res)

四、多表查詢引入

1、資料準備

在models.py裡面建立我們需要的資料庫中的表

(1)建立圖書表

  • 一本書只能有一個出版社,建立外來鍵關係為一對一
  • 一本書可以有多個作者,一個作者可以寫多本書,建立外來鍵關係為多對多
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_date = models.DateField(auto_now_add=True)

    # 庫存
    kucun = models.IntegerField(default=1000)
    # 賣出
    maichu = models.IntegerField(default=1000)

    # 自定義欄位使用
    # myfield = MyCharField(max_length=16,null=True)


    # 一對多
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
    # 對多對
    authors = models.ManyToManyField(to='Author')

    # 列印輸出資料,方便觀看
    def __str__(self):
        return self.title

(2)建立出版社表

  • 出版社不需要額外的外來鍵關係
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    # varchar(254)  該欄位型別不是給models看的,而是給後學我們會學到的校驗性元件看的

    def __str__(self):
        return f'物件:{self.name}'

(3)建立作者表

  • 作者具有作者詳情,所以作者和作者詳情表是一對一關係
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    
    # 一對一
    author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)

(4)建立作者詳情表

class AuthorDetail(models.Model):
    phone = models.BigIntegerField()  # 電話號碼用BigIntegerField或者直接用CharField
    addr = models.CharField(max_length=64)

(5)遷移資料庫

# 生成遷移記錄
python manage.py makemigrations

# 遷移記錄對資料庫生效
python manage.py migrate

2、外來鍵的增刪改查

(1)一對多外來鍵的增刪改查

① 增加

# 1. 直接寫實際欄位  id
models.Book.objects.create(title='解憂雜貨店', price=103.45, publish_id=1)
# 2. 虛擬欄位  物件
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='西遊記', price=666.66, publish=publish_obj)

② 刪除

models.Publish.objects.filter(pk=1).delete()  # 級聯刪除

③ 修改

#  - 直接寫實際欄位
models.Book.objects.filter(pk=1).update(publish_id=2)
#  - 虛擬欄位
publish_obj = models.Publish.objects.filter(pk=1).first()

models.Book.objects.filter(pk=1).update(publish=publish_obj)

④ 查詢

# 先查詢物件,然後物件.id
publish_obj = models.Publish.objects.filter(pk=1).first()
publish_obj.id = 2

(2)對多對外來鍵的增刪改查

  • 多對多 增刪改查 就是在操作第三張表

① 增加

  • 如何給書籍新增作者?
  • add給第三張表新增資料,括號內既可以傳數字,也可以傳物件,並且都支援多個
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(1)  # 書籍id為1的書籍繫結了一個主鍵為1的作者
book_obj.authors.add(2,3)

author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj,author_obj1)

② 刪除

remove:括號內既可以傳數字,也可以傳物件,並且都支援多個

book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.remove(2)
book_obj.authors.remove(1,3)

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj,author_obj1)

③ 修改

set:括號內必須傳一個可迭代物件,該物件既可以是數字也可以是物件,並且都支援多個

book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set([1,2])  # 括號內必須是一個可迭代物件
book_obj.authors.set([3])

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1])

④ 清空

clear:括號內不需要新增任何引數

# 在第三張表中清除某一本書和作者的繫結關係
book_obj = models.Book.objects.filter(pk=1).first()
# 不要加任何引數
book_obj.authors.clear()

五、多表查詢應用

1、正反向的概念

在Django中,正向和反向查詢是針對模型之間的關聯關係而言的。這兩個概念可以幫助您理解如何在模型之間進行資料訪問和查詢。

(1)正向查詢

  • 正向查詢:正向查詢是指您從定義關係的模型開始向關聯模型查詢資料。在一對多關係中,正向查詢是從擁有外來鍵的模型(一的一方)到外來鍵所關聯的模型(多的一方)的查詢。

在Django中,正向查詢是指從指定的模型例項開始,沿著定義的關聯向另一個模型例項查詢資料。正向查詢是透過使用關聯欄位(ForeignKey、OneToOneField、ManyToManyField)來實現的。以下是正向查詢的詳細說明和示例:

① 一對多關係的正向查詢

在一對多關係中,一個模型例項可以關聯多個另一個模型例項。透過正向查詢,您可以從一的一方訪問多的一方的資料。

假設有以下模型:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  • 正向查詢示例

    # 從 Author 到 Book,獲取作者寫的所有書籍
    author = Author.objects.get(id=1)
    books = author.book_set.all()
    

② 多對多關係的正向查詢

在多對多關係中,兩個模型之間存在多對多的關聯。透過正向查詢,您可以從一個模型例項訪問另一個模型例項的資料。

假設有以下模型:

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course)

class Course(models.Model):
    name = models.CharField(max_length=100)
  • 正向查詢示例

    # 從 Student 到 Course,獲取學生選修的所有課程
    student = Student.objects.get(id=1)
    courses = student.courses.all()
    

③ 一對一關係的正向查詢

在一對一關係中,每個模型例項只能與另一個模型例項相關聯。透過正向查詢,您可以從一個模型例項訪問另一個模型例項的資料。

假設有以下模型:

class Person(models.Model):
    name = models.CharField(max_length=100)

class Profile(models.Model):
    bio = models.TextField()
    person = models.OneToOneField(Person, on_delete=models.CASCADE)
  • 正向查詢示例

    # 從 Person 到 Profile,獲取人物的個人資料
    person = Person.objects.get(id=1)
    profile = person.profile
    

透過這些示例,您可以更好地理解和使用Django中的正向查詢功能,從而方便地在模型之間進行資料訪問和查詢操作。

(2)反向查詢

  • 反向查詢:反向查詢是指您從關聯模型開始向定義關係的模型查詢資料。在一對多關係中,反向查詢是從外來鍵所關聯的模型(多的一方)到擁有外來鍵的模型(一的一方)的查詢。

在Django中,反向查詢是透過使用雙下劃線(__)來訪問定義關係的模型的欄位。這種查詢方法允許您從關聯模型開始向定義關係的模型查詢資料。下面是一些反向查詢的詳細內容和示例:

① 一對多關係的反向查詢

在一對多關係中,一個模型擁有多個關聯模型的例項。透過反向查詢,您可以從多的一方訪問一的一方的資料。

假設有以下模型:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  • 反向查詢示例

    # 從 Book 到 Author,獲取書籍的作者
    book = Book.objects.get(id=1)
    author = book.author
    

② 多對多關係的反向查詢

在多對多關係中,兩個模型之間存在多對多的關聯。透過反向查詢,您可以從一個模型訪問另一個模型的資料。

假設有以下模型:

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course)

class Course(models.Model):
    name = models.CharField(max_length=100)
  • 反向查詢示例

    # 從 Course 到 Student,獲取選修該課程的學生
    course = Course.objects.get(id=1)
    students = course.student_set.all()
    

③ 多層級關聯的反向查詢

如果模型之間存在多層級的關聯,您可以透過多次使用雙下劃線來進行多層級反向查詢。

假設有以下模型:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

class Review(models.Model):
    content = models.TextField()
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
  • 多層級反向查詢示例

    # 從 Review 到 Author,獲取書評所屬書籍的作者
    review = Review.objects.get(id=1)
    author = review.book.author
    

透過這些示例,您可以更好地理解和使用Django中的反向查詢功能,從而方便地在模型之間進行資料訪問和查詢操作。

(3)查詢方法

  • 正向查詢按欄位
  • 反向查詢按表明名(小寫),_set
  • book>>>>外來鍵欄位在書那(正向)>>>>publish
    publish>>>>外來鍵欄位在書那(反向)>>>>book

2、基於物件的跨表查詢(子查詢)

(1)查詢書籍主鍵為1的出版社

book_obj = models.Book.objects.filter(pk=1).first()
# 書查出版社 - 正向 - 按欄位查
res = book_obj.publish
print(res)  # Publish object
print(res.name)  # 東方出版社
print(res.addr)  # 東方

(2)查詢書籍主鍵為2的作者

book_obj = models.Book.objects.filter(pk=1).first()
# 書查作者 - 正向查詢按欄位
res = book_obj.authors
print(res)  # app01.Author.None
# 列表中存放的是作者物件
print(res.all())  # <QuerySet [<Author: Author object>]>

(3)查詢作者的電話號碼

author_obj = models.Author.objects.filter(name="dream").first()
# 作者查詢作者詳情 - 正向查詢按欄位
res = author_obj.author_detail
print(res)  # AuthDetail object
print(res.phone)  # 110
print(res.addr)  # 山東

(4)查詢出版社是東方出版社出版的書

# 先拿到出版社物件
publish_obj = models.Publish.objects.filter(name="東方出版社").first()
# 出版社查書 - 主鍵欄位在書 - 反向查詢
res = publish_obj.book_set.all()
# publish_obj.book_set
# print(res) # app01.Book.None
# publish_obj.book_set.all()
print(res)  # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>

(5)查詢作者是xiao寫過的書

# 先拿到作者物件
author_obj = models.Author.objects.filter(name="xiao").first()
# 作者查書 - 主鍵在書 - 反向
res = author_obj.book_set.all()
print(res)  # <QuerySet [<Book: Book object>]>

(6)查詢手機號是 110的作者姓名

# 先拿到作者詳情的物件
author_detail_obj = models.AuthDetail.objects.filter(phone=110).first()
# 詳情查作者 - 主鍵在作者 - 反向
res = author_detail_obj.author
print(res)  # Author object
print(res.name)  # xiao

(7)補充

  • 正向查詢什麼時候需要加.all()

    • 當查詢返回的結果可以是多個的時候就需要用 .all()
    • 當查詢的結果只有一個的時候就不需要加
    • 例如
      • book_obj.publish 一本書對應一個出版社
      • book_obj.authors.all() 一本書可能對應多個作者,所以需要.all()
      • author_obj.author_detail 一個作者對應一個作者詳情
  • 反向查詢什麼時候需要加 _set.all()

    • 查詢結果可以是多個的時候需要加
    • 當查詢的結果只有一個的時候就不需要加
    • 如果不加的話,會報 app01.Book.None這個錯誤
  • 在書寫ORM語句的時候跟寫SQL語句一樣的

  • 不要企圖一次性將ORM語句寫完,如果比較複雜,需要寫一些看一些

3、基於雙下劃線的跨表查詢(聯表操作)

(1)查詢xiao的手機號和作者的姓名

  • 正向:先查詢到作者資訊再 .value(需要查詢資訊的表__需要查詢的欄位,其他欄位)
res = models.Author.objects.filter(name="xiao").values('author_detail__phone', 'name')
print(res)  # <QuerySet [{'author_detail__phone': 110, 'name': 'xiao'}]>
  • 反向:先拿到詳情,再用作者詳情關聯作者表,透過 __欄位的方法 過濾出我們想要的指定資料
res = models.AuthDetail.objects.filter(author__name="xiao").values('phone', 'author__name')
# AuthDetail.objects.filter(author__name="xiao")
print(res)  # <QuerySet [<AuthDetail: AuthDetail object>]>
# AuthDetail.objects.filter(author__name="xiao").values('phone','author__name')
print(res)  # <QuerySet [{'phone': 110, 'author__name': 'xiao'}]>

(2)查詢書籍主鍵ID為1的出版社名字和書的名字

  • 正向:先過濾出書籍ID為1的書籍物件,再去關聯出版者表,利用__欄位取值
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': '三國演義', 'publish__name': '東方出版社'}]>
  • 反向:先查詢到指定出版社,再從出版社反向找到書籍名字
res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '東方出版社', 'book__title': '三國演義'}]>

(3)查詢書籍主鍵ID為1的作者姓名

  • 正向:先拿到 書籍主鍵ID為1的物件,再關聯作者資訊表,透過__欄位取值
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)  # <QuerySet [{'authors__name': 'xiao'}]>
  • 反向 : 先拿到 書籍ID為1的作者資料再去取作者的名字
res = models.Author.objects.filter(book__id=1).values('name')
print(res)  # <QuerySet [{'name': 'xiao'}]>

(4)查詢書籍主鍵是1的作者的手機號

# book author authordetail
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 110}]>

(5)小結

  • 只要掌握了正反向的概念以及雙下劃線查詢
  • 就可以無限跨表

六、聚合查詢

1、知識儲備

  • 聚合查詢通常情況下都是配合分組一起使用的
  • 只要是和資料庫相關的模組基本上都在 django.db.models 裡面
  • 如果這裡面沒有 那大機率可能在 django.db 裡面

正常情況下,我們是需要 先進行分組再進行 聚合函式運算的

但是Django給我們提供了一種方法 : aggregate 可以不分組進行某個欄位的聚合函式。

2、aggregate()

在Django中,aggregate()方法用於執行聚合查詢,對查詢結果集進行聚合計算並返回一個包含聚合計算結果的字典。這個方法允許您在查詢中使用各種聚合函式(如Count、Sum、Avg、Min、Max等)來實現不同的聚合操作。

(1)語法

aggregate(**kwargs)

(2)引數

  • **kwargs: 一個字典,其中鍵是用於標識聚合計算結果的別名,值是聚合表示式(如Count、Sum、Avg等)。

(3)示例

  • 匯入聚合函式
from django.db.models import Max, Min, Sum, Count, Avg

示例1:所有書的平均價格

res = models.Book.objects.aggregate(Avg('price'))
print(res)  # {'price__avg': 1890.083333}

在這個示例中,Avg('price')表示對Book模型中的書籍價格進行平均值計算。

示例2:所有聚合函式一起使用

res = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Sum('price'), Count('pk'))
print(res) # {'price__avg': 1890.083333, 'price__max': Decimal('5959.25'), 'price__min': Decimal('555.25'), 'price__sum': Decimal('11340.50'), 'pk__count': 6}

(4)注意事項

  • aggregate()方法返回一個字典,其中鍵是您為每個聚合結果指定的別名,值是相應的聚合計算結果。
  • 您可以同時指定多個聚合計算,每個計算都應該作為**kwargs引數傳遞。
  • 聚合查詢通常用於獲取整個查詢集的聚合資訊,而不是在每個物件上執行聚合計算。

(5)總結

透過使用aggregate()方法,您可以方便地在Django中執行各種聚合查詢操作,如計數、求和、平均值等。這對於分析資料和生成彙總報告非常有用。記住,在使用aggregate()時,您可以根據需要選擇合適的聚合函式,併為每個聚合結果指定一個別名以便後續引用。

七、分組查詢

1、知識儲備

MySQL分組查詢都有哪些特點?

分組之後預設只能獲取到分組的資料,組內其他欄位都無法直接獲取了

2、annotate()

在Django中,annotate()方法用於在查詢結果集中新增聚合計算的註釋,而不是對整個查詢結果集進行聚合。這允許您在每個物件上執行聚合計算,並將計算結果作為新的欄位新增到每個物件中。

(1)語法

annotate(**kwargs)
  • models 後面跟的是什麼,就是按什麼分組
res = models.Book.objects.annotate()
  • 如果想按照指定的欄位分組該如何處理

  • 如果 annotate 前面沒東西 則會按照 Book 分組 ,如果前面有引數 就會按照前面的引數進行分組 price

models.Book.objects.values('price').annotate()

(2)引數

  • **kwargs: 一個字典,其中鍵是用於標識註釋欄位的新欄位名,值是聚合表示式(如Count、Sum、Avg等)。

(3)示例

  • 匯入聚合函式
from django.db.models import Max, Min, Sum, Count, Avg

示例1:計算每個作者的書籍數量

from django.db.models import Count

authors = Author.objects.annotate(book_count=Count('book'))
for author in authors:
    print(author.name, author.book_count)

在這個示例中,Count('book')表示對每個作者的書籍數量進行計數,並將計算結果作為新欄位book_count新增到每個作者物件中。

示例2:統計不止一個作者的圖書

# (3.1)先按照圖書分組
# (3.2)過濾出不止一個作者的圖書
# 我的資料有限,我統計的是大於 0 的作者的圖書
res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=0).values('title','author_num')
print(res)  # <QuerySet [{'title': '三國演義', 'author_num': 1}]>

只要你的ORM語句得出的結果還是一個queryset物件,那麼他就還可以繼續無限制的點queryset物件封裝的方法

(4)注意事項

  • annotate()方法用於對每個物件執行聚合計算,將計算結果作為新欄位新增到每個物件中。
  • 您可以同時指定多個註釋欄位,每個欄位應作為**kwargs引數傳遞。
  • annotate()方法通常用於在查詢結果集中新增聚合計算的註釋,以便在後續操作中使用這些計算結果。
  • 如果你們的機器上如果出現分組查詢報錯的情況,那麼你就需要修改資料庫嚴格模式了

(5)總結

透過使用annotate()方法,您可以在Django查詢結果集中新增聚合計算的註釋,為每個物件新增新的聚合計算欄位。這對於在每個物件級別執行聚合操作,並在結果中包含這些計算值非常有用。記住,在使用annotate()時,您可以根據需要選擇合適的聚合函式,併為每個註釋欄位指定一個新的欄位名以便後續引用。

八、F與Q查詢

1、關鍵詞引入

from django.db.models import F, Q

2、F查詢

  • 能夠幫助你直接獲取到列表中某個欄位對應的資料

注意: 在操作字串型別的資料的時候, F不能夠直接做到字串的拼接

(1)查出賣出數大於庫存數的書籍

# F 查詢 : 幫助我們直接獲取到表中的某個欄位對應的資料
res = models.Book.objects.filter(sales__gt=F('stock'))
print(res)  # <QuerySet [<Book: 水滸傳>]>

(2)將所有書籍的價格提升 50

res = models.Book.objects.update(price=F('price') + 500)
print(res)  # 6 - 影響到了 6 條資料

(3)將所有書的名稱後邊加上 爆款 兩個字

# 在操作字串的時候,F查詢不能夠直接做到字串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
# 上述的兩個匯入是對字串的操作,如果不匯入而直接+"爆款",名字就會變成空白

res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
print(res) # 6 - 影響到了 6 條資料

3、Q查詢

  • filter()等方法中的關鍵字引數查詢都是一起進行"and"
  • 如果你需要執行更復雜的查詢(列如ORM語句),你可以使用Q物件。

(1)查詢賣出數大於100或者價格小於500的書籍

# (1.1)直接使用 filter 查詢資料,逗號隔開,裡面放的引數是 and 關係
res = models.Book.objects.filter(sales__gt=100, price__lt=500)
print(res)  # <QuerySet []>

# (1.2)直接使用 Q 查詢資料,逗號隔開,裡面放的引數還是 and 關係
res = models.Book.objects.filter(Q(sales__gt=100), Q(price__lt=500))
print(res)  # <QuerySet []>

# (1.3)直接使用 Q 查詢資料,逗號可以換成其他連線符達到效果
res = models.Book.objects.filter(Q(sales__gt=100) or Q(price__lt=500))
# 二者等價 (| :或關係) ( ~ : 取反 not 關係)
res = models.Book.objects.filter(Q(sales__gt=100) | Q(price__lt=500))
print(res)  # <QuerySet [<Book: 三國演義爆款>, <Book: 水滸傳爆款>, <Book: 論語爆款>, <Book: 孫子兵法爆款>]>

(2) Q的高階用法 能夠將查詢條件的左邊也變成 字串形式

  • 能夠將查詢條件的左邊也變成字串的形式 而 不是變數形式
# 先產生一個空物件  實列化
q = Q()
q.connector = 'or'  # and修改成or
# q物件裡面有一個children
q.children.append(('maichu__gt', 100))
# 第一個元素就會被當作查詢條件的左邊 第二個元素會被當作查詢條件右邊
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)  # filter 除了可以放條件 還可以放物件
print(res)  # 預設還是and關係
  • 可以在去物件內 children裡面 無限制的新增元素 新增元組,兩個元素。
  • 而且還支援修改 orandnot
  • 預設是and
# 或查詢
q = Q()
q |= Q(條件一)
q |= Q(條件二)
qs = Model.objects.filter(q)

# 並查詢
q = Q()
q &= Q(條件一)
q &= Q(條件二)
qs = Model.objects.filter(q)

4、總結

  • F() 表示式允許您在查詢中引用欄位值,執行資料庫欄位之間的比較或進行算術操作。
  • Q() 物件允許您構建複雜的查詢邏輯,可以組合多個查詢條件並使用邏輯運算子進行連線。

九、django中如何開啟事務

1、知識回顧

事務的四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability)。

(1)原子性(Atomicity)

  • 事務被視為一個不可分割的原子操作單元。

  • 這意味著要麼全部操作成功並永久儲存,要麼全部操作失敗並回滾到事務開始前的狀態,不存在部分成功或部分失敗的情況。

(2)一致性(Consistency)

  • 事務在執行前後,資料庫都必須保持一致狀態。

  • 這意味著事務執行前後,資料庫中的資料必須滿足所有定義的完整性約束,例如列級別的約束、外來鍵關係等。

(3)隔離性(Isolation)

  • 事務之間應該相互隔離,每個事務的執行應該與其他事務的執行相互獨立,互不干擾。

  • 隔離性確保了多個事務可以併發執行,而不會產生不一致的結果。

(4)永續性(Durability)

  • 一旦事務成功提交後,其所做的修改將永久儲存在資料庫中,即使發生系統故障或重啟,資料也能夠恢復到提交後的狀態。
  • 永續性透過將事務日誌寫入非易失性儲存介質來實現,如硬碟驅動器或固態硬碟。

2、事務名詞

  • 開啟事務:Start Transaction
  • 事務結束:End Transaction
  • 提交事務:Commit Transaction
  • 回滾事務:Rollback Transaction

3、預設事務行為

  • Django是支援事務操作的,它的預設事務行為是自動提交
  • 具體表現形式為:每次資料庫操作(比如呼叫save()方法)會立即被提交到資料庫中。
  • 但是如果你希望把連續的SQL操作包裹在一個事務裡,就需要手動開啟事務。

4、開啟事務

(1)全域性開啟事務

① 配置

  • 在Web應用中,常用的事務處理方式是將每次請求都包裹在一個事務中。
  • 全域性開啟事務只需要將資料庫的配置項ATOMIC_REQUESTS設定為True,如下所示:
DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.mysql',
       'NAME': 'db1',
       'HOST': 'dbhost',
       'PORT': '3306',
       'USER': 'dbuser',
       'PASSWORD': 'password',
        #全域性開啟事務,繫結的是http請求響應整個過程
       'ATOMIC_REQUESTS': True, 
   }

② 原理

  • 每當有請求過來時,Django會在呼叫檢視方法前開啟一個事務。
  • 如果完成了請求處理並正確返回了結果,Django就會提交該事務。
  • 否則,Django會回滾該事務。

③ 區域性取消事務

  • 如果你全域性開啟了事務,你仍然可以使用non_atomic_requests裝飾器讓某些檢視方法不受事務控制
from django.db import transaction

 
@transaction.non_atomic_requests
def my_view(request):
    do_stuff()
 
 
# 如有多個資料庫,讓使用otherdb的檢視不受事務控制
@transaction.non_atomic_requests(using='otherdb')
def my_other_view(request):
    do_stuff_on_the_other_database()
  • 雖然全域性開啟事務很簡單,但Django並不推薦開啟全域性事務。
  • 因為一旦將事務跟 HTTP 請求繫結到一起時,每一個請求都會開啟事務,當訪問量增長到一定的時候會造成很大的效能損耗。
  • 在實際開發過程中,很多GET請求根本不涉及到事務操作,一個更好的方式是區域性開啟事務按需使用。

(2)區域性開啟事務

  • Django專案中區域性開啟事務,可以藉助於transaction.atomic方法。
  • 使用它我們就可以建立一個具備原子性的程式碼塊,一旦程式碼塊正常執行完畢,所有的修改會被提交到資料庫。
  • 反之,如果有異常,更改會被回滾。

① 裝飾器使用

  • atomic經常被當做裝飾器來使用,如下所示:
# 案例一:函式檢視
 from django.db import transaction
 
 
 @transaction.atomic
 def viewfunc(request):
     # This code executes inside a transaction.
     do_stuff()
 
 
 # 案例二:基於類的檢視
 from django.db import transaction
 from rest_framework.views import APIView
 
 
 class OrderAPIView(APIView):
       # 開啟事務,當方法執行完以後,自動提交事務
       @transaction.atomic  
       def post(self, request):
           pass 

② 區域性程式碼

  • 使用了atomic裝飾器,整個檢視方法裡的程式碼塊都會包裹著一個事務中執行。
  • 有時我們希望只對檢視方法裡一小段程式碼使用事務,這時可以使用transaction.atomic()顯式地開啟事務,如下所示:
 from django.db import transaction
 
 
 def viewfunc(request):
     # 預設自動提交
     do_stuff()
       
     # 顯式地開啟事務
     with transaction.atomic():
         # 下面這段程式碼在事務中執行
         do_more_stuff()

5、Savepoint儲存點

  • 在事務操作中,我們還會經常顯式地設定儲存點(savepoint)。
  • 一旦發生異常或錯誤,我們使用savepoint_rollback方法讓程式回滾到指定的儲存點。
  • 如果沒有問題,就使用savepoint_commit方法提交事務。
def viewfunc(request):
   # 預設自動提交
   do_stuff()

   # 顯式地開啟事務
   with transaction.atomic():
       # 建立事務儲存點
       sid = transaction.savepoint()

       try:
           do_more_stuff()
       except Exception as e:
           # 如發生異常,回滾到指定地方。
           transaction.savepoint_rollback(sid)          
       # 如果沒有異常,顯式地提交一次事務
       transaction.savepoint_commit(sid)


   return HttpResponse("Success")
  • 注意:雖然SQLite支援儲存點,但是sqlite3 模組設計中的缺陷使它們很難使用。

6、事務提交後回撥函式

  • 有的時候我們希望當前事務提交後立即執行額外的任務
  • 比如客戶下訂單後立即郵件通知賣家,這時可以使用Django提供的on_commit方法
# 例1
from django.db import transaction

def do_something():
  pass  # send a mail, invalidate a cache, fire off a Celery task, etc.


transaction.on_commit(do_something)
# 例2:呼叫celery非同步任務
transaction.on_commit(lambda: some_celery_task.delay('arg1'))

十、ORM常用欄位及引數

1、常用欄位型別

(1)CharField

  • max_length:指定欄位的最大長度,用於儲存較短的字串。例如,models.CharField(max_length=100)
  • verbose_name: 欄位的註釋

(2)TextField

  • 用於儲存較長的文字資料,沒有固定長度限制。
    • 例如,models.TextField()

(3)IntegerField

  • 用於儲存整數值。可以指定引數如defaultnullblank等。
    • 例如,models.IntegerField(default=0)

(4)FloatField

  • 用於儲存浮點數。可以指定引數如defaultnullblank等。
    • 例如,models.FloatField(default=0.0)

(5)DecimalField

  • max_digits:指定儲存在資料庫中的數字的最大位數(包括小數點前後的位數)。

    • 例如,如果 max_digits=6,則表示最多可以儲存 6 位數字。
  • decimal_places:指定儲存在資料庫中的小數部分的位數。

    • 例如,如果 decimal_places=2,則表示小數點後最多可以有 2 位數字。

(6)BooleanField

  • 用於儲存布林值(True或False)。
    • 例如,models.BooleanField(default=False)

(7)DateTimeField

  • 用於儲存日期和時間。可以指定引數如auto_nowauto_now_add等。例如,models.DateTimeField(auto_now=True)

(8)DateField

  • 用於儲存日期。
    • 例如,models.DateField()

(9)TimeField

  • 用於儲存時間。
    • 例如,models.TimeField()

(10)EmailField

  • 用於儲存電子郵件地址。
    • 例如,models.EmailField()

(11)ImageField

  • 用於儲存圖片檔案。
    • 例如,models.ImageField(upload_to='images/')

(12)FileField

  • 用於儲存檔案。
    • 例如,models.FileField(upload_to='files/')

(13)ForeignKey

  • 用於定義一對多關係。指向另一個模型的主鍵。
    • 例如,models.ForeignKey(OtherModel, on_delete=models.CASCADE)

(14)ManyToManyField

  • 用於定義多對多關係。
    • 例如,models.ManyToManyField(OtherModel)

(15)OneToOneField

  • 用於定義一對一關係。
    • 例如,models.OneToOneField(OtherModel, on_delete=models.CASCADE)

(16)UUIDField

  • 用於儲存UUID(通用唯一識別符號)。
    • 例如,models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

2、常用欄位引數

(1)default

  • 指定欄位的預設值。
    • 例如,default=0

(2)null

  • 設定為True允許欄位為空。
    • 例如,null=True

(3)blank

  • 設定為True允許欄位為空白。
    • 例如,blank=True

(4)choices

  • 提供選項列表,用於下拉框等選擇欄位。
    • 例如,choices=[('M', 'Male'), ('F', 'Female')]

(5)unique

  • 設定為True確保欄位值在整個表中唯一。
    • 例如,unique=True

(6)verbose_name

  • 設定欄位的可讀性更好的名稱。
    • 例如,verbose_name='Full Name'

(7)related_name

  • 指定反向關係的名稱。
    • 例如,related_name='books'

(8)on_delete

  • 在外來鍵關係中定義級聯刪除行為。
    • 例如,on_delete=models.CASCADE

(9)db_index

  • 設定為True在資料庫中建立索引。
    • 例如,db_index=True

(10)**auto_now **和 auto_now_add

  • 分別用於自動設定欄位的值為當前時間。
    • 例如,auto_now=True

(11)validators

  • 提供驗證器函式列表,用於驗證欄位值。
    • 例如,validators=[validate_email]

(12)to

  • 設定要關聯的表,用於外來鍵欄位
    • 例如,to='Author'

(13)to_field

  • 設定要關聯的表的欄位,用於外來鍵欄位,預設不寫關聯的就是另外一張表的主鍵欄位。
    • 例如,to_field='id'

3、欄位與sql語句之間的關係

'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

4、自定義欄位

django除了給你提供了很多欄位型別之外,還支援你自定義欄位

class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        # 呼叫父類的init方法
        super().__init__(max_length=max_length, *args, **kwargs)  # max_length一定要是關鍵字的形式傳入

    def db_type(self,connection):
        """
        返回真正的資料型別及各種約束條件
        :param connection:
        :return:
        """
        return f'char({self.max_length})'
    
# 自定義欄位使用
myfield = MyCharField(max_length=16,null=True)

十一、資料庫查詢最佳化

1、ORM語句的特點

惰性查詢:如果你僅僅只是書寫了ORM語句,在後面根本沒有用到該語句所查詢出來的引數,那麼ORM會自動識別,直接不執行。

2、only與defer

(1)惰性查詢的體現

  • 執行這句話的時候沒有SQL語句執行
models.Book.objects.all()
  • 這樣執行的時候就會返回相應的資料
res = models.Book.objects.all()
print(res)  # 只有需要用到真正的資料時,才會走資料庫查詢資料

(2)示例

  • 想要獲取書籍表中所有數的名字
res = models.Book.objects.values('title')
for d in res:
    print(d.get('title'))

(3)最佳化

根據上述程式碼進行最佳化:實現獲取到的是一個資料物件,然後點 title 就能夠拿到書名,並且沒有其他欄位

① only

res = models.Book.objects.only('title')
res = models.Book.objects.all()  # all不需要走資料庫
print(res)  # <QuerySet [<Book: 水滸傳爆款爆款>, <Book: 西遊記爆款爆款>, <Book: 鋼鐵是怎樣煉成的爆款爆款>, <Book: 白夜行爆款爆款>, <Book: 解憂雜貨店爆款爆款>]>
for i in res:
    print(i.title)  # 點選only括號內的欄位,不會走資料庫
    print(i.price)  # 點選only括號內沒有的欄位,會重新走資料庫查詢,而all不需要走資料庫

② defer

  • defer 和 only 相反
  • defer 括號內放的欄位不在查詢的物件裡面 ,查詢該欄位需要重新走資料庫
  • 而如果查詢的是非括號內的欄位 則不需要走資料庫
res = models.Book.objects.defer('title')  # 物件除了沒有title屬性之外其他都有
print(res)
for s in res:
  print(res.price)

(4)小結

  • only:

    • only方法用於指定只返回指定欄位的查詢結果。
    • 透過only方法,您可以限制查詢結果中包含的欄位,這有助於減少資料傳輸量和提高查詢效能。
    • 例如,Model.objects.only('field1', 'field2')將只返回field1field2欄位的查詢結果。
  • defer:

    • defer方法用於延遲載入指定欄位,這意味著這些欄位將在被訪問時才從資料庫中載入。
    • 透過defer方法,您可以推遲載入某些欄位,這在處理大型資料集時可以提高效能。
    • 例如,Model.objects.defer('field1', 'field2')將推遲載入field1field2欄位,直到訪問這些欄位時才會從資料庫中載入。

區別總結:

  • only用於選擇要返回的欄位,只返回指定欄位的查詢結果。
  • defer用於推遲載入欄位,這些欄位在被訪問時才會從資料庫中載入。
  • 與跨表操作有關
res = models.Book.objects.all()
for i in res:
    print(i.publish.name)  # 每迴圈一次就要走一次資料庫查詢
  • 內部直接將book表和publish表的資料連起來,一次性將所有資料封裝到查詢出來的物件,這個時候物件可以直接點去取兩個變中的資料而不需走資料庫。
  • select_related括號內只能放外來鍵欄位(一對一 一對多)
  • 但是多對多的外來鍵欄位不行
res = models.Book.objects.select_related('publish')  # INNER JOIN:聯表操作
print(res)
for i in res:
    print(i.publish.name)
  • prefetch_related該方法內部其實就是子查詢,就是將子查詢查詢出來的所有結果也給你封裝到物件中,給你的感覺就好像也是一次性搞定的
  • 較聯表操作多了一步
res = models.Book.objects.prefetch_related('publish')  # 子查詢
for i in res:
    print(i.publish.name)  # 每迴圈一次就要走一次資料庫查詢

(3)小結

在Django中,select_relatedprefetch_related都是用於最佳化資料庫查詢的方法,特別是在處理關聯表時非常有用。這裡我將解釋它們之間的區別:

  • select_related:

    • select_related用於在查詢時一次性獲取關聯物件的資料,而不是每次訪問關聯物件時都要執行額外的查詢。
    • 當您知道查詢將涉及到某些關聯欄位,並且希望在單個查詢中獲取這些關聯物件的資料時,select_related是很有用的。
    • select_related執行的是SQL的JOIN操作,將關聯物件的資料一起取出,因此可以減少資料庫查詢次數,提高效能。
    • 例如,Model.objects.select_related('related_model')將在查詢Model物件時一起獲取related_model的資料。
  • prefetch_related:

    • prefetch_related用於在查詢時獲取相關物件的資料,但它是透過執行額外的查詢來實現的,而不是透過JOIN操作。
    • prefetch_related適用於多對多關係或反向外來鍵關係,並且當您需要在查詢結果中訪問相關物件的資料時,這是一個不錯的選擇。
    • prefetch_related執行額外的查詢來獲取相關物件的資料,然後將這些資料快取起來,以便在訪問時使用。
    • 例如,Model.objects.prefetch_related('related_model')將在查詢Model物件時獲取related_model的資料,但是透過額外的查詢來實現。

區別總結:

  • select_related使用JOIN操作一次性獲取關聯物件的資料,減少資料庫查詢次數,適用於正向關係。
  • prefetch_related透過額外的查詢獲取關聯物件的資料並快取,適用於多對多關係或反向外來鍵關係。

透過合理使用select_relatedprefetch_related,您可以減少資料庫查詢次數,減少查詢時間,並提高查詢效能。希望這能幫助您理解它們之間的區別和用法。如果您有任何進一步的問題,請隨時提出。

需要注意的是,使用select_relatedprefetch_related方法時,需謹慎選擇要載入的關聯物件

避免過度載入導致的效能問題,同時也要注意資料庫索引的最佳化以提高查詢效率。

十二、choices引數(資料欄位設計常見)(重要)

1、引入

這個世界上不是所有的東西都是非黑即白,針對某個可以列舉完全的可能性欄位,我們應該如何儲存?

只要某個欄位的可能性是可以列舉完全的,那麼一般情況下都會採用choices引數

2、資料準備

from django.db import models

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    # 性別
    gender_choices = (
        (1, '男'),
        (2, '女'),
        (3, '其他')
    )
    gender = models.IntegerField(choices=gender_choices)

    score_choices = (
        ('A','優秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不及格')
    )
    # 保證欄位型別跟列舉出來的元組第一個資料型別一致即可
    score = models.CharField(choices=score_choices,null=True)
    """
    該gender欄位存的還是數字,但是如果存的數字在上面元組列舉的範圍之內,那麼可以非常輕鬆的獲取到數字對應的真正的內容。
    """

那麼問題又來了

  1. gender欄位存的數字不在上述元組列舉的範圍內容,該怎麼辦?
  2. 如果在,如何獲取對應的中文資訊?

3、問題解決

(1)gender欄位存的數字不在上述元祖列舉的範圍內

import os

from django.test import TestCase

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django06.settings')
    import django
    django.setup()

    from app01 import models
    # 存
    models.User.objects.create(username='xiao',age=18,gender=1)
    models.User.objects.create(username='xu',age=34,gender=2)
    models.User.objects.create(username='zhang',age=45,gender=3)
    
    # 存的時候,沒有列舉出來的數字也能存(範圍還是按照欄位型別決定)
    models.User.objects.create(username='quan',age=54,gender=4)

沒有報錯,且第四條已經插入到資料庫中

(2)獲取在上述元祖列舉的範圍內gender欄位存的數字

  • 只要是choices引數的欄位,如果你想要獲取對應資訊,固定的寫法為 get_欄位名_display()
# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)  # 1
"""只要是choices引數的欄位,如果你想要獲取對應資訊,固定的寫法為 get_欄位名_display()"""
print(user_obj.get_gender_display())  # 男
user_obj = models.User.objects.filter(pk=4).first()


"""如果沒有對應欄位,不會報錯,欄位存入是什麼展示的就是什麼"""
print(user_obj.get_gender_display())  # 4

4、總結

  • choice引數使用場景非常廣泛

  • 例如

    • 支付方式的選擇
    • 生源的來源地
    • 分數的分類
    • 學歷的分類
    • ...

十三、多對多三種建立方式

1、全自動

  • 利用ORM自動幫我們建立第三張表關係
class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自動
    authors = models.ManyToManyField(to='Author')


class Author(models.Model):
    name = models.CharField(max_length=32)
  • 優點
    • 程式碼不需要自己寫,非常方便,還支援ORM提供操作第三張表的方法
  • 缺點
    • 第三張關係表的擴充套件性極差(沒辦法新增額外欄位)

2、純手動(不建議使用)

class Book(models.Model):
    name = models.CharField(max_length=32)

class Author(models.Model):
    name = models.CharField(max_length=32)

class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 優點
    • 第三張表完全取決於自己進行額外的擴充
  • 缺點
    • 需要寫程式碼較多
    • 不能使用ORM提供的相關方法

3、半自動(實際專案用這個)

class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自動
    # through_fields : 當前表是誰,第一個引數就是誰
    # 判斷的本質:透過第三張表查詢對應的表,需要用到哪個欄位就把哪個欄位放在前面
    authors = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))


class Author(models.Model):
    name = models.CharField(max_length=32)


class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 這樣雖然可以使用ORM的正反向查詢,但是沒法使用add,set,remove,clear這四個方法

十四、補充MTV與MVC模型

  • MTV模型和MVC模型是兩種常見的軟體設計模式,用於組織和管理使用者介面和應用程式的邏輯。
  • 雖然它們存在一些相似之處,但它們在設計和應用上有一些不同。

1、MTV模型

MTV模型是指Model-Template-View(模型-模板-檢視)模型,是Django框架中採用的一種設計模式。它的核心思想是將應用程式分為三個主要部分:

  • 模型(Model):

    • 模型表示應用程式中處理資料的結構和行為。
    • 它通常與資料庫互動,並定義了資料的儲存和操作方式。
  • 模板(Template):

    • 模板負責處理使用者介面的顯示。
    • 它定義了應用程式的外觀和佈局,並將動態資料與靜態頁面結合在一起,生成最終的使用者介面。
  • 檢視(View):

    • 檢視處理應用程式的邏輯和業務流程。
    • 它接收使用者的請求,從模型中獲取資料,將資料傳遞給模板進行渲染,並生成響應返回給使用者。

MTV模型的優點在於它可以很好地將應用程式的邏輯和使用者介面進行分離,使程式碼更容易維護和擴充套件。

2、MVC模型

MVC模型是指Model-View-Controller(模型-檢視-控制器)模型,是一種常見的軟體設計模式,廣泛應用於Web開發和其他應用程式中。

  • 模型(Model):

    • 模型負責處理應用程式的資料邏輯。
    • 它包含了資料的儲存和操作方式,並定義了資料在應用程式內部如何互動和被操作。
  • 檢視(View):

    • 檢視是使用者介面的表示,負責展示資料給使用者並接收使用者的輸入操作。
    • 它通常從模型中獲取資料,並將其顯示給使用者。
  • 控制器(Controller):

    • 控制器處理使用者的互動和請求,並根據使用者的行為作出相應的響應。
    • 它接收使用者的輸入,並更新模型和檢視以反映使用者的操作。

MVC模型的優點在於它可以很好地分離應用程式的不同元件,使得程式碼更易於維護、測試和重用。

3、總結

  • MTV模型主要用於Django框架中,透過將應用程式分為模型、模板和檢視,提供了一種清晰的架構方案。
  • MVC模型則是一個通用的設計模式,廣泛應用於各種型別的應用程式中。
  • 無論使用哪種模型,都能幫助開發者更好地組織和管理程式碼,並實現可擴充套件和可維護的應用程式。

相關文章