Django(19)QuerySet API

Silent丿丶黑羽發表於2021-05-19

前言

我們通常做查詢操作的時候,都是通過模型名字.objects的方式進行操作。其實模型名字.objects是一個django.db.models.manager.Manager物件,而Manager這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過Python動態新增的方式,從QuerySet類中拷貝過來的。示例圖如下:

所以我們如果想要學習ORM模型的查詢操作,必須首先要學會QuerySet上的一些API的使用
 

QuerySet 21個常用的API

filter

filter:將滿足條件的資料提取出來,返回一個新的QuerySet。具體詳情可檢視這篇:https://www.cnblogs.com/jiakecong/p/14780601.html
 

exclude

exclude:排除滿足條件的資料,返回一個新的QuerySet。示例程式碼如下:

Article.objects.exclude(title__contains='hello')

以上程式碼的意思是提取那些標題不包含hello的圖書。
 

annotate

annotate:給QuerySet中的每個物件都新增一個使用查詢表示式(聚合函式、F表示式、Q表示式、Func表示式等)的新欄位。示例程式碼如下:

articles = Article.objects.annotate(author_age=F("author__age"))

以上程式碼將在每個物件中都新增一個author__age的欄位,用來顯示這個文章的作者的年齡。
 

order_by

order_by:指定將查詢的結果根據某個欄位進行排序。如果要倒敘排序,那麼可以在這個欄位的前面加一個負號。示例程式碼如下:

 # 根據建立的時間正序排序
 articles = Article.objects.order_by("create_time")
 # 根據建立的時間倒序排序
 articles = Article.objects.order_by("-create_time")
 # 根據作者的名字進行排序
 articles = Article.objects.order_by("author__name")
 # 首先根據建立的時間進行排序,如果時間相同,則根據作者的名字進行排序
 articles = Article.objects.order_by("create_time",'author__name')

一定要注意的一點是,多個order_by,會把前面排序的規則給打亂,而使用後面的排序方式。比如以下程式碼:

 articles = Article.objects.order_by("create_time").order_by("author__name")

他會根據作者的名字進行排序,而不是使用文章的建立時間。
 

values

values:用來指定在提取資料出來,需要提取哪些欄位。預設情況下會把表中所有的欄位全部都提取出來,可以使用values來進行指定,並且使用了values方法後,提取出的QuerySet中的資料型別不是模型,而是在values方法中指定的欄位和值形成的字典:

 articles = Article.objects.values("title",'content')
 for article in articles:
     print(article)

以上列印出來的article是類似於{"title":"abc","content":"xxx"}的形式。如果在values中沒有傳遞任何引數,那麼將會返回一個字典,字典中包含這個模型中所有的屬性。
如果我們想要提取的是這個模型上關聯物件的屬性,那麼也是可以的,示例程式碼如下:

articles = Article.objects.values('title', 'content', 'author__name')

以上將會提取author的name欄位,如果我們不想要這個名字,想自定義名字,可以使用關鍵字引數,示例程式碼如下:

articles = Articles.objects.values('title', 'content', authorName=F('author__name'))

注意:自定義的名字不能跟模型上本身擁有的欄位一樣,比如author__name名字改成author那麼會報錯,因為Article模型上本身擁有一個欄位叫做author,會產生衝突
 

values_list

values_list:類似於values。只不過返回的QuerySet中,儲存的不是字典,而是元組。示例程式碼如下:

 articles = Article.objects.values_list("id","title")
 print(articles)

那麼在列印articles後,結果為<QuerySet [(1,'abc'),(2,'xxx'),...]>等。
如果在values_list中只有一個欄位。那麼你可以傳遞flat=True,這樣返回的結果就不在是一個元組,而是整個欄位的值,示例程式碼如下:

 articles2 = Article.objects.values_list("title",flat=True)

那麼以上返回的結果是

abc
xxx

 

all

all:獲取這個ORM模型的QuerySet物件。
 

select_related:在提取某個模型的資料的同時,也提前將相關聯的資料提取出來。比如提取文章資料,可以使用select_relatedauthor資訊提取出來,以後再次使用article.author的時候就不需要再次去訪問資料庫了。可以減少資料庫查詢的次數。示例程式碼如下:

 article = Article.objects.get(pk=1)
 >> article.author # 重新執行一次查詢語句
 article = Article.objects.select_related("author").get(pk=2)
 >> article.author # 不需要重新執行查詢語句了

注意:selected_related只能用在一對多或者一對一中,不能用在多對多或者多對一中。比如可以提前獲取文章的作者,但是不能通過作者獲取這個作者的文章,或者是通過某篇文章獲取這個文章所有的標籤。
 

prefetch_related:這個方法和select_related非常的類似,就是在訪問多個表中的資料的時候,減少查詢的次數。這個方法是為了解決多對一和多對多的關係的查詢問題。比如要獲取標題中帶有hello字串的文章以及他的所有標籤,示例程式碼如下:

from django.db import connection
articles = Article.objects.prefetch_related("tag_set").filter(title__contains='hello')
print(articles.query) # 通過這條命令檢視在底層的SQL語句
for article in articles:
    print("title:",article.title)
    print(article.tag_set.all())

# 通過以下程式碼可以看出以上程式碼執行的sql語句
for sql in connection.queries:
    print(sql)

但是如果在使用article.tag_set的時候,如果又建立了一個新的QuerySet那麼會把之前的SQL優化給破壞掉。比如以下程式碼:

 tags = Tag.obejcts.prefetch_related("articles")
 for tag in tags:
     articles = tag.articles.filter(title__contains='hello')  # 因為filter方法會重新生成一個QuerySet,因此會破壞掉之前的sql優化

 # 通過以下程式碼,我們可以看到在使用了filter的,他的sql查詢會更多,而沒有使用filter的,只有兩次sql查詢
 for sql in connection.queries:
     print(sql)

那如果確實是想要在查詢的時候指定過濾條件該如何做呢,這時候我們可以使用django.db.models.Prefetch來實現,Prefetch這個可以提前定義好queryset。示例程式碼如下:

 tags = Tag.objects.prefetch_related(Prefetch("articles",queryset=Article.objects.filter(title__contains='hello'))).all()
 for tag in tags:
     articles = tag.articles.all()
     for article in articles:
         print(article)

 for sql in connection.queries:
     print('='*30)
     print(sql)

因為使用了Prefetch,即使在查詢文章的時候使用了filter,也只會發生兩次查詢操作
 

defer

defer:在一些表中,可能存在很多的欄位,但是一些欄位的資料量可能是比較龐大的,而此時你又不需要,比如我們在獲取文章列表的時候,文章的內容我們是不需要的,因此這時候我們就可以使用defer來過濾掉一些欄位。這個欄位跟values有點類似,只不過defer返回的不是字典,而是模型。示例程式碼如下:

articles = Article.objects.defer("title")
for article in articles:
    print('article.id')

defer雖然能過濾欄位,但是有些欄位是不能過濾的,比如id,即使你過濾了,也會提取出來。
 

only

only:跟defer類似,只不過defer是過濾掉指定的欄位,而only是隻提取指定的欄位。
 

get

get:獲取滿足條件的資料。這個函式只能返回一條資料,並且如果給的條件有多條資料,那麼這個方法會丟擲MultipleObjectsReturned錯誤,如果給的條件沒有任何資料,那麼就會丟擲DoesNotExit錯誤。所以這個方法在獲取資料,只能有且只有一條。
 

create

create:建立一條資料,並且儲存到資料庫中。這個方法相當於先用指定的模型建立一個物件,然後再呼叫這個物件的save方法。示例程式碼如下:

article = Article(title='abc')
article.save()

# 下面這行程式碼相當於以上兩行程式碼
article = Article.objects.create(title='abc')

 

get_or_create

get_or_create:根據某個條件進行查詢,如果找到了那麼就返回這條資料,如果沒有查詢到,那麼就建立一個。示例程式碼如下:

obj,created= Category.objects.get_or_create(title='預設分類')

如果有標題等於預設分類的分類,那麼就會查詢出來,如果沒有,則會建立並且儲存到資料庫中。這個方法的返回值是一個元組,元組的第一個引數obj是這個物件,第二個引數created代表是否建立的。
 

bulk_create

bulk_create:一次性建立多個資料。示例程式碼如下:

Tag.objects.bulk_create([
    Tag(name='111'),
    Tag(name='222'),
])

 

count

獲取提取的資料的個數。如果想要知道總共有多少條資料,那麼建議使用count,而不是使用len(articles)這種。因為count在底層是使用select count(*)來實現的,這種方式比使用len函式更加的高效。
 

first和last

first和last:返回QuerySet中的第一條和最後一條資料
 

aggregate

aggregate:使用聚合函式。具體詳情可參考這篇:https://www.cnblogs.com/jiakecong/p/14784109.html
 

exists

exists:判斷某個條件的資料是否存在。如果要判斷某個條件的元素是否存在,那麼建議使用exists,這比使用count或者直接判斷QuerySet更有效得多。示例程式碼如下:

result = Book.objects.filter(name="三國演義").exists()
    print(result)

 

distinct

distinct:去除掉那些重複的資料。這個方法如果底層資料庫用的是MySQL,那麼不能傳遞任何的引數。比如想要提取所有銷售的價格超過80元的圖書,並且刪掉那些重複的,那麼可以使用distinct來幫我們實現,示例程式碼如下:

books = Book.objects.filter(bookorder__price__gte=80).distinct()

需要注意的是,如果在distinct之前使用了order_by,那麼因為order_by會提取order_by中指定的欄位,因此再使用distinct就會根據多個欄位來進行唯一化,所以就不會把那些重複的資料刪掉。示例程式碼如下:

orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()

那麼以上程式碼因為使用了order_by,即使使用了distinct,也會把重複的book_id提取出來。
 

update

update:執行更新操作,在SQL底層走的也是update命令。比如要將所有category為空的articlearticle欄位都更新為預設的分類。示例程式碼如下:

Article.objects.filter(category__isnull=True).update(category_id=3)

 

delete

delete:刪除所有滿足條件的資料。刪除資料的時候,要注意on_delete指定的處理方式。
 

切片

切片操作:有時候我們查詢資料,有可能只需要其中的一部分。那麼這時候可以使用切片操作來幫我們完成。QuerySet使用切片操作就跟列表使用切片操作是一樣的。示例程式碼如下:

books = Book.objects.all()[1:3]
for book in books:
    print(book)

切片操作並不是把所有資料從資料庫中提取出來再做切片操作。而是在資料庫層面使用LIMIEOFFSET來幫我們完成。所以如果只需要取其中一部分的資料的時候,建議大家使用切片操作。
 

Django將QuerySet轉換為SQL語句去執行的五種情況

  1. 迭代:在遍歷QuerySet物件的時候,會首先先執行這個SQL語句,然後再把這個結果返回進行迭代。比如以下程式碼就會轉換為SQL語句:
 for book in Book.objects.all():
     print(book)
  1. 使用步長做切片操作:QuerySet可以類似於列表一樣做切片操作。做切片操作本身不會執行SQL語句,但是如果如果在做切片操作的時候提供了步長,那麼就會立馬執行SQL語句。需要注意的是,做切片後不能再執行filter方法,否則會報錯。
  2. 呼叫len函式:呼叫len函式用來獲取QuerySet中總共有多少條資料也會執行SQL語句。
  3. 呼叫list函式:呼叫list函式用來將一個QuerySet物件轉換為list物件也會立馬執行SQL語句。
  4. 判斷:如果對某個QuerySet進行判斷,也會立馬執行SQL語句。

相關文章