天天看點

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>​類中拷貝過來的。示例圖如下:

Pycharm開發Django項目QuerySet API詳細教程

是以我們如果想要學習​<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>Article.objects.exclude(title__contains='hello')</code>

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

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

<code>articles = Article.objects.annotate(author_name=F("author__name"))</code>

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

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

<code># 根據建立的時間正序排序 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')</code>

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

<code>articles = Article.objects.order_by("create_time").order_by("author__name")</code>

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

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

<code>articles = Article.objects.values("title",'content') for article in articles: print(article)</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 = Article.objects.values_list("id","title") print(articles)</code>

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

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

<code>articles1 = Article.objects.values_list("title") &gt;&gt; &lt;QuerySet [( "abc",),("xxx",),...]&gt; articles2 = Article.objects.values_list( "title",flat=True) &gt;&gt; &lt;QuerySet [ "abc",'xxx',...]&gt;</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>article = Article.objects.get(pk=1) &gt;&gt; article.author # 重新執行一次查詢語句 article = Article.objects.select_related("author").get(pk=2) &gt;&gt; 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>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)</code>

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

<code>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)</code>

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

<code>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)</code>

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

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

<code>articles = list(Article.objects.defer("title")) for sql in connection.queries: print( '='*30) print(sql)</code>

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

<code>articles = list(Article.objects.defer("title")) for article in articles: # 因為在上面提取的時候過濾了title # 這個地方重新擷取title,将重新向資料庫中進行一次查找操作 print(article.title) for sql in connection.queries: print( '='*30) print(sql)</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>article = Article(title='abc') article.save() # 下面這行代碼相當于以上兩行代碼 article = Article.objects.create(title='abc')</code>

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

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

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

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

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

<code>Tag.objects.bulk_create([ Tag(name= '111'), Tag(name= '222'), ])</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>if Article.objects.filter(title__contains='hello').exists(): print( True) 比使用count更高效: if Article.objects.filter(title__contains='hello').count() &gt; 0: print( True) 也比直接判斷QuerySet更高效: if Article.objects.filter(title__contains='hello'): print( True)</code>

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

<code>books = Book.objects.filter(bookorder__price__gte=80).distinct()</code>

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

<code>orders = BookOrder.objects.order_by("create_time").values("book_id").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>Article.objects.filter(category__isnull=True).update(category_id=3)</code>

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

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

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

<code>books = Book.objects.all()[1:3] for book in books: print(book)</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>for book in Book.objects.all(): print(book)</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>語句。