django相關知識

来源:https://www.cnblogs.com/HeroZhang/archive/2023/07/12/17547771.html
-Advertisement-
Play Games

# Django ## select_related 和 prefetch_related 函數 對 QuerySet 查詢的優化 在資料庫有外鍵的時候,使用 select_related() 和 prefetch_related() 能夠很好的減小資料庫請求的次數,從而提升性能。本文經過一個簡單的 ...


Django

對 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失效,只保留最後一個。

對於多對多欄位(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。

  1. 一些實例
    選擇哪一個函數
    若是咱們想要得到全部家鄉是湖北的人,最無腦的作法是先得到湖北省,再得到湖北的全部城市,最後得到故鄉是這個城市的人。就像這樣:
 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查詢:
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);

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"湖北省"))
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.hometown_id = QSOptimize_city.id)
INNER JOIN QSOptimize_province ON (QSOptimize_city.province_id = QSOptimize_province.id)
WHERE QSOptimize_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查詢
SELECT QSOptimize_order.id, QSOptimize_order.customer_id, QSOptimize_order.orderinfo, QSOptimize_order.time
FROM QSOptimize_order
WHERE QSOptimize_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查詢:
SELECT QSOptimize_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
FROM QSOptimize_order
INNER JOIN QSOptimize_person ON (QSOptimize_order.customer_id = QSOptimize_person.id)
WHERE QSOptimize_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 = 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 = 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 = 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 = Data.objects.filter(name__contains='game')
number3 = data.len()
以上三個例子中,不考慮別的因素下,number1和number3都是比較好的,number2就儘量別考慮了。
有時候後端返回數據量較大,會大量占用記憶體(緩存)。我們可以使用values和value_list方法按需提取數據。比如,我們只要數據里的name,而不用其它的信息,諸如:性別,年齡之類的,那麼我們就可以使用values和value_list方法。

values()

data = 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 = 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 = 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 = Data.objects.get(id=1)
data.name = "gamebox"
data.save()

update()

data = 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查詢

  1. filter() filter(kwargs)
    返回滿足查詢參數的對象集合。查找的參數(
    kwargs)應該滿足下文欄位查找中的格式。多個參數之間是和AND的關係。
  2. 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')
  3. 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

  1. 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'}, ...]>

  1. 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,)]>

  1. 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)]

  1. 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參數定義在截取之前將數據時間轉換到的時區。
  2. none() 調用none()將創建一個不返回任何對象的查詢集,並且在訪問結果時不會執行任何查詢。
    例子:

Entry.objects.none()
<QuerySet []>
from django.db.models.query import EmptyQuerySet
isinstance(Entry.objects.none(), EmptyQuerySet)
True

  1. all() 返回當前QuerySet(或QuerySet子類)的副本。通常用於獲取全部QuerySet對象。
  2. union() union(*other_qs, all=False) 集合中並集
    使用SQL的UNION運算符組合兩個或更多個QuerySet的結果。例如:

qs1.union(qs2, qs3)
預設情況下,UNION操作符僅選擇不同的值。 要允許重覆值,請使用all=True參數。

  1. intersection() intersection(*other_qs) 集合中交集
    使用SQL的INTERSECT運算符返回兩個或更多個QuerySet的共有元素。例如:

qs1.intersection(qs2, qs3)

  1. difference() difference(*other_qs) 集合中差集
    使用SQL的EXCEPT運算符只保留QuerySet中的元素,但不保留其他QuerySet中的元素。例如:

qs1.difference(qs2, qs3)

  1. 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.ob

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、表結構設計 二、實現思路 1.系統啟動時將字典數據載入到redis作為可選常量池,以及mapper.xml、dao、數據規則信息載入到redis 2.用訪問時通過springmvc攔截器對用戶進行攔截獲取token然後通過RSA解密獲取用戶信息,將用戶信息,以及請求參數加入本地線程 3.myba ...
  • 相信熟悉 Golang 的小伙伴不少都知道 `條件編譯` 這個事,最近項目中也可能會用到這個東西。所以特意重新學習下,記錄下學習的過程。這樣用的時候記不住了,還可以直接過來看自己的筆記。 > 文章很多內容來源於參考資料,感謝。 ### 1、條件編譯簡介 #### 1.1、為什麼需要條件編譯 在實際的 ...
  • # Java 封裝 # 1. 封裝 ## 封裝就是把抽象的數據和方法進行封裝,數據在內部通過授權的方法來進行數據的操作,例如遙控器通過按鈕對電視機的操作。 ## 封裝的好處: > ## 隱藏實現細節 > > ## 校驗並保護數據的安全 ## 封裝的三步驟: > ## 對屬性進行私有化 > > ## ...
  • # 一、vue複習 ## 1.vue的使用步驟: (1)導入vue.js (2)創建除body以外最大的div標簽,給定id值 (3)創建vue對象 ```bash new Vue({ el:"#app", data:{}//定義變數 methods:{} //定義方法 ``` ## 2.vue語法 ...
  • Docker提供了一個名為**Docker Desktop**的桌面應用程式,簡化了安裝和設置過程。還有另一個選項可以使用**Docker引擎**進行安裝。 - [Docker Desktop網站](https://www.docker.com/products/docker-desktop/) - ...
  • 使用easyExcel在導入數據事有很好的使用性,方便操作。 添加依賴: <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.5</version> </depende ...
  • 本文旨在簡明扼要說明各回收器調優參數,如有疏漏歡迎指正。 #### 1、JDK版本 以下所有優化全部基於JDK8版本,強烈建議低版本升級到JDK8,並儘可能使用update_191以後版本。 #### 2、如何選擇垃圾回收器 響應優先應用:面向C端對響應時間敏感的應用,堆記憶體8G以上建議選擇G1,堆 ...
  • # 文件的上傳和下載 在上網的時候我們常常遇到文件上傳的情況,例如上傳頭像、上傳資料等:當然除了上傳,遇見下載的情況 也很多,接下來看看我們servlet中怎麼實現文件的上傳和下載。 ## 文件上傳 文件上傳涉及到前臺頁面的編寫和後臺伺服器端代碼的編寫,前臺發送文件,後臺接收並保存文件,這才是 一個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...