本來想自己動手寫篇文章詳細介紹下Django的QuerySet查詢方法的,看了網上有篇博客已經總結得很不錯了,所以轉下(原文地址: http://yshblog.com/blog/157。作者楊仕航)。
一開始使用Django的QuerySet獲取數據不太習慣。簡單的條件可以通過get或filter獲取,但複雜的我總是用SQL語句查詢得到。隨著接觸QuerySet越來越多,發現它相當鋒利、健全。
QuerySet是Django的查詢集,可以通過QuerySet條件查詢得到對應模型的對象集合。
相對直接使用SQL而言,QuerySet可以防止大部分SQL注入,而且提高代碼可讀性。畢竟Django的模型和具體表名不太一樣,需要查閱才得知表名,而且寫一大串SQL代碼可能直接把人看暈。假如碰到模型變動(改名、增刪欄位等),SQL可能又要重新調整。所以,能使用QuerySet的情況下,最好使用QuerySet。
還有重要一點,QuerySet是懶惰的。創建一個QuerySet對象,它不會直接返回數據集。等到使用它的時候,才解析該對象得到數據集。而且解析過一次會被緩存起來,下次使用時直接返回緩存中的數據,緩存的使用提高多次查詢的效率。
先假設有如下模型,以供下面的QuerySet查詢使用:
#coding:utf-8
from django.db import models
from django.contrib.auth.models import User
#博客模型
class Blog(models.Model):
#標題
caption = models.CharField(max_length=50)
#作者(外鍵關聯User模型)
author = models.ForeignKey(User)
#內容
content = models.TextField()
#分類(和Tag模型關聯,多對多)
tags = models.ManyToManyField(Tag,blank=True)
#發表時間
publish_time = models.DateTimeField(auto_now_add=True)
#博客分類標籤
class Tag(models.Model):
#分類名
tag_name = models.CharField(max_length=20,blank=True)
1、QuerySet和SQL語句首先講如何將SQL語句解析成RawQuerySet對象。先讓不會使用QuerySet的同學順利使用SQL語句查詢。
不熟悉SQL的可以跳過該部分,如下代碼:
sql = 'select * from blog_blog' #需要查詢資料庫具體Blog對應表名
qs = Blog.objects.raw(sql) #將sql語句轉成RawQuerySet對象
該SQL是獲取全部記錄,相當於QuerySet如下查詢:
qs = Blog.objects.all()
以上的方法可將SQL語句轉成Blog模型的查詢集。
不過這個RawQuerySet和QuerySet稍微有些不同,屬性和方法沒有QuerySet多。例如我想知道這個查詢有多少條記錄。QuerySet可以直接使用count方法或者len方法獲取,而RawQuerySet沒有。這個也有方法處理,如下代碼:
#給rawqueryset對象加上len()方法
def add_len_to_raw_query(query):
from django.db.models.query import RawQuerySet
def __len__(self):
from django.db import connection
sql = 'select count(*) from (%s) as newsql' % query.raw_query
with connection.cursor() as cursor:
cursor.execute(sql)
row = cursor.fetchone()
return row[0]
setattr(RawQuerySet, '__len__', __len__)
sql = 'select * from blog_blog'
qs = Blog.objects.raw(sql)
add_len_to_raw_query(qs) #給qs加上len()方法
print(len(qs))
我們可通過該方法設置並獲取相關對象。另外,使用SQL查詢,我們還可以通過SQL語句給RawQuerySet對象添加額外的屬性。raw方法會解析SQL語句中的欄位,將欄位轉成屬性的方式方便調用。例如:
sql = '''
Select blog_blog.id, blog_blog.caption, count(blog_blog_tags.id) as tag_count
From blog_blog
Left join blog_blog_tags on blog_blog.id = blog_blog_tags.blog_id
Group by blog_blog.id, blog_blog.caption
'''
qs = Blog.objects.raw(sql)
print(qs[0].tag_count)
上面SQL語句是得到每篇Blog對應分類標籤的個數,是不是覺得SQL很複雜了。
不過,我們可以在查詢結果直接使用tag_count屬性。
另外,為了方便測試,我們可以輸出QuerySet對象的SQL語句:
qs = Blog.objects.filter(id=1)
print(qs.query)
2、filter和get方法上面使用了filter。filter是篩選的意思,通過filter篩選得到符合條件的數據集。
例如我分別獲取如下情況的數據集。
#獲取blog id為1的數據集
qs1 = Blog.objects.filter(id=1)
#獲取作者user所發表的博客
user = request.user #先使用當前登錄的用戶判斷
qs2 = Blog.objects.filter(author=user)
#多個條件用逗號隔開
qs3 = Blog.objects.filter(id=1, author=user)
Blog id為1相當於SQL:
select * from blog_blog where id = 1
這裡你會發現只有一條記錄,而filter返回是一個只有一條記錄的數據集。獲取該數據還需要進一步獲取:
q = qs1[0]
這時可以使用get方法直接獲取該條記錄:
q = Blog.objects.get(id=1)
不過,若符合條件的沒有記錄或多條記錄會拋出異常。我們可以利用這個特點做一些操作:
try:
q = Blog.objects.get(caption='test') #找標題為test的博客
except Blog.DoesNotExist:
q = Blog(caption='test') #創建標題為test的博客
#...
q.save()
當然,django有個get_or_create方法可以簡化該過程:
#獲取標題為test的博客,不存在則創建
q = Blog.objects.get_or_create(caption='test')
3、不等於條件filter篩選的條件都是符合的條件,也就是我們SQL語句中的等於。有時候,我們希望排除一些條件,也就是不等於或者not條件:
select * from blog_blog where id <> 1
select * from blog_blog where not(id = 1)
這種類型的條件,需要使用exclude方法。例如:
qs = Blog.objects.exclude(id=1)
如果我要實現複雜一些的查詢,獲取當前登錄用戶發表的博客,並排除id為1的博客。查詢如下:
user = request.user
qs = Blog.objects.filter(author=user).exclude(id=1)
這裡延伸QuerySet另一特性,可以在QuerySet上繼續查詢,即QuerySet支持鏈式查詢。
繼續延伸,既然QuerySet可以實現等於和不等於。那麼大於和小於應當如何實現?
這種filter和exclude都無能為力。需要藉助欄位條件修飾,例如:
#查詢id大於10的記錄
qs1 = Blog.objects.filter(id__gt=10)
#查詢id大於等於10的記錄
qs2 = Blog.objects.filter(id_gte=10)
#排除id小於10的記錄
qs3 = Blog.objects.exclude(id__lt=10)
#排除id小於等於10的記錄
qs4 = Blog.objects.exclude(id__lte=10)
從這裡我們可以總結一下,objects查詢基本就3種方法:get、filter、exclude。
條件再用欄位修飾拓展,欄位修飾是兩個下劃線加修飾碼。
有個方法可以幫助記憶大於、小於的修飾碼。
gt 是 greater than 的縮寫,或者你熟悉html可以記憶為">",">"是大於號的轉義。
gte 後面的 e 是等於equal單詞的第1個字母。
lt 和 lte 是同樣方法,less than 的縮寫;"<"小於號的轉義。
這裡還有兩個拓展。
1)範圍查詢
有時候,不單單是大於或小於。可能是一個範圍,例如 1<=id<=9。你可以用兩個條件組合。
qs = Blog.objects.filter(id__gte=1).filter(id__lte=9)
或者
qs = Blog.objects.filter(id__gte=1, id__lte=9)
可以使用__range範圍修飾:
qs = Blog.objects.filter(id__range=[1, 9])
2)in條件查詢
有時候不是簡單大於或小於,需要從一個列表的值獲取。in條件查詢用__in修飾,例如:
#獲取id為 1、3、6、7、9的記錄
qs = Blog.objects.filter(id__in=[1, 3, 6, 7, 9])
5、字符串模糊匹配數值有大於小於等判斷,那麼字符串有模糊匹配判斷的需求。例如開頭是什麼,結尾是什麼,包含什麼字符等等。這些同樣需要藉助欄位條件修飾。例如:
#標題包含django,若忽略大小寫使用__icontains
qs1 = Blog.objects.filter(caption__contains='django')
#標題是django開頭的,若忽略大小寫使用__istartswith
qs2 = Blog.objects.filter(caption__startswith='django')
#標題是django結尾的,若忽略大小寫使用__iendswith
qs2 = Blog.objects.filter(caption__endswith='django')
那"不包含"、"開頭不是"、"結尾不是"用什麼?當然用exclude啦。
6、日期時間處理上面Blog模型有個發表日期publish_time欄位。該欄位是datetime類型。
日期時間類型要不數值、字符串複雜得多。同樣使用欄位條件修飾實現相應查詢:
import datetime
#獲取今天的datetime
now = datetime.datetime.now()
#找今年發布的博客
qs1 = Blog.objects.filter(publish_time__year=now.year)
#找本月發布的博客
qs2 = Blog.objects.filter(publish_time__year=now.year,publish_time__month=now.month)
#找到每月1號發布的博客
qs3 = Blog.objects.filter(publish_time__day=1)
我們還可以使用使用__range獲取某範圍內的數據:
import datetime
#獲取前7天發表的博客
now = datetime.datetime.now()
end_date = datetime.datetime(now.year, now.month, now.day, 0, 0)
start_date = end_date - datetime.timedelta(7)
#找今年發布的博客
qs = Blog.objects.filter(publish_time__range=[start_date, end_date])
7、or條件上面多個條件出現大多是and邏輯。那我可能使用or邏輯。例如我想模糊匹配標題為"django" 或 "python"。這時只能採用or邏輯處理。如下代碼:
qs = Blog.objects.filter(caption__icontains='django')|Blog.objects.filter(caption__icontains='python')
若你嫌棄代碼過程,可以使用Q函數:
from django.models import Q
qs = Blog.objects.filter(Q(caption__icontains='django')|Q(caption__icontains='python'))
8、limit獲取前幾條有時頁面顯示只需要顯示前10條或前20條記錄。這個在SQL語句中可以用limit限制返回的記錄數。同樣,在QuerySet查詢中也可以實現該功能。
qs = Blog.objects.all()[:10]
上面代碼是獲取全部博客的前10條記錄,和切片一樣,但索引不能為負數。
另外,這個切片器也不會讓QuerySet直接執行,直到使用該查詢。
9、外鍵查找我上面寫的示例模型,有兩個外鍵。
假如我需要獲取某一篇博客有什麼分類標籤,或分類標籤下有哪些博客,應當如何獲取?
獲取某一篇博客有什麼分類標籤,由於Blog有個tags外鍵關聯Tag模型。可直接獲取:
blog = Blog.objects.all()[0]
tags = blog.tags.all() #獲取第1篇博客全部標籤
而分類標籤沒有直接寫一個外鍵和Blog模型關聯,但可以用set通過tag獲取全部blog:
tag = Tag.objects.all()[0]
blogs = tag.blog_set.all() #獲取第1個分類標籤全部博客
10、排序SQL語句可以使用Order by排序。QuerySet查詢也有order_by與之對應:
#按照日期正序排序
qs1 = Blog.objects.all().order_by('publish_time')
#安裝日期倒序排序(all可以省略)
qs2 = Blog.objects.order_by('-publish_time')
針對哪個排序,就寫該欄位名稱即可。默認正序排序,若加個負號表示倒序排序。
正序一般是數值由小到大,日期由遠到近。
排序還有一個神奇的用法,在SQL我還沒見過:隨機排序。通過該功能,我們可以隨機獲取記錄:
#隨機獲取前10條記錄
qs = Blog.objects.order_by('?')[:10]
11、其他有些知識過於零碎,沒有和上面放在一起講。隨便也羅列出來:
1)獲取值為null的數據
qs = Blog.objects.filter(content__isnull=True)
2)去重
qs = Tag.objects.filter(id__in=[1, 3]).blog_set().distinct()
因為這裡通過Tag獲取的博客可能重複,可以用distinct去重。