Django常用的QuerySet操作

Ch3n發表於2020-09-24

1. 支援鏈式呼叫的介面

  • all

使用頻率比較高,相當於SELECT * FROM table 語句,用於查詢所有資料。

  • filter

使用頻率比較高,根據條件過濾資料,常用的條件基本上欄位等於、不等於、大於、小於。當然,還有其他的,比如能修改成產生LIKE查詢的:Model.objects.filter(content__contains="條件")。

  • exclude

與filter是相反的邏輯

  • reverse

將QuerySet中的結果倒敘排列

  • distinct

用來進行去重查詢,產生SELECT DISTINCT這樣的SQL查詢

  • none

返回空的QuerySet

2. 不支援鏈式呼叫的介面

  • get

比如Post.objects.get(id=1)用於查詢id為1的文章:如果存在,則直接返回對應的Post例項;如果不存在,則丟擲DoesNotExist異常。所以一般情況下,要使用異常捕獲處理:

1 try:
2     post = Post.objects.get(id=1)
3 except Post.DoesNotExist:
4 #做異常情況處理
  • create

用來直接建立一個Model物件,比如post = Post.objects.create(title="一起學習")。

  • get_or_create

根據條件查詢,如果沒查詢到,就呼叫create建立。

  • update_or_create

與get_or_create相同,只是用來做更新操作。

  • count

用於返回QuerySet有多少條記錄,相當於SELECT COUNT(*) FROM table 。

  • latest

用於返回最新的一條記錄,但要在Model的Meta中定義:get_latest_by= <用來排序的欄位>。

  • earliest

同上,返回最早的一條記錄。

  • first

從當前QuerySet記錄中獲取第一條。

  • last

同上,獲取最後一條。

  • exists

返回True或者False,在資料庫層面執行SELECT (1) AS "a" FROM table LIMIT 1的查詢,如果只是需要判斷QuerySet是否有資料,用這個介面是最合適的方式。

不要用count或者len(queryset)這樣的操作來判斷是否存在。相反,如果可以預期接下來會用到QuerySet中的資料,可以考慮使用len(queryset)的方式來做判斷,這樣可以減少一次DB查詢請求。

  • bulk_create

同create,用來批量建立記錄。

  • in_ bulk

批量查詢,接收兩個引數id_ list和filed_ name。可以通過Post.objects. in_ bulk([1, 2, 3])查詢出id為1、2、3的資料,返回結果是字典型別,字典型別的key為查詢條件。返回結果示例: {1: <Post 例項1>, 2: <Post例項2>,3:<Post例項3>}。

  • update

用來根據條件批量更新記錄,比如: Post.objects.filter(owner__name='123').update(title='測試更新')。

  • delete

同update,這個介面是用來根據條件批量刪除記錄。需要注意的是,和delete都會觸發Djiango的signal

  • values

當我們明確知道只需要返回某個欄位的值,不需要Model例項時,用它,用法如下:

1 title_list = Post.objects.filter(category_id=1).values('title')

返回的結果包含dict的QuerySet,類似這樣: <QuerySet [{'title' :xxx},]>

  • values_list

同values,但是直接返回的是包含tuple的QuerySet:

1 titles_list = Post.objects.filter(category=1).values_list('title')

返回結果類似: <QuerySet[("標題",)]>

如果只是一個欄位的話,可以通過增加flat=True引數,便於我們後續 處理:

1 title_list = Post.objects.filter(category=1).values_list('title',flat=True)
2 for title in title__list:
3     print(title)

2.1進階介面

除了上面介紹的常用介面外,還有其他用來提高效能的介面,在下面介紹。 在優化Django專案時,尤其要考慮這幾種介面的用法。

  • defer

把不需要展示的欄位做延遲載入。比如說,需要獲取到文章中除正文外的其他欄位,就可以通過posts = Post.objects.all() .defer('content'),這樣拿到的記錄中就不會包含content部分。但是當我們需要用到這個欄位時,在使用時會去載入。程式碼:

1 posts = Post.objects.all().defer('content')
2 for post in posts:  #此時會執行資料庫查詢
3     print (post.content)  #此時會執行資料查詢,獲取到content

當不想載入某個過大的欄位時(如text型別的欄位),會使用defer,但是上面的演示代產生N+1的查詢問題,在實際使用時千萬要注意!

注意:上面的程式碼是個不太典型的 N+1查詢的問題, 一般情況下 由外來鍵查詢產生的N+1問題比較多,即一條查詢請求返回N條資料,當我們運算元據時,又會產生額外的請求。這就是N+1問題,所有的ORM框架都存在這樣的問題。

  • only

同defer介面剛好相反, 如果只想獲取到所有的title記錄,就可以使用only,只獲取title的內容,其他值在獲取時會產生額外的查詢。

這就是用來解決外來鍵產生的N+1問題的方案。我們先來看看什麼情況下會產生這個問題:

posts = Post.objects.all ()
for post in posts:  #產生資料庫查詢
    print (post.owner)  #產生額外的資料庫查詢

程式碼同上面類似,只是這裡用的是owenr(是關聯表)。它的解決方法就是用select_ related介面:

post = Post.objects.all() .select_related('category')
for post in posts: # 產生資料庫查詢,category資料也會一次性查詢出來
    print (post.category)

當然,這個介面只能用來解決一對多的關聯關係。對於多對多的關係,還得使用下面的介面。

針對多對多關係的資料,可以通過這個介面來避免N+1查詢。比如,post和tag的關係可以通過這種方式來避免:

posts = Post.objects.all().prefetch_related('tag')
for post in posts:#產生兩條查詢語句,分別查詢post和tag
    print(post.tag.al1())

3.常用的欄位查詢

  • contains

包含,用來進行相似查詢。

  • icontains

同contains,只是忽略大小寫。

  • exact

精確匹配。

  • iexact

同exact,忽略大小寫。

  • in

指定某個集合,比如Post.objects.filter(id__in=[1, 2, 3])相當於SELECT FROM table WHERE IN (1, 2, 3);。

  • gt

大於某個值。比如:Post.objects.filter(id__gt=1)

注意:是__gt

  • gte

大於等於某個值。

  • lt

小於某個值。

  • lte

小於等於某個值。

  • startswith

以某個字串開頭,與contains類似,只是會產生LIKE '<關鍵詞>%'這樣的SQL。

  • istartswith

同startswith, 忽略大小寫。

  • endswith

以某個字串結尾。

  • iendswith

同endswith,忽略大小寫。

  • range

範圍查詢,多用於時間範圍,如Post.objects.filter(created_time__range= ('2018-05-01','2018-06-01'))會產生這樣的查詢: SELECT .. . WHERE created_ time BETWEEN '2018-05-01' AND '2018-06-01' ;。

關於日期類的查詢還有很多,比如date、year和month等,具體等需要時查文件即可。

這裡你需要理解的是,Django之所以提供這麼多的欄位查詢,其原因是通過ORM來運算元據庫無法做到像SQL的條件查詢那麼靈活。

因此,這些查詢條件都是用來匹配對應SQL語句的,這意味著,如果你知道某個查詢在SQL中如何實現,可以對應來看Django提供的介面。

3.1 進階查詢

除了上面基礎的查詢語句外,Django還提供了其他封裝,來滿足更復雜的查詢,比如 SELECT ... WHERE id = 1 OR id = 2 這樣的查詢,用上面的基礎查詢就無法滿足。

  • F

F表示式常用來執行資料庫層面的計算,從而避免出現競爭狀態。比如需要處理每篇文章的訪問量,假設存在post.pv這樣的欄位,當有使用者訪問時,我們對其加1:

post = Post.objects.get(id=1)
post.pv = post.pv+1
post.save()

這在多執行緒的情況下會出現問題,其執行邏輯是先獲取到當前的pv值,然後將其加1後賦值給post .pv.最後儲存。

如果多個執行緒同時執行了post = Post.objects.get(id=1),那麼每個執行緒裡的post .pv值都是一樣的, 執行完加1和儲存之後,相當於只執行了一個加1,而不是多個。

這時通過F表示式就可以方便地解決這個問題:

from ajango.ab. models import F
post = Post.objects.get(id=1)
post.pv = F('pv') + 1
post.save():

這種方式最終會產生類似這樣的SQL語句: UPDATE table SET pv = pv +1 WHERE ID = 1。 它在資料庫層面執行原子性操作。

  • Q

Q表示式就是用來解決前面提到的那個OR查詢的,可以這麼用:

from django.db.mode1s import Q
Post.objects.filter(Q(id=1) | Q(id=2))

或者進行AND查詢:

Post.objects.filter(Q(id=1) & Q(id=2))
  • Count

用來做聚合查詢,比如想要得到某個分類下有多少篇文章,簡單的做法就是:

category = Category.objects.get(id=1)
posts_count = category.post_set.count()

但是如果想要把這個結果放到category上呢?通過category.post_count可以訪問到:

from django.db.models import Count
categories = Category.objects.annotate(posts_count=Count('post'))
print(categories[0].posts_count)

這相當於給category動態增加了屬性post_count,而這個屬性的值來源於Count('post'),最後可以用int取整。

  • Sum

同Count類似,只是它是用來做合計的。比如想要統計所有資料欄位的總和,可以這麼做:

from django.db.models import Sum
Post.objects.all().aggregate(a=Sum('欄位'))
#輸出類似結果:{'a':487}為字典

python中對字典中鍵值對的獲取:

for i in book:
    print(i)#鍵的獲取
    print(book[i])#值的獲取

上面演示了QuerySet的annotate和aggregate的用法,其中前者用來給QuerySet結果増加屬性,後者只用來直接計算結果,這些聚合表示式都可以與它們結合使用。

除了Count和Sum外,還有Avg、Min和Max等表示式,均用來滿足我們對SQL査洵的需求。

相關文章