在這里我根據是否支持鏈式調用分類進行介紹
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的內容,其他值在獲取時會產生額外的查詢。
-
select_related
這就是用來解決外鍵產生的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)
當然,這個接口只能用來解決一對多的關聯關系。對於多對多的關系,還得使用下面的接口。
-
prefetch_related
針對多對多關系的數據,可以通過這個接口來避免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査洵的需求。