Django
select_related 和 prefetch_related 函式
對 QuerySet 查詢的優化
在資料庫有外鍵的時候,使用 select_related() 和 prefetch_related() 能夠很好的減小資料庫請求的次數,從而提升性能,本文經過一個簡單的例子詳解這兩個函式的做用,雖然QuerySet的檔案中已經詳細說明了,但本文試圖從QuerySet觸發的SQL陳述句來分析工做方式,從而進一步了解Django具體的運做方式,
1. 實體的背景說明
假定一個我的資訊系統,須要記錄系統中各我的的故鄉、居住地、以及到過的城市,資料庫設計以下:python
Models.py 內容以下:mysql
from django.db import models
class Province(models.Model):
name = models.CharField(max_length=10)
def __unicode__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=5)
province = models.ForeignKey(Province)
def __unicode__(self):
return self.name
class Person(models.Model):
firstname = models.CharField(max_length=10)
lastname = models.CharField(max_length=10)
visitation = models.ManyToManyField(City, related_name = "visitor")
hometown = models.ForeignKey(City, related_name = "birth")
living = models.ForeignKey(City, related_name = "citizen")
def __unicode__(self):
return self.firstname + self.lastname
注1:建立的app名為“QSOptimize”sql
注2:為了簡化起見,qsoptimize_province 表中只有2條資料:湖北省和廣東省,qsoptimize_city表中只有三條資料:武漢市、十堰市和廣州市資料庫
2. select_related()
對于一對一欄位(OneToOneField)和外鍵欄位(ForeignKey),可使用select_related 來對QuerySet進行優化django
做用和方法
在對QuerySet使用select_related()函式后,Django會獲取相應外鍵對應的物件,從而在以后須要的時候沒必要再查詢資料庫了,以上例說明,若是咱們須要列印資料庫中的全部市及其所屬省份,最直接的作法是:快取
citys = City.objects.all()
for c in citys:
print c.province
這樣會致使線性的SQL查詢,若是物件數量n太多,每一個物件中有k個外鍵欄位的話,就會致使n*k+1次SQL查詢,在本例中,由于有3個city物件就致使了4次SQL查詢:app
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 2 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;
注:這里的SQL陳述句是直接從Django的logger:‘django.db.backends’輸出出來的資料庫設計
若是咱們使用select_related()函式:函式
citys = City.objects.select_related().all()
for c in citys:
print c.province
就只有一次SQL查詢,顯然大大減小了SQL查詢的次數:性能
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`,
`QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM`QSOptimize_city`
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;
這里咱們能夠看到,Django使用了INNER JOIN來得到省份的資訊,順便一提這條SQL查詢獲得的結果以下:
+----+-----------+-------------+----+-----------+
| id | name | province_id | id | name |
+----+-----------+-------------+----+-----------+
| 1 | 武漢市 | 1 | 1 | 湖北省 |
| 2 | 廣州市 | 2 | 2 | 廣東省 |
| 3 | 十堰市 | 1 | 1 | 湖北省 |
+----+-----------+-------------+----+-----------+
3 rows in set (0.00 sec)
使用方法
函式支持以下三種用法:
*fields 引數
select_related() 接受可變長引數,每一個引數是須要獲取的外鍵(父表的內容)的欄位名,以及外鍵的外鍵的欄位名、外鍵的外鍵的外鍵…,若要選擇外鍵的外鍵須要使用兩個下劃線“__”來鏈接,
例如咱們要得到張三的現居省份,能夠用以下方式:
zhangs = Person.objects.select_related('living__province').get(firstname=u"張",lastname=u"三")
zhangs.living.province
觸發的SQL查詢以下:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`,
`QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`,
`QSOptimize_province`.`name`
FROM `QSOptimize_person`
INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`living_id` = `QSOptimize_city`.`id`)
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
WHERE (`QSOptimize_person`.`lastname` = '三' AND `QSOptimize_person`.`firstname` = '張' );
能夠看到,Django使用了2次 INNER JOIN 來完成請求,得到了city表和province表的內容并添加到結果表的相應列,這樣在呼叫 zhangs.living的時候也沒必要再次進行SQL查詢,
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| 1 | 張 | 三 | 3 | 1 | 1 | 武漢市 | 1 | 1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
1 row in set (0.00 sec)
然而,未指定的外鍵則不會被添加到結果中,這時候若是須要獲取張三的故鄉就會進行SQL查詢了:
zhangs.hometown.province
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
WHERE `QSOptimize_city`.`id` = 3 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1
同時,若是不指定外鍵,就會進行兩次查詢,若是深度更深,查詢的次數更多,
值得一提的是,從Django 1.7開始,select_related()函式的做用方式改變了,在本例中,若是要同時得到張三的故鄉和現居地的省份,在1.7之前你只能這樣作:
zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"張",lastname=u"三")
zhangs.hometown.province
zhangs.living.province
可是1.7及以上版本,你能夠像和queryset的其余函式同樣進行鏈式操做:
zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"張",lastname=u"三")
zhangs.hometown.province
zhangs.living.province
若是你在1.7如下版本這樣作了,你只會得到最后一個操做的結果,在本例中就是只有現居地而沒有故鄉,在你列印故鄉省份的時候就會形成兩次SQL查詢,
depth 引數
select_related() 接受depth引數,depth引數能夠肯定select_related的深度,Django會遞回遍歷指定深度內的全部的OneToOneField和ForeignKey,以本例說明:
zhangs = Person.objects.select_related(depth = d)
d=1 至關于 select_related(‘hometown’,’living’)
d=2 至關于 select_related(‘hometown__province’,’living__province’)
無引數
select_related() 也能夠不加引數,這樣表示要求Django盡量深的select_related,例如:zhangs = Person.objects.select_related().get(firstname=u”張”,lastname=u”三”),但要注意兩點:
Django自己內置一個上限,對于特別復雜的表關系,Django可能在你不知道的某處跳出遞回,從而與你想的作法不同,具體限制是怎么工做的我表示不清楚,
Django并不知道你實際要用的欄位有哪些,因此會把全部的欄位都抓進來,從而會形成沒必要要的浪費而影響性能,
小結
select_related主要針一對一和多對一關系進行優化,
select_related使用SQL的JOIN陳述句進行優化,經過減小SQL查詢的次數來進行優化、提升性能,
能夠經過可變長引數指定須要select_related的欄位名,也能夠經過使用雙下劃線“__”鏈接欄位名來實作指定的遞回查詢,沒有指定的欄位不會快取,沒有指定的深度不會快取,若是要訪問的話Django會再次進行SQL查詢,
也能夠經過depth引數指定遞回的深度,Django會自動快取指定深度內全部的欄位,若是要訪問指定深度外的欄位,Django會再次進行SQL查詢,
也接受無引數的呼叫,Django會盡量深的遞回查詢全部的欄位,但注意有Django遞回的限制和性能的浪費,
Django >= 1.7,鏈式呼叫的select_related至關于使用可變長引數,Django < 1.7,鏈式呼叫會致使前邊的select_related失效,只保留最后一個,
3. prefetch_related()
對于多對多欄位(ManyToManyField)和一對多欄位,可使用prefetch_related()來進行優化,或許你會說,沒有一個叫OneToManyField的東西啊,實際上 ,ForeignKey就是一個多對一的欄位,而被ForeignKey關聯的欄位就是一對多欄位了,
做用和方法
prefetch_related()和select_related()的設計目的很類似,都是為了減小SQL查詢的數量,可是實作的方式不同,后者是經過JOIN陳述句,在SQL查詢內解決問題,可是對于多對多關系,使用SQL陳述句解決就顯得有些不太明智,由于JOIN獲得的表將會很長,會致使SQL陳述句運行時間的增長和記憶體占用的增長,如有n個物件,每一個物件的多對多欄位對應Mi條,就會生成Σ(n)Mi 行的結果表,prefetch_related()的解決方法是,分別查詢每一個表,而后用Python處理他們之間的關系,繼續以上邊的例子進行說明,若是咱們要得到張三全部去過的城市,使用prefetch_related()應該是這么作:
zhangs = Person.objects.prefetch_related('visitation').get(firstname=u"張",lastname=u"三")
for city in zhangs.visitation.all() :
print city
上述代碼觸發的SQL查詢以下:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE (`QSOptimize_person`.`lastname` = '三' AND `QSOptimize_person`.`firstname` = '張');
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);
第一條SQL查詢僅僅是獲取張三的Person物件,第二條比較關鍵,它選取關系表QSOptimize_person_visitation中person_id為張三的行,而后和city表行內(INNER JOIN 也叫等值鏈接)獲得結果表,
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
| 1 | 張 | 三 | 3 | 1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+-----------+-------------+
| 1 | 1 | 武漢市 | 1 |
| 1 | 2 | 廣州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
+-----------------------+----+-----------+-------------+
3 rows in set (0.00 sec)
顯然張三武漢、廣州、十堰都去過,
又或者,咱們要得到湖北的全部城市名,能夠這樣:
hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"湖北省")
for city in hb.city_set.all():
... city.name
...
觸發的SQL查詢:
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city` WHERE `QSOptimize_city`.`province_id` IN (1);
獲得的表:
+----+-----------+
| id | name |
+----+-----------+
| 1 | 湖北省 |
+----+-----------+
1 row in set (0.00 sec)
+----+-----------+-------------+
| id | name | province_id |
+----+-----------+-------------+
| 1 | 武漢市 | 1 |
| 3 | 十堰市 | 1 |
+----+-----------+-------------+
2 rows in set (0.00 sec)
咱們能夠看見,prefetch使用的是 IN 陳述句實作的,這樣,在QuerySet中的物件數量過多的時候,根據資料庫特性的不一樣有可能形成性能問題,
使用方法
*lookups 引數
prefetch_related()在Django < 1.7 只有這一種用法,和select_related()同樣,prefetch_related()也支持深度查詢,例如要得到全部姓張的人去過的省:
zhangs = Person.objects.prefetch_related('visitation__province').filter(firstname__iexact=u'張')
for i in zhangs:
for city in i.visitation.all():
print city.province
觸發的SQL:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE `QSOptimize_person`.`firstname` LIKE '張' ;
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 4);
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` IN (1, 2);
得到的結果:
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
| 1 | 張 | 三 | 3 | 1 |
| 4 | 張 | 六 | 2 | 2 |
+----+-----------+----------+-------------+-----------+
2 rows in set (0.00 sec)
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+-----------+-------------+
| 1 | 1 | 武漢市 | 1 |
| 1 | 2 | 廣州市 | 2 |
| 4 | 2 | 廣州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
+-----------------------+----+-----------+-------------+
4 rows in set (0.00 sec)
+----+-----------+
| id | name |
+----+-----------+
| 1 | 湖北省 |
| 2 | 廣東省 |
+----+-----------+
2 rows in set (0.00 sec)
值得一提的是,鏈式prefetch_related會將這些查詢添加起來,就像1.7中的select_related那樣,
要注意的是,在使用QuerySet的時候,一旦在鏈式操做中改變了資料庫請求,以前用prefetch_related快取的資料將會被忽略掉,這會致使Django從新請求資料庫來得到相應的資料,從而形成性能問題,這里提到的改變資料庫請求指各類filter()、exclude()等等最侄訓改變SQL代碼的操做,而all()并不會改變最終的資料庫請求,所以是不會致使從新請求資料庫的,
舉個例子,要獲取全部人訪問過的城市中帶有“市”字的城市,這樣作會致使大量的SQL查詢:
plist = Person.objects.prefetch_related('visitation')
[p.visitation.filter(name__icontains=u"市") for p in plist]
由于資料庫中有4人,致使了2+4次SQL查詢:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`;
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 2, 3, 4);
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE(`QSOptimize_person_visitation`.`person_id` = 1 AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 2 AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 3 AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 4 AND `QSOptimize_city`.`name` LIKE '%市%' );
詳細分析一下這些請求事件,
眾所周知,QuerySet是lazy的,要用的時候才會去訪問資料庫,運行到第二行Python代碼時,for回圈將plist看作iterator,這會觸發資料庫查詢,最初的兩次SQL查詢就是prefetch_related致使的,
雖然已經查詢結果中包含全部所需的city的資訊,但由于在回圈體中對Person.visitation進行了filter操做,這顯然改變了資料庫請求,所以這些操做會忽略掉以前快取到的資料,從新進行SQL查詢,
可是若是有這樣的需求了應該怎么辦呢?在Django >= 1.7,能夠經過下一節的Prefetch物件來實作,若是你的環境是Django < 1.7,能夠在Python中完成這部分操做,
plist = Person.objects.prefetch_related('visitation')
[[city for city in p.visitation.all() if u"市" in city.name] for p in plist]
Prefetch 物件
在Django >= 1.7,能夠用Prefetch物件來控制prefetch_related函式的行為,
注:因為我沒有安裝1.7版本的Django環境,本節內容是參考Django檔案寫的,沒有進行實際的測驗,
Prefetch物件的特征:
一個Prefetch物件只能指定一項prefetch操做,
Prefetch物件對欄位指定的方式和prefetch_related中的引數相同,都是經過雙下劃線鏈接的欄位名完成的,
能夠經過 queryset 引數手動指定prefetch使用的QuerySet,
能夠經過 to_attr 引數指定prefetch到的屬性名,
Prefetch物件和字串形式指定的lookups引數能夠混用,
繼續上面的例子,獲取全部人訪問過的城市中帶有“武”字和“州”的城市:
wus = City.objects.filter(name__icontains = u"武")
zhous = City.objects.filter(name__icontains = u"州")
plist = Person.objects.prefetch_related(
Prefetch('visitation', queryset = wus, to_attr = "wu_city"),
Prefetch('visitation', queryset = zhous, to_attr = "zhou_city"),)
[p.wu_city for p in plist]
[p.zhou_city for p in plist]
注:這段代碼沒有在實際環境中測驗過,如有不正確的地方請指正,
順帶一提,Prefetch物件和字串引數能夠混用,
None
能夠經過傳入一個None來清空以前的prefetch_related,就像這樣:
prefetch_cleared_qset = qset.prefetch_related(None)
小結
prefetch_related主要針一對多和多對多關系進行優化,
prefetch_related經過分別獲取各個表的內容,而后用Python處理他們之間的關系來進行優化,
能夠經過可變長引數指定須要select_related的欄位名,指定方式和特征與select_related是相同的,
在Django >= 1.7能夠經過Prefetch物件來實作復雜查詢,但低版本的Django好像只能本身實作,
做為prefetch_related的引數,Prefetch物件和字串能夠混用,
prefetch_related的鏈式呼叫會將對應的prefetch添加進去,而非替換,彷佛沒有基于不一樣版本上區別,
能夠經過傳入None來清空以前的prefetch_related,
- 一些實體
選擇哪一個函式
若是咱們想要得到全部家鄉是湖北的人,最無腦的作法是先得到湖北省,再得到湖北的全部城市,最后得到故鄉是這個城市的人,就像這樣:
hb = Province.objects.get(name__iexact=u"湖北省")
people = []
for city in hb.city_set.all():
people.extend(city.birth.all())
顯然這不是一個明智的選擇,由于這樣作會致使1+(湖北省城市數)次SQL查詢,反正是個反例,致使的查詢和得到掉結果就不列出來了,
prefetch_related() 或許是一個好的解決方法,讓咱們來看看,
hb = Province.objects.prefetch_related("city_set__birth").objects.get(name__iexact=u"湖北省")
people = []
for city in hb.city_set.all():
... people.extend(city.birth.all())
...
由于是一個深度為2的prefetch,因此會致使3次SQL查詢:
SELECTQSOptimize_province
.id
,QSOptimize_province
.name
FROMQSOptimize_province
WHEREQSOptimize_province
.name
LIKE '湖北省' ;
SELECT QSOptimize_city
.id
, QSOptimize_city
.name
, QSOptimize_city
.province_id
FROM QSOptimize_city
WHERE QSOptimize_city
.province_id
IN (1);
SELECT QSOptimize_person
.id
, QSOptimize_person
.firstname
, QSOptimize_person
.lastname
,
QSOptimize_person
.hometown_id
, QSOptimize_person
.living_id
FROM QSOptimize_person
WHERE QSOptimize_person
.hometown_id
IN (1, 3);
嗯…看上去不錯,可是3次查詢么?倒過來查詢可能會更簡單?
people = list(Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北省"))
SELECTQSOptimize_person
.id
,QSOptimize_person
.firstname
,QSOptimize_person
.lastname
,
QSOptimize_person
.hometown_id
,QSOptimize_person
.living_id
,QSOptimize_city
.id
,
QSOptimize_city
.name
,QSOptimize_city
.province_id
,QSOptimize_province
.id
,QSOptimize_province
.name
FROMQSOptimize_person
INNER JOINQSOptimize_city
ON (QSOptimize_person
.hometown_id
=QSOptimize_city
.id
)
INNER JOINQSOptimize_province
ON (QSOptimize_city
.province_id
=QSOptimize_province
.id
)
WHEREQSOptimize_province
.name
LIKE '湖北省';
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| 1 | 張 | 三 | 3 | 1 | 3 | 十堰市 | 1 | 1 | 湖北省 |
| 2 | 李 | 四 | 1 | 3 | 1 | 武漢市 | 1 | 1 | 湖北省 |
| 3 | 王 | 麻子 | 3 | 2 | 3 | 十堰市 | 1 | 1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
3 rows in set (0.00 sec)
徹底沒問題,不只SQL查詢的數量減小了,python程式上也精簡了,
select_related()的效率要高于prefetch_related(),所以,最好在能用select_related()的地方盡可能使用它,也就是說,對于ForeignKey欄位,避免使用prefetch_related(),
聯用
對于同一個QuerySet,你能夠同時使用這兩個函式,
在咱們一直使用的例子上加一個model:Order (訂單)
class Order(models.Model):
customer = models.ForeignKey(Person)
orderinfo = models.CharField(max_length=50)
time = models.DateTimeField(auto_now_add = True)
def unicode(self):
return self.orderinfo
若是咱們拿到了一個訂單的id 咱們要知道這個訂單的客戶去過的省份,由于有ManyToManyField顯然必需要用prefetch_related(),若是只用prefetch_related()會怎樣呢?
plist = Order.objects.prefetch_related('customer__visitation__province').get(id=1)
for city in plist.customer.visitation.all():
... print city.province.name
...
顯然,關系到了4個表:Order、Person、City、Province,根據prefetch_related()的特性就得有4次SQL查詢
SELECTQSOptimize_order
.id
,QSOptimize_order
.customer_id
,QSOptimize_order
.orderinfo
,QSOptimize_order
.time
FROMQSOptimize_order
WHEREQSOptimize_order
.id
= 1 ;
SELECT QSOptimize_person
.id
, QSOptimize_person
.firstname
, QSOptimize_person
.lastname
, QSOptimize_person
.hometown_id
, QSOptimize_person
.living_id
FROM QSOptimize_person
WHERE QSOptimize_person
.id
IN (1);
SELECT (QSOptimize_person_visitation
.person_id
) AS _prefetch_related_val
, QSOptimize_city
.id
, QSOptimize_city
.name
, QSOptimize_city
.province_id
FROM QSOptimize_city
INNER JOIN QSOptimize_person_visitation
ON (QSOptimize_city
.id
= QSOptimize_person_visitation
.city_id
)
WHERE QSOptimize_person_visitation
.person_id
IN (1);
SELECT QSOptimize_province
.id
, QSOptimize_province
.name
FROM QSOptimize_province
WHERE QSOptimize_province
.id
IN (1, 2);
+----+-------------+---------------+---------------------+
| id | customer_id | orderinfo | time |
+----+-------------+---------------+---------------------+
| 1 | 1 | Info of Order | 2014-08-10 17:05:48 |
+----+-------------+---------------+---------------------+
1 row in set (0.00 sec)
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
| 1 | 張 | 三 | 3 | 1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+--------+-------------+
| 1 | 1 | 武漢市 | 1 |
| 1 | 2 | 廣州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
+----+--------+
| id | name |
+----+--------+
| 1 | 湖北省 |
| 2 | 廣東省 |
+----+--------+
2 rows in set (0.00 sec)
更好的辦法是先呼叫一次select_related()再呼叫prefetch_related(),最后再select_related()后面的表
plist = Order.objects.select_related('customer').prefetch_related('customer__visitation__province').get(id=1)
for city in plist.customer.visitation.all():
... print city.province.name
...
這樣只會有3次SQL查詢,Django會先作select_related,以后prefetch_related的時候會利用以前快取的資料,從而避免了1次額外的SQL查詢:
SELECTQSOptimize_order
.id
,QSOptimize_order
.customer_id
,QSOptimize_order
.orderinfo
,
QSOptimize_order
.time
,QSOptimize_person
.id
,QSOptimize_person
.firstname
,
QSOptimize_person
.lastname
,QSOptimize_person
.hometown_id
,QSOptimize_person
.living_id
FROMQSOptimize_order
INNER JOINQSOptimize_person
ON (QSOptimize_order
.customer_id
=QSOptimize_person
.id
)
WHEREQSOptimize_order
.id
= 1 ;
SELECT (QSOptimize_person_visitation
.person_id
) AS _prefetch_related_val
, QSOptimize_city
.id
,
QSOptimize_city
.name
, QSOptimize_city
.province_id
FROM QSOptimize_city
INNER JOIN QSOptimize_person_visitation
ON (QSOptimize_city
.id
= QSOptimize_person_visitation
.city_id
)
WHERE QSOptimize_person_visitation
.person_id
IN (1);
SELECT QSOptimize_province
.id
, QSOptimize_province
.name
FROM QSOptimize_province
WHERE QSOptimize_province
.id
IN (1, 2);
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| id | customer_id | orderinfo | time | id | firstname | lastname | hometown_id | living_id |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| 1 | 1 | Info of Order | 2014-08-10 17:05:48 | 1 | 張 | 三 | 3 | 1 |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+--------+-------------+
| 1 | 1 | 武漢市 | 1 |
| 1 | 2 | 廣州市 | 2 |
| 1 | 3 | 十堰市 | 1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
+----+--------+
| id | name |
+----+--------+
| 1 | 湖北省 |
| 2 | 廣東省 |
+----+--------+
2 rows in set (0.00 sec)
小結
由于select_related()老是在單次SQL查詢中解決問題,而prefetch_related()會對每一個相關表進行SQL查詢,所以select_related()的效率一般比后者高,
鑒于第一條,盡量的用select_related()解決問題,只有在select_related()不能解決問題的時候再去想prefetch_related(),
你能夠在一個QuerySet中同時使用select_related()和prefetch_related(),從而減小SQL查詢的次數,
只有prefetch_related()以前的select_related()是有效的,以后的將會被無視掉,
Django中的queryset
1、django中的queryset是一個查詢集,支持鏈式呼叫的介面如下:
all介面,用于查詢所有資料
filter介面,根據條件進行過濾
exclude介面,與filter一樣,只是結果與filter相反
reverse介面,把queryset中的結果倒序排列
distinct介面,用來進行去重查詢
none介面,回傳空的查詢集
2、Django的queryset是惰性的
例如:data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains="game"),data是一個名稱包含game的查詢集,但是如果只有這一句,那么Django的資料介面queryset并沒有對資料庫進行任何查詢,無論你加多少過濾條件,Django都不會對資料庫進行查詢,只有當你需要對data做進一步運算時(比如列印出查詢結果,判斷是否存在,統計查詢結果長度),Django才會真正執行對資料庫的查詢,
其實Django這樣設計的本意是盡量減少對資料庫的無效操作,比如查詢了結果而不用,那么就是對資源的很大浪費,對吧,
3、Django的queryset自帶快取(Cache)
先看個例子如下:
for i in data:
print i.name
上面的例子中我們對查詢集進行了遍歷,所有匹配的記錄會從資料庫獲取,也就是在這個時候才會去操作資料庫,這些結果會載入記憶體并保存在queryset內置的cache中,這樣如果你再次遍歷或讀取這個data時,Django就不需要重復查詢了,這樣也可以減少對資料庫的查詢,
再看如下例子:
例一
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game')
for i in data:
print i.name
例二
for i in Data.objects.filter(name__contains='game'):
print i.name
以上兩個例子中例一要優于例二,因為在使用for回圈后,Django不僅執行了查詢,還把查詢到的data放在了快取里,這個data是可以復用的,例二就不行了,后續如果還要使用data就不用再去查詢資料庫,而是直接從快取里讀取,
使用if判斷也會執行,一般來說我們在進行遍歷的時候都要加上一層判斷,if也會導致queryset執行, 快取data,所以我們在遍歷data時不用擔心Django會對資料庫進行二次查詢,
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game')
if data:
for i in data:
print i.name
上面的示例中,在進行if判斷就已經去查詢資料庫了,所以在我們for遍歷的時候拿的是快取里的資料,
有時我們只希望了解查詢的結果是否存在,而不需要使用整個資料集,這時使用if,就會觸發整個queryset的快取就變成了一件壞事情,當然了,解決方法就是使用exists,
resp = Data.objects.filter(name__contains='game').exists()
resp是True或False,與if判斷不同,exists只會檢查查詢結果是否存在,回傳True或False,而不會快取,當然了,使用哪種方法取決于我們邏輯哈,
有時候我們需要統計查詢結果數量,len()與count()均能統計查詢結果的數量,一般來說count更快,因為它是從資料庫層面直接獲取查詢結果的數量,而不是回傳整個資料集,而len會導致queryset的執行,需要將整個queryset載入記憶體后才能統計其長度,但事情也沒有絕對,如果資料集queryset已經在快取里了,使用len更快,因為它不需要跟資料庫再次打交道,
number1
number1 = Data.objects.filter(name__contains='game').count()
number2
number2 = Data.objects.filter(name__contains='game').len()
number3
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game')
number3 = data.len()
以上三個例子中,不考慮別的因素下,number1和number3都是比較好的,number2就盡量別考慮了,
有時候后端回傳資料量較大,會大量占用記憶體(快取),我們可以使用values和value_list方法按需提取資料,比如,我們只要資料里的name,而不用其它的資訊,諸如:性別,年齡之類的,那么我們就可以使用values和value_list方法,
values()
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game').values('name')
print data
data:[{'name': 'gameboy'}, {'name': 'gameheny'}, {'name': 'game'}, ...]
print type(data)
<class 'django.db.models.query.ValuesQuerySet'>
values_list()
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game').values_list('name')
print data
data:[('gameboy',), ('gameheny',), ('game',), ...]
print type(data)
<class 'django.db.models.query.ValuesListQuerySet'>
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(name__contains='game').values_list('name', flat=True)
print data
data:['gameboy', 'gameheny', 'game', ...]
print type(data)
<class 'django.db.models.query.ValuesListQuerySet'>
以上示例可以知道,無論是values還是value_list,回傳的資料都不是串列,而是查詢集,
有時候需要對資料庫中的某條已有資料或某些欄位進行更新,更好的方式是用update,而不是save方法,
save()
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.get(id=1)
data.name = "gamebox"
data.save()
update()
data = https://www.cnblogs.com/HeroZhang/archive/2023/07/12/Data.objects.filter(id=1).update(name="gamebox")
save()需要把整個Data物件的資料(姓名,年齡,性別…..)先提取出來,快取到記憶體中,變更資訊后再寫入資料庫,而update()直接對name做了更新,不需要把整個物件的資料載入記憶體,顯然更高效,
盡管單個資料占用記憶體不多,但是萬一用戶非常多呢,那么占用的記憶體加起來也是很恐怖的,
update()還會回傳已更新條目的數量,這點也非常有用,當然事情也沒有絕對,save()對于單個模型的更新還是很有優勢的,
如何從Django QuerySet中獲取欄位名稱,即使它是一個空集?
Django和Pandas之間的一個很酷的系結是能夠直接從QuerySet構建DataFrame,使用:
queryset = models.A.objects.filter(...).annotate(...)
frame = pd.DataFrame(queryset.values())
只要QuerySet至少回傳一條記錄,它就能很好地作業,在QuerySet級別上操作很有意思,因為在那里我們可以使用所有注解和本機列,
但是這個方法將回傳一個完全空的DataFrame(沒有定義列),比如說:
queryset = models.A.objects.filter(id__lt=0).annotate(...)
frame = pd.DataFrame(queryset.values())
DataFrame完全為空:
Empty DataFrame
Columns: []
Index: []
而我們想要的是這樣的東西:
Empty DataFrame
Columns: ["id", "key", "prop1", ...]
Index: []
其中保留了列名,以便使該幀能夠與其他幀無縫合并,
pandas的方法是在創建DataFrame時使用columns開關強制列名,
queryset = models.A.objects.filter(...)
frame = pd.DataFrame(queryset.values(), columns=queryset.get_fields())
不幸的是,get_fields或類似的物件似乎沒有實作,或者乍一看對QuerySet物件來說并不明顯,
我已經知道我可以從QuerySet中獲取exists()的列名,使用這個臟的:
frame = pd.DataFrame(queryset.values(), columns=queryset[0].dict.keys() )
但是,實際上它不會對空的QuerySet起作用,
我還知道我可以得到模型列如下:
frame = pd.DataFrame( queryset.values(), columns=[item.name for item in queryset.model._meta.get_fields()] + [...] )
但是這樣我就錯過了QuerySet創建的所有注解列,或者需要手動編碼,這是我們想要避免的,
我有一種感覺,不知何故,QuerySet可能知道它應該回傳的所有列,至少它應該在查詢執行之后知道它,因為空的SQL結果集肯定會包含列名和型別,
所以我的問題是如何從Django QuerySet中獲取欄位名稱,即使它是一個空集?
如果構造有點奇怪或復雜,只要它還允許獲取注解列名,這就不是問題,
可以這樣嘗試:
fields = [item.name for item in queryset.model._meta.get_fields()] + [item for item in queryset.query.annotations.keys()]
frame = pd.DataFrame(queryset.values(*fields), columns=fields)
我在除錯queryset物件時發現了這個解決方案,它有一個名為query的屬性,指向這個類Query的示例,在Query類中,有一個名為annotations的屬性,此屬性包含所有注解資訊,您可以使用它來獲取所有帶注解的欄位,
Django之QuerySet詳解
從資料庫中查詢出來的結果一般是一個集合,這個集合叫做 QuerySet,
一、QuerySet何時被提交
在內部,創建、過濾、切片和傳遞一個QuerySet不會真實操作資料庫,在你對查詢集提交之前,不會發生任何實際的資料庫操作,可以使用下列方法對QuerySet提交查詢操作:
迭代:QuerySet是可迭代的,在首次迭代查詢集時執行實際的資料庫查詢, 例如, 下面的陳述句會將資料庫中所有Entry的headline列印出來:
for e in Entry.objects.all():
print(e.headline)
切片:如果使用切片的”step“引數,Django 將執行資料庫查詢并回傳一個串列,
Pickling/快取
repr()
len():當你對QuerySet呼叫len()時, 將提交資料庫操作,
list():對QuerySet呼叫list()將強制提交操作entry_list = list(Entry.objects.all())
bool()
測驗布林值,像這樣:
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
注:如果你需要知道是否存在至少一條記錄(而不需要真實的物件),使用exists() 將更加高效,
二、QuerySet
class QuerySet(model=None, query=None, using=None)[source]
QuerySet類具有兩個公有屬性用于內省:
ordered:如果QuerySet是排好序的則為True,否則為False,
db:如果現在執行,則回傳使用的資料庫,
三、回傳新QuerySets的API
以下的方法都將回傳一個新的QuerySets,重點是加粗的幾個API,其它的使用場景很少,
方法名 解釋
filter() 過濾查詢物件, exclude() 排除滿足條件的物件
annotate() 使用聚合函式 order_by() 對查詢集進行排序
reverse() 反向排序 distinct() 對查詢集去重
values() 回傳包含物件具體值的字典的QuerySet
values_list() 與values()類似,只是回傳的是元組而不是字典,
dates() 根據日期獲取查詢集 datetimes() 根據時間獲取查詢集
none() 創建空的查詢集 all() 獲取所有的物件
union() 并集 intersection() 交集
difference() 差集 select_related() 附帶查詢關聯物件
prefetch_related() 預先查詢 extra() 附加SQL查詢
defer() 不加載指定欄位 only() 只加載指定的欄位
using() 選擇資料庫 select_for_update() 鎖住選擇的物件,直到事務結束,
raw() 接收一個原始的SQL查詢
- filter() filter(kwargs)
回傳滿足查詢引數的物件集合,查找的引數(kwargs)應該滿足下文欄位查找中的格式,多個引數之間是和AND的關系, - exclude() exclude(kwargs)回傳一個新的QuerySet,它包含不滿足給定的查找引數的物件,
查找的引數(kwargs)應該滿足下文欄位查找中的格式,多個引數通過AND連接,然后所有的內容放入NOT() 中,
下面的示例排除所有pub_date晚于2005-1-3且headline為“Hello” 的記錄:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
下面的示例排除所有pub_date晚于2005-1-3或者headline 為“Hello” 的記錄:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello') - annotate() annotate(args, *kwargs)使用提供的聚合運算式查詢物件,
運算式可以是簡單的值、對模型(或任何關聯模型)上的欄位的參考或者聚合運算式(平均值、總和等),
annotate()的每個引數都是一個annotation,它將添加到回傳的QuerySet每個物件中,
關鍵字引數指定的Annotation將使用關鍵字作為Annotation 的別名, 匿名引數的別名將基于聚合函式的名稱和模型的欄位生成, 只有參考單個欄位的聚合運算式才可以使用匿名引數, 其它所有形式都必須用關鍵字引數,
例如,如果正在操作一個Blog串列,你可能想知道每個Blog有多少Entry:
from django.db.models import Count
q = Blog.objects.annotate(Count('entry'))
The name of the first blog
q[0].name
'Blogasaurus'
The number of entries on the first blog
q[0].entry__count
42
Blog模型本身沒有定義entry__count屬性,但是通過使用一個關鍵字引數來指定聚合函式,可以控制Annotation的名稱:
q = Blog.objects.annotate(number_of_entries=Count('entry'))
The number of entries on the first blog, using the name provided
q[0].number_of_entries
42
- order_by()
order_by(*fields)
默認情況下,根據模型的Meta類中的ordering屬性對QuerySet中的物件進行排序
Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')
上面的結果將按照pub_date降序排序,然后再按照headline升序排序,"-pub_date"前面的負號表示降序順序, 升序是默認的, 要隨機排序,使用"?",如下所示:
Entry.objects.order_by('?')
注:order_by('?')可能耗費資源且很慢,這取決于使用的資料庫,
若要按照另外一個模型中的欄位排序,可以使用查詢關聯模型的語法,即通過欄位的名稱后面跟兩個下劃線(__),再加上新模型中的欄位的名稱,直到希望連接的模型, 像這樣:
Entry.objects.order_by('blog__name', 'headline')
如果排序的欄位與另外一個模型關聯,Django將使用關聯的模型的默認排序,或者如果沒有指定Meta.ordering將通過關聯的模型的主鍵排序, 例如,因為Blog模型沒有指定默認的排序:
Entry.objects.order_by('blog')
與以下相同:
Entry.objects.order_by('blog__id')
如果Blog設定了ordering = ['name'],那么第一個QuerySet將等同于:
Entry.objects.order_by('blog__name')
還可以通過呼叫運算式的desc()或者asc()方法:
Entry.objects.order_by(Coalesce('summary', 'headline').desc())
考慮下面的情況,指定一個多值欄位來排序(例如,一個ManyToManyField 欄位或者ForeignKey 欄位的反向關聯):
class Event(Model):
parent = models.ForeignKey(
'self', on_delete=models.CASCADE, related_name='children',
)
date = models.DateField()
Event.objects.order_by('children__date')
在這里,每個Event可能有多個排序資料;具有多個children的每個Event將被多次回傳到order_by()創建的新的QuerySet中, 換句話說,用order_by()方法對QuerySet物件進行操作會回傳一個擴大版的新QuerySet物件,因此,使用多值欄位對結果進行排序時要格外小心,
沒有方法指定排序是否考慮大小寫, 對于大小寫的敏感性,Django將根據資料庫中的排序方式排序結果,
可以通過Lower將一個欄位轉換為小寫來排序,它將達到大小寫一致的排序:
Entry.objects.order_by(Lower('headline').desc())
可以通過檢查QuerySet.ordered屬性來知道查詢是否是排序的,
每個order_by()都將清除前面的任何排序, 例如下面的查詢將按照pub_date排序,而不是headline:
Entry.objects.order_by('headline').order_by('pub_date')
5. reverse()
反向排序QuerySet中回傳的元素, 第二次呼叫reverse()將恢復到原有的排序,
如要獲取QuerySet中最后五個元素,可以這樣做:
my_queryset.reverse()[:5]
這與Python直接使用負索引有點不一樣, Django不支持負索引,只能曲線救國,
6. distinct() distinct(*fields)
去除查詢結果中重復的行,
默認情況下,QuerySet不會去除重復的行,當查詢跨越多張表的資料時,QuerySet可能得到重復的結果,這時候可以使用distinct()進行去重,
7. values() values(fields, *expressions)
回傳一個包含資料的字典的queryset,而不是模型實體,
每個字典表示一個物件,鍵對應于模型物件的屬性名稱,
下面的例子將values() 與普通的模型物件進行比較:
串列中包含的是Blog物件
Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>
串列中包含的是資料字典
Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
該方法接收可選的位置引數*fields,它指定values()應該限制哪些欄位,如果指定欄位,每個字典將只包含指定的欄位的鍵/值,如果沒有指定欄位,每個字典將包含資料庫表中所有欄位的鍵和值,
例如:
Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>
values()方法還有關鍵字引數**expressions,這些引數將傳遞給annotate():
from django.db.models.functions import Lower
Blog.objects.values(lower_name=Lower('name'))
<QuerySet [{'lower_name': 'beatles blog'}]>
在values()子句中的聚合應用于相同values()子句中的其他引數之前, 如果需要按另一個值分組,請將其添加到較早的values()子句中, 像這樣:
from django.db.models import Count
Blog.objects.values('author', entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]>
Blog.objects.values('author').annotate(entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 33}]>
注意:如果你有一個欄位foo是一個ForeignKey,默認的foo_id引數回傳的字典中將有一個叫做foo 的鍵,因為這是保存實際值的那個隱藏的模型屬性的名稱, 當呼叫foo_id并傳遞欄位的名稱,傳遞foo 或values()都可以,得到的結果是相同的,像這樣:
Entry.objects.values()
<QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
Entry.objects.values('blog')
<QuerySet [{'blog': 1}, ...]>
Entry.objects.values('blog_id')
<QuerySet [{'blog_id': 1}, ...]>
當values()與distinct()一起使用時,注意排序可能影響最終的結果,
如果values()子句位于extra()呼叫之后,extra()中的select引數定義的欄位必須顯式包含在values()呼叫中, values( 呼叫后面的extra( 呼叫將忽略選擇的額外的欄位,
在values()之后呼叫only()和defer()不太合理,所以將引發一個NotImplementedError,
可以通過ManyToManyField、ForeignKey 和 OneToOneFiel 屬性反向參考關聯的模型的欄位:
Blog.objects.values('name', 'entry__headline')
<QuerySet [{'name': 'My blog', 'entry__headline': 'An entry'},
{'name': 'My blog', 'entry__headline': 'Another entry'}, ...]>
- values_list() values_list(*fields, flat=False)
與values()類似,只是在迭代時回傳的是元組而不是字典,每個元組包含傳遞給values_list()呼叫的相應欄位或運算式的值,因此第一個專案是第一個欄位等, 像這樣:
Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
from django.db.models.functions import Lower
Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>
如果只傳遞一個欄位,還可以傳遞flat引數, 如果為True,它表示回傳的結果為單個值而不是元組, 如下所示:
Entry.objects.values_list('id').order_by('id')
<QuerySet[(1,), (2,), (3,), ...]>
Entry.objects.values_list('id', flat=True).order_by('id')
<QuerySet [1, 2, 3, ...]>
如果有多個欄位,傳遞flat將發生錯誤,
如果不傳遞任何值給values_list(),它將回傳模型中的所有欄位,以在模型中定義的順序,
常見的情況是獲取某個模型實體的特定欄位值,可以使用values_list(),然后呼叫get():
Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'
values()和values_list()都用于特定情況下的優化:檢索資料子集,而無需創建模型實體,
注意通過ManyToManyField進行查詢時的行為:
Author.objects.values_list('name', 'entry__headline')
<QuerySet [('Noam Chomsky', 'Impressions of Gaza'),
('George Orwell', 'Why Socialists Do Not Believe in Fun'),
('George Orwell', 'In Defence of English Cooking'),
('Don Quixote', None)]>
類似地,當查詢反向外鍵時,對于沒有任何作者的條目,回傳None,
Entry.objects.values_list('authors')
<QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]>
- dates() dates(field, kind, order='ASC')
回傳一個QuerySet,表示QuerySet內容中特定型別的所有可用日期的datetime.date物件串列,
field引數是模型的DateField的名稱, kind引數應為"year","month"或"day", 結果串列中的每個datetime.date物件被截取為給定的型別,
"year" 回傳對應該field的所有不同年份值的串列,
"month"回傳欄位的所有不同年/月值的串列,
"day"回傳欄位的所有不同年/月/日值的串列,
order引數默認為'ASC',或者'DESC', 它指定如何排序結果,
例子:
Entry.objects.dates('pub_date', 'year')
[datetime.date(2005, 1, 1)]
Entry.objects.dates('pub_date', 'month')
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
Entry.objects.dates('pub_date', 'day')
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.date(2005, 3, 20)]
- datetimes() datetimes(field_name, kind, order='ASC', tzinfo=None)
回傳QuerySet,為datetime.datetime物件的串列,表示QuerySet內容中特定種類的所有可用日期,
field_name應為模型的DateTimeField的名稱,
kind引數應為"hour","minute","month","year","second"或"day",
結果串列中的每個datetime.datetime物件被截取到給定的型別,
order引數默認為'ASC',或者'DESC', 它指定如何排序結果,
tzinfo引數定義在截取之前將資料時間轉換到的時區, - none() 呼叫none()將創建一個不回傳任何物件的查詢集,并且在訪問結果時不會執行任何查詢,
例子:
Entry.objects.none()
<QuerySet []>
from django.db.models.query import EmptyQuerySet
isinstance(Entry.objects.none(), EmptyQuerySet)
True
- all() 回傳當前QuerySet(或QuerySet子類)的副本,通常用于獲取全部QuerySet物件,
- union() union(*other_qs, all=False) 集合中并集
使用SQL的UNION運算子組合兩個或更多個QuerySet的結果,例如:
qs1.union(qs2, qs3)
默認情況下,UNION運算子僅選擇不同的值, 要允許重復值,請使用all=True引數,
- intersection() intersection(*other_qs) 集合中交集
使用SQL的INTERSECT運算子回傳兩個或更多個QuerySet的共有元素,例如:
qs1.intersection(qs2, qs3)
- difference() difference(*other_qs) 集合中差集
使用SQL的EXCEPT運算子只保留QuerySet中的元素,但不保留其他QuerySet中的元素,例如:
qs1.difference(qs2, qs3)
- select_related() select_related(*fields)
沿著外鍵關系查詢關聯的物件的資料,這會生成一個復雜的查詢并引起性能的損耗,但是在以后使用外鍵關系時將不需要再次資料庫查詢,
下面的例子解釋了普通查詢和select_related()查詢的區別, 下面是一個標準的查詢:
訪問資料庫,
e = Entry.objects.get(id=5)
再次訪問資料庫以得到關聯的Blog物件,
b = e.blog
下面是一個select_related查詢:
訪問資料庫,
e = Entry.objects.select_related('blog').get(id=5)
不會訪問資料庫,因為e.blog已經在前面的查詢中獲得了,
b = e.blog
select_related()可用于objects任何的查詢集:
from django.utils import timezone
Find all the blogs with entries scheduled to be published in the future.
blogs = set()
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
# 沒有select_related(),下面的陳述句將為每次回圈迭代生成一個資料庫查詢,以獲得每個entry關聯的blog,
blogs.add(e.blog)
filter()和select_related()的順序不重要, 下面的查詢集是等同的:
Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
可以沿著外鍵查詢, 如果有以下模型:
from django.db import models
class City(models.Model):
# ...
pass
class Person(models.Model):
# ...
hometown = models.ForeignKey(
City,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)
呼叫Book.objects.select_related('author__hometown').get(id=4)將快取相關的Person 和相關的City:
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # Doesn't hit the database.
c = p.hometown # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author # Hits the database.
c = p.hometown # Hits the database.
在傳遞給select_related()的欄位中,可以使用任何ForeignKey和OneToOneField,
在傳遞給select_related的欄位中,還可以反向參考OneToOneField,也就是說,可以回溯到定義OneToOneField 的欄位, 此時,可以使用關聯物件欄位的related_name,而不要指定欄位的名稱,
17. prefetch_related() prefetch_related(*lookups)
在單個批處理中自動檢索每個指定查找的相關物件,
與select_related類似,但是策略是完全不同的,
假設有這些模型:
from django.db import models
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def str(self): # unicode on Python 2
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)
并運行:
Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
問題是每次QuerySet要求Pizza.objects.all()查詢資料庫,因此self.toppings.all()將在Pizza Pizza.str()中的每個專案的Toppings表上運行查詢,
可以使用prefetch_related減少為只有兩個查詢:
Pizza.objects.all().prefetch_related('toppings')
這意味著現在每次self.toppings.all()被呼叫,不會再去資料庫查找,而是在一個預取的QuerySet快取中查找,
還可以使用正常連接語法來執行相關欄位的相關欄位, 假設在上面的例子中增加一個額外的模型:
class Restaurant(models.Model):
pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
以下是合法的:
Restaurant.objects.prefetch_related('pizzas__toppings')
這將預取所有屬于餐廳的比薩餅,和所有屬于那些比薩餅的配料, 這將導致總共3個查詢 - 一個用于餐館,一個用于比薩餅,一個用于配料,
Restaurant.objects.prefetch_related('best_pizza__toppings')
這將獲取最好的比薩餅和每個餐廳最好的披薩的所有配料, 這將在3個表中查詢 - 一個為餐廳,一個為“最佳比薩餅”,一個為一個為配料,
當然,也可以使用best_pizza來獲取select_related關系,以將查詢數減少為2:
Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
- extra() extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
有些情況下,Django的查詢語法難以簡單的表達復雜的WHERE子句,對于這種情況,可以在extra()生成的SQL從句中注入新子句,使用這種方法作為最后的手段,這是一個舊的API,在將來的某個時候可能被棄用,僅當無法使用其他查詢方法表達查詢時才使用它,
例如:
qs.extra(
... select={'val': "select col from sometable where othercol = %s"},
... select_params=(someparam,),
... )
相當于:
qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
- defer() defer(*fields)
在一些復雜的資料建模情況下,模型可能包含大量欄位,其中一些可能包含大尺寸資料(例如文本欄位),將它們轉換為Python物件需要花費很大的代價,
當最扯訓取資料時不知道是否需要這些特定欄位的情況下,如果正在使用查詢集的結果,可以告訴Django不要從資料庫中檢索它們,
通過傳遞欄位名稱到defer()實作不加載:
Entry.objects.defer("headline", "body")
具有延遲加載欄位的查詢集仍將回傳模型實體,
每個延遲欄位將在你訪問該欄位時從資料庫中檢索(每次只檢索一個,而不是一次檢索所有的延遲欄位),
可以多次呼叫defer(), 每個呼叫都向延遲集添加新欄位:
延遲body和headline兩個欄位,
Entry.objects.defer("body").filter(rating=5).defer("headline")
欄位添加到延遲集的順序無關緊要,對已經延遲的欄位名稱再次defer()沒有問題(該欄位仍將被延遲),
可以使用標準的雙下劃線符號來分隔關聯的欄位,從而加載關聯模型中的欄位:
Blog.objects.select_related().defer("entry__headline", "entry__body")
如果要清除延遲欄位集,將None作為引數傳遞到defer():
立即加載所有的欄位,
my_queryset.defer(None)
defer()方法(及其兄弟,only())僅適用于高級用例,它們提供了資料加載的優化方法,
20. only() only(*fields)
only()方法與defer()相反,
如果有一個模型幾乎所有的欄位需要延遲,使用only()指定補充的欄位集可以使代碼更簡單,
假設有一個包含欄位biography、age和name的模型, 以下兩個查詢集是相同的,就延遲欄位而言:
Person.objects.defer("age", "biography")
Person.objects.only("name")
每當你呼叫only()時,它將替換立即加載的欄位集,因此,對only()的連續呼叫的結果是只有最后一次呼叫的欄位被考慮:
This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")
由于defer()以遞增方式動作(向延遲串列中添加欄位),因此你可以結合only()和defer()呼叫:
Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")
Final result loads headline and body immediately (only() replaces any
existing set of fields).
Entry.objects.defer("body").only("headline", "body")
當對具有延遲欄位的實體呼叫save()時,僅保存加載的欄位,
21. using() using(alias)
如果正在使用多個資料庫,這個方法用于指定在哪個資料庫上查詢QuerySet,方法的唯一引數是資料庫的別名,定義在DATABASES,
例如:
queries the database with the 'default' alias.
Entry.objects.all()
queries the database with the 'backup' alias
Entry.objects.using('backup')
- select_for_update() select_for_update(nowait=False, skip_locked=False)
回傳一個鎖住行直到事務結束的查詢集,如果資料庫支持,它將生成一個SELECT ... FOR UPDATE陳述句,
例如:
entries = Entry.objects.select_for_update().filter(author=request.user)
所有匹配的行將被鎖定,直到事務結束,這意味著可以通過鎖防止資料被其它事務修改,
一般情況下如果其他事務鎖定了相關行,那么本查詢將被阻塞,直到鎖被釋放,使用select_for_update(nowait=True)將使查詢不阻塞,如果其它事務持有沖突的鎖,那么查詢將引發DatabaseError例外,也可以使用select_for_update(skip_locked=True)忽略鎖定的行,nowait和skip_locked是互斥的,
目前,postgresql,oracle和mysql資料庫后端支持select_for_update(),但是,MySQL不支持nowait和skip_locked引數, - raw() raw(raw_query, params=None, translations=None)
接收一個原始的SQL查詢,執行它并回傳一個django.db.models.query.RawQuerySet實體,
這個RawQuerySet實體可以迭代,就像普通的QuerySet一樣,
四、不回傳QuerySets的API
以下的方法不會回傳QuerySets,但是作用非常強大,尤其是粗體顯示的方法,需要背下來,
方法名 解釋
get() 獲取單個物件
create() 創建物件,無需save()
get_or_create() 查詢物件,如果沒有找到就新建物件
update_or_create() 更新物件,如果沒有找到就創建物件
bulk_create() 批量創建物件
count() 統計物件的個數
in_bulk() 根據主鍵值的串列,批量回傳物件
iterator() 獲取包含物件的迭代器
latest() 獲取最近的物件
earliest() 獲取最早的物件
first() 獲取第一個物件
last() 獲取最后一個物件
aggregate() 聚合操作
exists() 判斷queryset中是否有物件
update() 批量更新物件
delete() 批量洗掉物件
as_manager() 獲取管理器
- get() get(**kwargs) 回傳按照查詢引數匹配到的單個物件,引數的格式應該符合Field lookups的要求,
如果匹配到的物件個數不只一個的話,觸發MultipleObjectsReturned例外
如果根據給出的引數匹配不到物件的話,觸發DoesNotExist例外,例如:
Entry.objects.get(id='foo') # raises Entry.DoesNotExist
DoesNotExist例外從django.core.exceptions.ObjectDoesNotExist繼承,可以定位多個DoesNotExist例外, 例如:
from django.core.exceptions import ObjectDoesNotExist
try:
e = Entry.objects.get(id=3)
b = Blog.objects.get(id=1)
except ObjectDoesNotExist:
print("Either the entry or blog doesn't exist.")
如果希望查詢器只回傳一行,則可以使用get()而不使用任何引數來回傳該行的物件:
entry = Entry.objects.filter(...).exclude(...).get()
2. create() create(**kwargs) 在一步操作中同時創建并且保存物件的便捷方法.
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
等于:
p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
引數force_insert表示強制創建物件,如果model中有一個你手動設定的主鍵,并且這個值已經存在于資料庫中, 呼叫create()將會失敗并且觸發IntegrityError因為主鍵必須是唯一的,如果你手動設定了主鍵,做好例外處理的準備,
3. get_or_create() get_or_create(defaults=None, **kwargs)
通過kwargs來查詢物件的便捷方法(如果模型中的所有欄位都有默認值,可以為空),如果該物件不存在則創建一個新物件,
該方法回傳一個由(object, created)組成的元組,元組中的object 是一個查詢到的或者是被創建的物件, created是一個表示是否創建了新的物件的布林值,
對于下面的代碼:
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
obj.save()
如果模型的欄位數量較大的話,這種模式就變的非常不易用了, 上面的示例可以用get_or_create()重寫 :
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
任何傳遞給get_or_create()的關鍵字引數,除了一個可選的defaults,都將傳遞給get()呼叫, 如果查找到一個物件,回傳一個包含匹配到的物件以及False 組成的元組, 如果查找到的物件超過一個以上,將引發MultipleObjectsReturned,如果查找不到物件,get_or_create()將會實體化并保存一個新的物件,回傳一個由新的物件以及True組成的元組,新的物件將會按照以下的邏輯創建:
params = {k: v for k, v in kwargs.items() if '__' not in k}
params.update({k: v() if callable(v) else v for k, v in defaults.items()})
obj = self.model(**params)
obj.save()
它表示從非'defaults' 且不包含雙下劃線的關鍵字引數開始,然后將defaults的內容添加進來,覆寫必要的鍵,并使用結果作為關鍵字引數傳遞給模型類,
如果有一個名為defaults__exact的欄位,并且想在get_or_create()時用它作為精確查詢,只需要使用defaults,像這樣:
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
當你使用手動指定的主鍵時,get_or_create()方法與create()方法有相似的錯誤行為 , 如果需要創建一個物件而該物件的主鍵早已存在于資料庫中,IntegrityError例外將會被觸發,
這個方法假設進行的是原子操作,并且正確地配置了資料庫和正確的底層資料庫行為,如果資料庫級別沒有對get_or_create中用到的kwargs強制要求唯一性(unique和unique_together),方法容易導致競態條件,可能會有相同引數的多行同時插入,(簡單理解,kwargs必須指定的是主鍵或者unique屬性的欄位才安全,)
最后建議只在Django視圖的POST請求中使用get_or_create(),因為這是一個具有修改性質的動作,不應該使用在GET請求中,那樣不安全,
可以通過ManyToManyField屬性和反向關聯使用get_or_create(),在這種情況下,應該限制查詢在關聯的背景關系內部, 否則,可能導致完整性問題,
例如下面的模型:
class Chapter(models.Model):
title = models.CharField(max_length=255, unique=True)
class Book(models.Model):
title = models.CharField(max_length=256)
chapters = models.ManyToManyField(Chapter)
可以通過Book的chapters欄位使用get_or_create(),但是它只會獲取該Book內部的背景關系:
book = Book.objects.create(title="Ulysses")
book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, True)
book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, False)
Chapter.objects.create(title="Chapter 1")
<Chapter: Chapter 1>
book.chapters.get_or_create(title="Chapter 1")
Raises IntegrityError
發生這個錯誤是因為嘗試通過Book “Ulysses”獲取或者創建“Chapter 1”,但是它不能,因為它與這個book不關聯,但因為title 欄位是唯一的它仍然不能創建,
在Django1.11在defaults中增加了對可呼叫值的支持,
4. update_or_create() update_or_create(defaults=None, **kwargs) 類似前面的get_or_create(),
通過給出的kwargs來更新物件的便捷方法, 如果沒找到物件,則創建一個新的物件,defaults是一個由 (field, value)對組成的字典,用于更新物件,defaults中的值可以是可呼叫物件(也就是說函式等),
該方法回傳一個由(object, created)組成的元組,元組中的object是一個創建的或者是被更新的物件, created是一個標示是否創建了新的物件的布林值,
update_or_create方法嘗試通過給出的kwargs 去從資料庫中獲取匹配的物件, 如果找到匹配的物件,它將會依據defaults 字典給出的值更新欄位,
像下面的代碼:
defaults = {'first_name': 'Bob'}
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
for key, value in defaults.items():
setattr(obj, key, value)
obj.save()
except Person.DoesNotExist:
new_values = {'first_name': 'John', 'last_name': 'Lennon'}
new_values.update(defaults)
obj = Person(**new_values)
obj.save()
如果模型的欄位數量較大的話,這種模式就變的非常不易用了,
上面的示例可以用update_or_create() 重寫:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
kwargs中的名稱如何決議的詳細描述可以參見get_or_create(),
和get_or_create()一樣,這個方法也容易導致競態條件,如果資料庫層級沒有前置唯一性會讓多行同時插入,
在Django1.11在defaults中增加了對可呼叫值的支持,
5. bulk_create() bulk_create(objs, batch_size=None)
以高效的方式(通常只有1個查詢,無論有多少物件)將提供的物件串列插入到資料庫中:
Entry.objects.bulk_create([
... Entry(headline='This is a test'),
... Entry(headline='This is only a test'),
... ])
注意事項:
不會呼叫模型的save()方法,并且不會發送pre_save和post_save信號,
不適用于多表繼承場景中的子模型,
如果模型的主鍵是AutoField,則不會像save()那樣檢索并設定主鍵屬性,除非資料庫后端支持,
不適用于多對多關系,
batch_size引數控制在單個查詢中創建的物件數,
- count() 回傳在資料庫中對應的QuerySet物件的個數,count()永遠不會引發例外,
回傳總個數.
Entry.objects.count()
回傳包含有'Lennon'的物件的總數
Entry.objects.filter(headline__contains='Lennon').count()
7. in_bulk() in_bulk(id_list=None)
獲取主鍵值的串列,并回傳將每個主鍵值映射到具有給定ID的物件的實體的字典, 如果未提供串列,則會回傳查詢集中的所有物件,
例如:
Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
Blog.objects.in_bulk([])
{}
Blog.objects.in_bulk()
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
如果向in_bulk()傳遞一個空串列,會得到一個空的字典,
在舊版本中,id_list是必需的引數,現在是一個可選引數,
- iterator() 提交資料庫操作,獲取QuerySet,并回傳一個迭代器,
QuerySet通常會在內部快取其結果,以便在重復計算時不會導致額外的查詢,而iterator()將直接讀取結果,不在QuerySet級別執行任何快取,對于回傳大量只需要訪問一次的物件的QuerySet,這可以帶來更好的性能,顯著減少記憶體使用,
請注意,在已經提交了的iterator()上使用QuerySet會強制它再次提交資料庫操作,進行重復查詢,此外,使用iterator()會導致先前的prefetch_related()呼叫被忽略,因為這兩個一起優化沒有意義, - latest() latest(field_name=None)使用日期欄位field_name,按日期回傳最新物件,
下例根據Entry的'pub_date'欄位回傳最新發布的entry:
Entry.objects.latest('pub_date')
如果模型的Meta指定了get_latest_by,則可以將latest()引數留給earliest()或者field_name, 默認情況下,Django將使用get_latest_by中指定的欄位,
earliest()和latest()可能會回傳空日期的實體,可能需要過濾掉空值:
Entry.objects.filter(pub_date__isnull=False).latest('pub_date') - earliest() earliest(field_name=None)
類同latest(), - first() 回傳結果集的第一個物件, 當沒有找到時回傳None,如果QuerySet沒有設定排序,則將會自動按主鍵進行排序,例如:
p = Article.objects.order_by('title', 'pub_date').first()
first()是一個簡便方法,下面的例子和上面的代碼效果是一樣:
try:
p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
p = None - last() 作業方式類似first(),只是回傳的是查詢集中最后一個物件,
- aggregate() aggregate(args, *kwargs)
回傳匯總值的字典(平均值,總和等),通過QuerySet進行計算,每個引數指定回傳的字典中將要包含的值,
使用關鍵字引數指定的聚合將使用關鍵字引數的名稱作為Annotation 的名稱, 匿名引數的名稱將基于聚合函式的名稱和模型欄位生成, 復雜的聚合不可以使用匿名引數,必須指定一個關鍵字引數作為別名,
例如,想知道Blog Entry 的數目:
from django.db.models import Count
q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}
通過使用關鍵字引數來指定聚合函式,可以控制回傳的聚合的值的名稱:
q = Blog.objects.aggregate(number_of_entries=Count('entry'))
- exists() 如果QuerySet包含任何結果,則回傳True,否則回傳False,
查找具有唯一性欄位(例如primary_key)的模型是否在一個QuerySet中的最高效的方法是:
entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
print("Entry contained in queryset")
它將比下面的方法快很多,這個方法要求對QuerySet求值并迭代整個QuerySet:
if entry in some_queryset:
print("Entry contained in QuerySet")
若要查找一個QuerySet是否包含任何元素:
if some_queryset.exists():
print("There is at least one object in some_queryset")
將快于:
if some_queryset:
print("There is at least one object in some_queryset") - update() update(**kwargs)
對指定的欄位執行批量更新操作,并回傳匹配的行數(如果某些行已具有新值,則可能不等于已更新的行數),
例如,要對2010年發布的所有博客條目啟用評論,可以執行以下操作:
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
可以同時更新多個欄位 (沒有多少欄位的限制), 例如同時更新comments_on和headline欄位:
Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')
update()方法無需save操作,唯一限制是它只能更新模型主表中的列,而不是關聯的模型,例如不能這樣做:
Entry.objects.update(blog__name='foo') # Won't work!
仍然可以根據相關欄位進行過濾:
Entry.objects.filter(blog__id=1).update(comments_on=True)
update()方法回傳受影響的行數:
Entry.objects.filter(id=64).update(comments_on=True)
1
Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True)
0
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
132
如果你只是更新一下物件,不需要為物件做別的事情,最有效的方法是呼叫update(),而不是將模型物件加載到記憶體中, 例如,不要這樣做:
e = Entry.objects.get(id=10)
e.comments_on = False
e.save()
建議如下操作:
Entry.objects.filter(id=10).update(comments_on=False)
用update()還可以防止在加載物件和呼叫save()之間的短時間內資料庫中某些內容可能發生更改的競爭條件,
如果想更新一個具有自定義save()方法的模型的記錄,請回圈遍歷它們并呼叫save(),如下所示:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
- delete() 批量洗掉QuerySet中的所有物件,并回傳洗掉的物件個數和每個物件型別的洗掉次數的字典,
delete()動作是立即執行的,
不能在QuerySet上呼叫delete(),
例如,要洗掉特定博客中的所有條目:
b = Blog.objects.get(pk=1)
Delete all the entries belonging to this Blog.
Entry.objects.filter(blog=b).delete()
(4, {'weblog.Entry': 2, 'weblog.Entry_authors': 2})
默認情況下,Django的ForeignKey使用SQL約束ON DELETE CASCADE,任何具有指向要洗掉的物件的外鍵的物件將與它們一起被洗掉, 像這樣:
blogs = Blog.objects.all()
This will delete all Blogs and all of their Entry objects.
blogs.delete()
(5, {'weblog.Blog': 1, 'weblog.Entry': 2, 'weblog.Entry_authors': 2})
這種級聯的行為可以通過的ForeignKey的on_delete引數自定義,(什么時候要改變這種行為呢?比如日志資料,就不能和它關聯的主體一并被洗掉!)
delete()會為所有已洗掉的物件(包括級聯洗掉)發出pre_delete和post_delete信號,
- as_manager()
class method as_manager() 一個類方法,回傳Manager的實體與QuerySet的方法的副本,
Django中的queryset查詢方法
在django中,我們常常需要對得到的queryset進行查詢,django會將我們的查詢陳述句轉換為slite3的查詢語法然后去資料庫查詢資料,我們要做的就是使用django給我們的一些查詢方法進行查詢
我們可以通過模型名稱+objects.all()來獲取一個完整的queryset,然后使用這個queryset進行查詢和篩選
QuerySet.filter(condition)
該方法將根據condition提取queryset中的模型實體,并且回傳一個queryset
QuerySet.get(condition)
需要注意get方法僅回傳一個模型實體,不回傳queryset,如果查詢的結果超過1將報錯
QuerySet.exclude(condition)
該方法是filter方法的取反
鏈式查詢 有了上述的三個方法我們就可以進行簡單的查詢了,而且我們還可以進行連續的查詢,例如:
queryset.filter(tag='user').filter(date =date(2018,11,06))
queryset.filter(tag='user').order_by('release_time')
使用Q()進行查詢
除此以外,我們還可以使用一些更為復雜的篩選方法,有一些輔助函式可以使用,例如Q():
from django.db.models import Q
condition1 = Q(tag='user1')
condition2 = Q(tag='user2')
condition3 = Q(date__gte=date(2018,11,01))
queryset.filter(condition1|condition2) #提取并集
queryset.filter(condition1,condition3) #提取交集
queryset.filter(condtion1,condition2) #取反
常用的查詢條件
上述中有一個雙下劃線加gte的東西出現,這個東西的意思是greater than or equal to的意思,實際上,django還提供了一些常用的用于比較大小,包含某些字體,是否精確匹配的方法用于查詢,
queryset.filter(date__lte=date(2018,11,01)) #時間早于20181101
queryset.filter(date__gte=date(2018,11,01)) #時間晚于20181101
queryset.filter(tag__contain='tag1') #tag欄位中包含字串tag1的模型實體
queryset.filter(tag__icontain='tag1') #同上,同時忽略大小寫
queryset.filter(tag__iexact='tag1')#tag欄位中等于tag1的模型實體,忽略大小寫
queryset.filter(tag__exact='tag1')#精確匹配
queryset.filter(tag__isnull=True)#提取tag欄位為缺失值的模型實體
跨模型查詢
有時候我們需要根據那些django的關系欄位OneToOneField,ForeignKey,ManyToManyField的關系來直接找到關聯的模型并進行查詢,對于這種需求,django進行查詢也較為方便,只需要使用雙下劃線進行連接即可,
queryset.filter(othermodel__tag='tag1')
找到與該模型關聯模型othermodel的tag為tag1的該模型的實體
queryset.filter(othermodel__otherothermodel__tag='tag1')
穿越兩個模型進行查詢也是可以的,
- Queryset簡介
每個Model都有一個默認的manager實體,名為objects,QuerySet有兩種來源:通過manager的方法得到、通過QuerySet的方法得到,mananger的方法和QuerySet的方法大部分同名,同意思,如filter(),update()等,但也有些不同,如manager有create()、get_or_create(),而QuerySet有delete()等,看原始碼就可以很容易的清楚Manager類與Queryset類的關系,Manager類的絕大部分方法是基于Queryset的,一個QuerySet包含一個或多個model instance,QuerySet類似于Python中的list,list的一些方法QuerySet也有,比如切片,遍歷,
from userex.models import UserEx
type(UserEx.objects)
<class ‘django.db.models.manager.Manager’>
a = UserEx.objects.all()
type(a)
<class ‘django.db.models.query.QuerySet’>
QuerySet是延遲獲取的,只有當用到這個QuerySet時,才會查詢資料庫求值,另外,查詢到的QuerySet又是快取的,當再次使用同一個QuerySet時,并不會再查詢資料庫,而是直接從快取獲取(不過,有一些特殊情況),一般而言,當對一個沒有求值的QuerySet進行的運算,回傳的是QuerySet、ValuesQuerySet、ValuesListQuerySet、Model實體時,一般不會立即查詢資料庫;反之,當回傳的不是這些型別時,會查詢資料庫,下面介紹幾種(并非全部)對QuerySet求值的場景,
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
我們以上面的models為例,
1.1 遍歷
a = Entry.objects.all()
for e in a:
print (e.headline)
當遍歷一開始時,先從資料庫執行查詢select * from Entry得到a,然后再遍歷a,注意:這里只是查詢Entry表,回傳的a的每條記錄只包含Entry表的欄位值,不管Entry的model中是否有onetoone、onetomany、manytomany欄位,都不會關聯查詢,這遵循的是資料庫最少讀寫原則,我們修改一下代碼,如下,遍歷一開始也是先執行查詢得到a,但當執行print (e.blog.name)時,還需要再次查詢資料庫獲取blog物體,
from django.db import connection
l = connection.queries #l是一個串列,記錄SQL陳述句
a = Entry.objects.all()
for e in a:
print (e.blog.name)
len(l)
遍歷時,每次都要查詢資料庫,l長度每次增1,Django提供了方法可以在查詢時回傳關聯表物體,如果是onetoone或onetomany,那用select_related,不過對于onetomany,只能在主表(定義onetomany關系的那個表)的manager中使用select_related方法,即通過select_related獲取的關聯物件是model instance,而不能是QuerySet,如下,e.blog就是model instance,對于onetomany的反向和manytomany,要用prefetch_related,它回傳的是多條關聯記錄,是QuerySet,
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
len(l)
可以看到從開始到結束,l的長度只增加1,另外,通過查詢connection.queries[-1]可以看到Sql陳述句用了join,
1.2 切片 切片不會立即執行,除非顯示指定了步長,如a= Entry.objects.all()[0:10:2],步長為2,
1.3 序列化,即Pickling 序列化QuerySet很少用,
1.4 repr() 和str()功能相似,將物件轉為字串,很少用,
1.5 len() 計算QuerySet元素的數量,并不推薦使用len(),除非QuerySet是求過值的(即evaluated),否則,用QuerySet.count()獲取元素數量,這個效率要高,
1.6 list() 將QuerySet轉為list,
1.7 bool(),判斷是否為空
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
同樣不建議這種方法判斷是否為空,而應該使用QuerySet.exists(),查詢效率高,
- QuerySet的方法
資料庫的常用操作就四種:增、刪、改、查,QuerySet的方法涉及刪、改、查,后面還會講model物件的方法,model方法主要是增、刪、改、還有呼叫model實體的欄位,
2.1 刪delete()
原型:delete() 回傳:None
相當于delete-from-where, delete-from-join-where,先filter,然后對得到的QuerySet執行delete()方法就行了,它會同時洗掉關聯它的那些記錄,比如我洗掉記錄表1中的A記錄,表2中的B記錄中有A的外鍵,那同時也會洗掉B記錄,那ManyToMany關系呢?對于ManyToMany,洗掉其中一方的記錄時,會同時洗掉中間表的記錄,即洗掉雙方的關聯關系,由于有些資料庫,如Sqlite不支持delete與limit連用,所以在這些資料庫對QuerySet的切片執行delete()會出錯,如
a = UserEx.objects.filter(is_active=False)
b = a[:3]
b.delete() #執行時會報錯
解決:UserEx.objects.filter(pk__in=b).delete()
in后面可以是一個QuerySet,見 https://docs.djangoproject.com/en/1.6/ref/models/querysets/#in
2.2 改 update()
批量修改,回傳修改的記錄數,不過update()中的鍵值對的鍵只能是主表中的欄位,不能是關聯表欄位,如下
Entry.objects.update(blog__name='foo') #錯誤,無法修改關聯表欄位,只能修改Entry表的欄位
Entry.objects.filter(blog__name='foo').update(comments_on=False) #正確
最好的方法是先filter,查詢出QuerySet,然后再執行QuerySet.update(),
由于有些資料庫,不支持update與limit連用,所以在這些資料庫對QuerySet的切片執行update()會出錯,
2.3 查詢 filter(kwargs)、exclude(kwargs)、get(**kwargs)
相當于select-from-where,select-from-join-where,很多網站讀資料庫操作最多,可以看到,filter()的引數是變個數的鍵值對,而不會出現>,<,!=等符號,這些符號分別用__gt,__lt,~Q或exclude(),不過對于!=,建議使用Q查詢,更不容易出錯,可以使用雙下劃線對OneToOne、OneToMany、ManyToMany進行關聯查詢和反向關聯查詢,而且方法都是一樣的,如:
Entry.objects.filter(blog__name='Beatles Blog') #限定外鍵表的欄位
下面是反向連接,不過要注意,這里不是entry_set,entry_set是Blog instance的一個屬性,代表某個Blog object
的關聯的所有entry,而QuerySet的方法中反向連接是直接用model的小寫,不要把兩者搞混,It works backwards,
too. To refer to a “reverse” relationship, just use the lowercase name of the model.
Blog.objects.filter(entry__headline__contains='Lennon')
Blog.objects.filter(entry__authors__name='Lennon') #ManyToMany關系,反向連接
myblog = Blog.objects.get(id=1)
Entry.objects.filter(blog=myblog) #正向連接,與下面一句等價,既可以用物體,也可以用
物體的主鍵,其實即使用物體,也是只用物體的主鍵而已,這兩種方式對OneToOne、
OneToMany、ManyToMany的正向、反向連接都適用,
Entry.objects.filter(blog=1) #我個人不建議這樣用,對于create(),不支持這種用法
myentry = Entry.objects.get(id=1)
Blog.objects.filter(entry=myentry) #ManyToMany反向連接,與下面兩種方法等價
Blog.objects.filter(entry=1)
Blog.objects.filter(entry_id=1) #適用于OneToOne和OneToMany的正向連接
OneToOne的關系也是這樣關聯查詢,可以看到,Django對OneToOne、OneToMany、ManyToMany關聯查詢及其反向關聯查詢提供了相同的方式,真是牛逼啊,對于OneToOne、OneToMany的主表,也可以使用下面的方式
Entry.objects.filter(blog_id=1),因為blog_id是資料庫表Entry的一個欄位, 這條陳述句與Entry.objects.filter(blog=blog1)生成的SQL是完全相同的,
與filter類似的還有exclude(**kwargs)方法,這個方法是剔除,相當于select-from-where not,可以使用雙下劃線對OneToOne、OneToMany、ManyToMany進行關聯查詢和反向關聯查詢,方法與filter()中的使用方法相同,
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
轉為SQL為
SELECT * FROM Entry WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
2.4 SQL其它關鍵字在django中的實作
在SQL中,很多關鍵詞在刪、改、查時都是可以用的,如order by、 like、in、join、union、and、or、not等等,我們以查詢為例,說一下django如何映射SQL的這些關鍵字的(查、刪、改中這些關鍵字的使用方法基本相同),
2.4.1 F類(無對應SQL關鍵字)
前面提到的filter/exclude中的查詢引數值都是常量,如果我們想比較model的兩個欄位怎么辦呢?Django也提供了方法,F類,F類實體化時,引數也可以用雙下劃線,也可以邏輯運算,如下
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
Entry.objects.filter(authors__name=F('blog__name'))
2.4.2 Q類(對應and/or/not)
如果有or等邏輯關系呢,那就用Q類,filter中的條件可以是Q物件與非Q查詢混和使用,但不建議這樣做,因為混和查詢時Q物件要放前面,這樣就有難免忘記順序而出錯,所以如果使用Q物件,那就全部用Q物件,Q物件也很簡單,就是把原來filter中的各個條件分別放在一個Q()即可,不過我們還可以使用或與非,分別對應符號為”|”和”&”和”~”,而且這些邏輯操作回傳的還是一個Q物件,另外,逗號是各組條件的基本連接符,也是與的關系,其實可以用&代替(在python manage.py shell測驗過,&代替逗號,執行的SQL是一樣的),不過那樣的話可讀性會很差,這與我們直接寫SQL時,各組條件and時用換行一樣,邏輯清晰,
from django.db.models import Q
Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who') #正確,但不要這樣混用
Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
Q(question__startswith='Who')) #推薦,全部是Q物件
Poll.objects.get( (Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))&
Q(question__startswith='Who')) #與上面陳述句同意,&代替”,”,可讀性差
Q類中時應該可以用F類,待測驗,
2.4.3 annotate(無對應SQL關鍵字)
函式原型annotate(*args, **kwargs)
回傳QuerySet
往每個QuerySet的model instance中加入一個或多個欄位,欄位值只能是聚合函式,因為使用annotate時,會用group by,所以只能用聚合函式,聚合函式可以像filter那樣關聯表,即在聚合函式中,Django對OneToOne、OneToMany、ManyToMany關聯查詢及其反向關聯提供了相同的方式,見下面例子,
from django.contrib.auth.models import User
from django.db.models import Count
計算每個用戶的userjob數量,欄位命名為ut_num,回傳的QuerySet中的每個object都有
這個欄位,在UserJob中定義User為外鍵,在Job中定義與User是ManyToMany
a = User.objects.filter(is_active=True, userjob__is_active=True). annotate(n=Count(‘userjob’)) #一對多反向連接
b = User.objects.filter(is_active=True, job__is_active=True).annotate(n=Count(‘job__name’)) #多對多反向連接,User與Job是多對多
len(a) #這里才會對a求值
len(b) #這里才會對b求值
a對應的SQL陳述句為(SQL中沒有為表起別名,u、ut是我加的):
select auth.user.,Count(ut.id) as ut_num
from auth_user as u left outer join job_userjob as ut on u.id = ut.user_id
where u.is_active=True and ut.is_active=True
group by u.
b對應的SQL陳述句為(SQL中沒有為表起別名,u、t、r是我加的):
select u.,Count(t.name) as n
from auth_user as u
left outer join job_job_users as r on u.id=r.user_id
left outer join job_job as t on r.job_id=t.id
where t.is_active=True and u.is_active=True
group by u.
2.4.4 order_by——對應order by
函式原型 order_by(*fields) 回傳QuerySet
正向的反向關聯表跟filter的方式一樣,如果直接用欄位名,那就是升序asc排列;如果欄位名前加-,就是降序desc
2.4.5 distinct——對應distinct
原型 distinct() 一般與values()、values_list()連用,這時它回傳ValuesQuerySet、ValuesListQuerySet
這個類跟串列很相似,它的每個元素是一個字典,它沒有引數(其實是有引數的,不過,引數只在PostgreSQL上起作用),使用方法為
a=Author.objects.values_list(name).distinct()
b=Author.objects.values_list(name,email).distinct()
對應的SQL分別為
select distinct name from Author
和
select distinct name,email from Author
2.4.6 values()和values_list()——對應‘select 某幾個欄位’
函式原型values(field), values_list(field)
回傳ValuesQuerySet, ValuesListQuerySet
Author.objects.filter(**kwargs)對應的SQL只回傳主表(即Author表)的所有欄位值,即使在查詢時關聯了其它表,關聯表的欄位也不會回傳,只有當我們通過Author instance用關聯表時,Django才會再次查詢資料庫獲取值,當我們不用Author instance的方法,且只想回傳幾個欄位時,就要用values(),它回傳的是一個ValuesQuerySet物件,它類似于一個串列,不過,它的每個元素是字典,而values_list()跟values()相似,它回傳的是一個ValuesListQuerySet,也型別于一個串列,不過它的元素不是字典,而是元組,一般的,當我們不需要model instance的方法且回傳多個欄位時,用values(*field),而回傳單個欄位時用values_list(‘field’,flat=True),這里flat=True是要求每個元素不是元組,而是單個值,見下面例子,而且我們可以回傳關聯表的欄位,用法跟filter中關聯表的方式完全相同,
a = User.objects.values(‘id’,’username’,’userex__age’)
type(a)
<class ‘django.db.models.query.ValuesQuerySet’>
a
[{‘id’:0,’username’:u’test0’,’ userex__age’: 20},{‘id’:1,’username’:u’test1’,’userex__age’: 25},
{‘id’:2,’username’:u’test2’, ’ userex__age’: 28}]
b= User.objects.values_list(’username’,flat=True)
b
[u’test0’, u’test1’ ,u’test2’]
2.4.7 select_related()——對應回傳關聯記錄物體
原型select_related(*filed) 回傳QuerySet
它可以指定回傳哪些關聯表model instance,這里的field跟filter()中的鍵一樣,可以用雙下劃線,但也有不同,You can refer to any ForeignKey or OneToOneField relation in the list of fields passed to select_related(),QuerySet中的元素中的OneToOne關聯及外鍵對應的是都是關聯表的一條記錄,如my_entry=Entry.objects.get(id=1),my_entry.blog就是關聯表的一條記錄的物件,select_related()不能用于OneToMany的反向連接,和ManyToMany,這些都是model的一條記錄對應關聯表中的多條記錄,前面提到了對于a = Author.objects.filter(**kwargs)這類陳述句,對應的SQL只回傳主表,即Author的所有欄位,并不會回傳關聯表欄位值,只有當我們使用關聯表時才會再查資料庫回傳,但有些時候這樣做并不好,看下面兩段代碼,這兩段代碼在1.1中提到過,在代碼1中,在遍歷a前,先執行a對應的SQL,拿到資料后,然后再遍歷a,而遍歷程序中,每次都還要查詢資料庫獲取關聯表,代碼2中,當遍歷開始前,先拿到Entry的QuerySet,并且也拿到這個QuerySet的每個object中的blog物件,這樣遍歷程序中,就不用再查詢資料庫了,這樣就減少了資料庫讀次數,
代碼1
a = Entry.objects.all()
for e in a:
print (e.blog.name)
代碼2
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
2.4.8 prefetch_related(field) ——對應回傳關聯記錄物體的集合
函式原型prefetch_related(field) 回傳的是QuerySet
這里的field跟filter()中的鍵一樣,可以用雙下劃線,用于OneToMany的反向連接,及ManyToMany,其實,prefetch_related()也能做select_related()的事情,但由于策略不同,可能相比select_related()要低效一些,所以建議還是各管各擅長的,select_related是用select ……join來回傳關聯的表欄位,而prefetch_related是用多條SQL陳述句的形式查詢,一般,后一條陳述句用IN來呼叫上一句話回傳的結果,
class Restaurant(models.Model):
pizzas = models.ManyToMany(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
Restaurant.objects.prefetch_related('pizzas__toppings')
Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
先用select_related查到best_pizza物件,再用prefetch_related 從best_pizza查出toppings
2.4.9 extra()——實作復雜的where子句
函式原型:extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
基本上,查詢時用django提供的方法就夠用了,不過有時where子句中包含復雜的邏輯,這種情況下django提供的方法可能不容易做到,還好,django有extra(), extra()中直接寫一些SQL陳述句,不過,不同的資料庫用的SQL有些差異,所以盡可能不要用extra(),需要時再看使用方法吧,
2.4.10 aggregate(*args, kwargs)——對應聚合函式
引數為聚合函式,最好用kwargs的形式,每個引數起一個名字,
該函式與annotate()有何區別呢?annotate相當于aggregate()和group by的結合,對每個group執行aggregate()函式,而單獨的aggregate()并沒有group by,
from django.db.models import Count
q = Blog.objects.aggregate(Count('entry')) #這是用*args的形式,最好不要這樣用
q = Blog.objects.aggregate(number_of_entries=Count('entry')) #這是用**kwargs的形式
{'number_of_entries': 16}
至此,我們總結了QuerySet方法回傳的資料形式,主要有五種,第一種:回傳QuerySet,每個object只包含主表欄位;第二種:回傳QuerySet,每個object除了包含主表所有欄位,還包含某些關聯表的object,這種情況要用select_related()和prefetch_related(),可以是任意深度(即任意多個雙下劃線)的關聯,通常一層關聯和二層關聯用的比較多;第三種:回傳ValuesQuerySet, ValuesListQuerySet,它們的每個元素包含若干主表和關聯表的欄位,不包含任何物體和關聯實體,這種情況要用values()和values_list();第四種:回傳model instance;第五種:單個值,如aggregate()方法,
2.4.11 exists()、count()、len()
如果只是想知道一個QuerySet是否為空,而不想獲取QuerySet中的每個元素,那就用exists(),它要比len()、count()、和直接進行if判斷效率高,如果只想知道一個QuerySet有多大,而不想獲取QuerySet中的每個元素,那就用count();如果已經從資料庫獲取到了QuerySet,那就用len()
2.4.12 contains/startswith/endswith——對應like
欄位名加雙下劃線,除了它,還有icontains,即Case-insensitive contains,這個是大小寫不敏感的,這需要相應資料庫的支持,有些資料庫需要設定
才能支持大小寫敏感,
2.4.13 in——對應in 欄位名加雙下劃線
2.4.14 exclude(field__in=iterable)——對應not in iterable是可迭代物件
2.4.15 gt/gte/lt/lte——對應于>,>=,<,<= 欄位名加雙下劃線
2.4.16 range——對應于between and 欄位名加雙下劃線,range后面值是串列
2.4.17 isnull——對應于is null
Entry.objects.filter(pub_date__isnull=True)對應的SQL為SELECT ... WHERE pub_date IS NULL;
2.4.18 QuerySet切片——對應于limit
QuerySet的索引只能是非負整數,不支持負整數,所以QuerySet[-1]錯誤
a=Entry.objects.all()[5:10]
b=len(a)
執行Entry.objects.all()[5:8],對于不同的資料庫,SQL陳述句不同,Sqlite 的SQL陳述句為select * from tablename limit 3 offset 5; MySQL的SQL陳述句為select * from tablename limit 5,3
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/557105.html
標籤:其他
上一篇:Windows 驅動程式簽名教程(一) --- 開發測驗篇
下一篇:返回列表