天天看點

Pycharm開發Django項目QuerySet API教程

queryset api:

我們通常做查詢操作的時候,都是通過<code>模型名字.objects</code>的方式進行操作。其實<code>模型名字.objects</code>是一個<code>django.db.models.manager.manager</code>對象,而<code>manager</code>這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過<code>python</code>動态添加的方式,從<code>queryset</code>類中拷貝過來的。示例圖如下:

是以我們如果想要學習<code>orm</code>模型的查找操作,必須首先要學會<code>queryset</code>上的一些<code>api</code>的使用。

在使用<code>queryset</code>進行查找操作的時候,可以提供多種操作。比如過濾完後還要根據某個字段進行排序,那麼這一系列的操作我們可以通過一個非常流暢的<code>鍊式調用</code>的方式進行。比如要從文章表中擷取标題為<code>123</code>,并且提取後要将結果根據釋出的時間進行排序,那麼可以使用以下方式來完成:

可以看到<code>order_by</code>方法是直接在<code>filter</code>執行後調用的。這說明<code>filter</code>傳回的對象是一個擁有<code>order_by</code>方法的對象。而這個對象正是一個新的<code>queryset</code>對象。是以可以使用<code>order_by</code>方法。

那麼以下将介紹在那些會傳回新的<code>queryset</code>對象的方法。

<code>filter</code>:将滿足條件的資料提取出來,傳回一個新的<code>queryset</code>。具體的<code>filter</code>可以提供什麼條件查詢。請見查詢操作章節。

<code>exclude</code>:排除滿足條件的資料,傳回一個新的<code>queryset</code>。示例代碼如下:

以上代碼的意思是提取那些标題不包含<code>hello</code>的圖書。

<code>annotate</code>:給<code>queryset</code>中的每個對象都添加一個使用查詢表達式(聚合函數、f表達式、q表達式、func表達式等)的新字段。示例代碼如下:

以上代碼将在每個對象中都添加一個<code>author__name</code>的字段,用來顯示這個文章的作者的年齡。

<code>order_by</code>:指定将查詢的結果根據某個字段進行排序。如果要倒叙排序,那麼可以在這個字段的前面加一個負号。示例代碼如下:

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

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

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

以上列印出來的<code>article</code>是類似于<code>{"title":"abc","content":"xxx"}</code>的形式。

如果在<code>values</code>中沒有傳遞任何參數,那麼将會傳回這個惡模型中所有的屬性。

<code>values_list</code>:類似于<code>values</code>。隻不過傳回的<code>queryset</code>中,存儲的不是字典,而是元組。示例代碼如下:

那麼在列印<code>articles</code>後,結果為<code>&lt;queryset [(1,'abc'),(2,'xxx'),...]&gt;</code>等。

如果在<code>values_list</code>中隻有一個字段。那麼你可以傳遞<code>flat=true</code>來将結果扁平化。示例代碼如下:

<code>all</code>:擷取這個<code>orm</code>模型的<code>queryset</code>對象。

<code>select_related</code>:在提取某個模型的資料的同時,也提前将相關聯的資料提取出來。比如提取文章資料,可以使用<code>select_related</code>将<code>author</code>資訊提取出來,以後再次使用<code>article.author</code>的時候就不需要再次去通路資料庫了。可以減少資料庫查詢的次數。示例代碼如下:

<code>select_related</code>隻能用在<code>一對多</code>或者<code>一對一</code>中,不能用在<code>多對多</code>或者<code>多對一</code>中。比如可以提前擷取文章的作者,但是不能通過作者擷取這個作者的文章,或者是通過某篇文章擷取這個文章所有的标簽。

<code>prefetch_related</code>:這個方法和<code>select_related</code>非常的類似,就是在通路多個表中的資料的時候,減少查詢的次數。這個方法是為了解決<code>多對一</code>和<code>多對多</code>的關系的查詢問題。比如要擷取标題中帶有<code>hello</code>字元串的文章以及他的所有标簽,示例代碼如下:

但是如果在使用<code>article.tag_set</code>的時候,如果又建立了一個新的<code>queryset</code>那麼會把之前的<code>sql</code>優化給破壞掉。比如以下代碼:

那如果确實是想要在查詢的時候指定過濾條件該如何做呢,這時候我們可以使用<code>django.db.models.prefetch</code>來實作,<code>prefetch</code>這個可以提前定義好<code>queryset</code>。示例代碼如下:

因為使用了<code>prefetch</code>,即使在查詢文章的時候使用了<code>filter</code>,也隻會發生兩次查詢操作。

<code>defer</code>:在一些表中,可能存在很多的字段,但是一些字段的資料量可能是比較龐大的,而此時你又不需要,比如我們在擷取文章清單的時候,文章的内容我們是不需要的,是以這時候我們就可以使用<code>defer</code>來過濾掉一些字段。這個字段跟<code>values</code>有點類似,隻不過<code>defer</code>傳回的不是字典,而是模型。示例代碼如下:

在看以上代碼的<code>sql</code>語句,你就可以看到,查找文章的字段,除了<code>title</code>,其他字段都查找出來了。當然,你也可以使用<code>article.title</code>來擷取這個文章的标題,但是會重新執行一個查詢的語句。示例代碼如下:

<code>defer</code>雖然能過濾字段,但是有些字段是不能過濾的,比如<code>id</code>,即使你過濾了,也會提取出來。

<code>only</code>:跟<code>defer</code>類似,隻不過<code>defer</code>是過濾掉指定的字段,而<code>only</code>是隻提取指定的字段。

<code>get</code>:擷取滿足條件的資料。這個函數隻能傳回一條資料,并且如果給的條件有多條資料,那麼這個方法會抛出<code>multipleobjectsreturned</code>錯誤,如果給的條件沒有任何資料,那麼就會抛出<code>doesnotexit</code>錯誤。是以這個方法在擷取資料的隻能,隻能有且隻有一條。

<code>create</code>:建立一條資料,并且儲存到資料庫中。這個方法相當于先用指定的模型建立一個對象,然後再調用這個對象的<code>save</code>方法。示例代碼如下:

<code>get_or_create</code>:根據某個條件進行查找,如果找到了那麼就傳回這條資料,如果沒有查找到,那麼就建立一個。示例代碼如下:

如果有标題等于<code>預設分類</code>的分類,那麼就會查找出來,如果沒有,則會建立并且存儲到資料庫中。

這個方法的傳回值是一個元組,元組的第一個參數<code>obj</code>是這個對象,第二個參數<code>created</code>代表是否建立的。

<code>bulk_create</code>:一次性建立多個資料。示例代碼如下:

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

<code>first</code>和<code>last</code>:傳回<code>queryset</code>中的第一條和最後一條資料。

<code>aggregate</code>:使用聚合函數。

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

<code>distinct</code>:去除掉那些重複的資料。這個方法如果底層資料庫用的是<code>mysql</code>,那麼不能傳遞任何的參數。比如想要提取所有銷售的價格超過80元的圖書,并且删掉那些重複的,那麼可以使用<code>distinct</code>來幫我們實作,示例代碼如下:

需要注意的是,如果在<code>distinct</code>之前使用了<code>order_by</code>,那麼因為<code>order_by</code>會提取<code>order_by</code>中指定的字段,是以再使用<code>distinct</code>就會根據多個字段來進行唯一化,是以就不會把那些重複的資料删掉。示例代碼如下:

那麼以上代碼因為使用了<code>order_by</code>,即使使用了<code>distinct</code>,也會把重複的<code>book_id</code>提取出來。

<code>update</code>:執行更新操作,在<code>sql</code>底層走的也是<code>update</code>指令。比如要将所有<code>category</code>為空的<code>article</code>的<code>article</code>字段都更新為預設的分類。示例代碼如下:

注意這個方法走的是更新的邏輯。是以更新完成後儲存到資料庫中不會執行<code>save</code>方法,是以不會更新<code>auto_now</code>設定的字段。

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

切片操作:有時候我們查找資料,有可能隻需要其中的一部分。那麼這時候可以使用切片操作來幫我們完成。<code>queryset</code>使用切片操作就跟清單使用切片操作是一樣的。示例代碼如下:

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

生成一個<code>queryset</code>對象并不會馬上轉換為<code>sql</code>語句去執行。

比如我們擷取<code>book</code>表下所有的圖書:

我們可以看到在列印<code>connection.quries</code>的時候列印的是一個空的清單。說明上面的<code>queryset</code>并沒有真正的執行。

在以下情況下<code>queryset</code>會被轉換為<code>sql</code>語句執行:

疊代:在周遊<code>queryset</code>對象的時候,會首先先執行這個<code>sql</code>語句,然後再把這個結果傳回進行疊代。比如以下代碼就會轉換為<code>sql</code>語句:

使用步長做切片操作:<code>queryset</code>可以類似于清單一樣做切片操作。做切片操作本身不會執行<code>sql</code>語句,但是如果如果在做切片操作的時候提供了步長,那麼就會立馬執行<code>sql</code>語句。需要注意的是,做切片後不能再執行<code>filter</code>方法,否則會報錯。

調用<code>len</code>函數:調用<code>len</code>函數用來擷取<code>queryset</code>中總共有多少條資料也會執行<code>sql</code>語句。

調用<code>list</code>函數:調用<code>list</code>函數用來将一個<code>queryset</code>對象轉換為<code>list</code>對象也會立馬執行<code>sql</code>語句。

判斷:如果對某個<code>queryset</code>進行判斷,也會立馬執行<code>sql</code>語句。