在查詢時發生了什么(When QuerySets are evaluated)
QuerySet 可以被構造,過濾,切片,做為參數傳遞,這些行為都不會對數據庫進行操作。只要你查詢的時候才真正的操作數據庫。
下面的 QuerySet 行為會導致執行查詢的操作:
循環(Iteration):QuerySet 是可迭代的,在你遍歷對象時就會執行數據庫操作。例如,打印出所有博文的大標題:
for e in Entry.objects.all(): print(e.headline)
切片(Slicing): QuerySet 是可以用 Python 的數組切片語法完成切片。一般來說對一個未查詢的 QuerySet 切片就返回另一個未查詢的 QuerySet (新 QuerySet 不會被執行)。不過如果你在切片時使用了 "step" 參數,Django 仍會執行數據庫操作並且返回列表對象。對一個查詢過的QuerySet執行切片也會返回列表對象。
序列化/緩存化(Pickling/Caching): 詳情請查看 pickling QuerySets。 這一節所強調的一點是查詢結果是從數據庫中讀取的。
repr(). 調用 QuerySet 的 repr() 方法時,查詢就會被運行。這對於 Python 命令行來說非常方便,你可以使用 API 立即看到查詢結果。
len(). 調用 QuerySet 的 len() 方法,查詢就會被運行。這正如你所料,會返回查詢結果列表的長度。
注意:如果你想得到集合中記錄的數量,就不要使用 QuerySet 的 len() 方法。因為直接在數據庫層面使用 SQL 的 SELECT COUNT(*) 會更加高效,Django 提供了 count() 方法就是這個原因。詳情參閱下面的 count() 方法。
list(). 對 QuerySet 應用 list() 方法,就會運行查詢。例如:
entry_list = list(Entry.objects.all())
要注意地是:使用這個方法會占用大量內存,因為 Django 將列表內容都載入到內存中。做為對比,遍歷 QuerySet 是從數據庫讀取數據,僅在使用某個對象時才將其載入到內容中。
Pickling QuerySets
如果你要 序列化(pickle) 一個 QuerySet,Django 首先就會將查詢對象載入到內存中以完成序列化,這樣你就可以第一時間使用對象(直接從數據庫中讀取數據需要一定時間,這正是緩存所想避免的)。而序列化是緩存化的先行工作,所以在緩存查詢時,首先就會進行序列化工作。這意味着當你反序列化 QuerySet 時,第一時間就會從內存中獲得查詢的結果,而不是從數據庫中查找。
如果你只是想序列化部分必要的信息以便晚些時候可以從數據庫中重建 Queryset ,那只序列化 QuerySet 的 query 屬性即可。接下來你就可以使用下面的代碼重建原來的 QuerySet (這當中沒有數據庫讀取):
>>> import pickle >>> query = pickle.loads(s) # Assuming 's' is the pickled string. >>> qs = MyModel.objects.all() >>> qs.query = query # Restore the original 'query'.
query 屬性是一個不透明的對象。這就意味着它的內部結構並不是公開的。即便如此,對於本節提到的序列化和反序列化來說,它仍是安全和被完全支持的。
查詢API(QuerySet API)
filter(**kwargs)
返回一個新的 QuerySet ,它包含了與所給的篩選條件相匹配的對象。
這些篩選條件(**kwargs)在下面的字段篩選(Field lookups) 中有詳細介紹。多個條件之間在 SQL 語句中是 AND 關系。
exclude(**kwargs)
返回一個新的 QuerySet,它包含那些與所給篩選條件不匹配的對象。
這些篩選條件(**kwargs)也在下面的 字段篩選(Field lookups) 中有詳細描述。多個條件之間在 SQL 語句中也是 AND 關系,但是整體又是一個 NOT() 關系。
下面的例子剔除了出版日期 pub_date 晚於 2005-1-3 並且大標題 headline 是 "Hello" 的所有博文(entry):
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
在 SQL 語句中,這等價於:
SELECT ... WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
下面的例子剔除出版日期 pub_date 晚於 2005-1-3 或者大標題是 "Hello" 的所有博文:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
在 SQL 語句中,這等價於:
SELECT ... WHERE NOT pub_date > '2005-1-3' OR NOT headline = 'Hello'
要注意第二個例子是有很多限制的。
annotate(*args, **kwargs)
我們可以為 QuerySet 中的每個對象添加注解。可以通過計算查詢結果中每個對象所關聯的對象集合,從而得出總計值(也可以是平均值或總和,等等),做為 QuerySet 中對象的注解。annotate() 中的每個參數都會被做為注解添加到 QuerySet 中返回的對象。
Django 提供的注解函式在下面的 (注解函式Aggregation Functions) 有詳細介紹。
注解會使用關鍵字參數來做為注解的別名。其中任何參數都會生成一個別名,它與注解函式的名稱和被注解的 model 相關。
例如,你正在操作一個博客列表,你想知道一個博客究竟有多少篇博文:
>>> 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 model 類本身並沒有定義 entry__count 屬性,但可以使用注解函式的關系字參數,從而改變注解的命名:
>>> 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(*fields)
默認情況下, QuerySet 返回的查詢結果是根據 model 類的 Meta 設置所提供的 ordering 項中定義的排序元組來進行對象排序的。你可以使用 order_by 方法覆蓋之前 QuerySet 中的排序設置。
例如:
Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')
返回結果就會先按照 pub_date 進行升序排序,再按照 headline 進行降序排序。 "-pub_date" 前面的負號"-"表示降序排序。默認是采用升序排序。要隨機排序,就使用 "?",例如:
Entry.objects.order_by('?')
注意:order_by('?') 可能會非常緩慢和消耗過多資源,這取決於你所使用的數據庫。
要根據其他 model 字段排序,所用語法和跨關系查詢的語法相同。就是說,用兩個連續的下划線(__)連接關聯 model 和 要排序的字段名稱, 而且可以一直延伸。例如:
Entry.objects.order_by('blog__name', 'headline')
如果你想對關聯字段排序,在沒有指定 Meta.ordering 的情況下,Django 會采用默認排序設置,就是按照關聯 model 的主鍵進行排序。例如:
Entry.objects.order_by('blog')
等價於:
Entry.objects.order_by('blog__id')
這是因為 Blog model 沒有聲明排序項的原故。
Django 1.7新添加:
# No Join Entry.objects.order_by('blog_id') #可以避免JOIN的代價 # Join Entry.objects.order_by('blog__id')
如果你使用了 distinct() 方法,那么在對關聯字段排序時要格外謹慎。
在 Django 當中是可以按照多值字段(例如 ManyToMany 字段)進行排序的。不過,這個特性雖然先進,但是並不實用。除非是你已經很清楚過濾結果或可用數據中的每個對象,都只有一個相關聯的對象時(就是相當於只是一對一關系時),排序才會符合你預期的結果,所以對多值字段排序時要格外注意。
如果你不想對任何字段排序,也不想使用 model 中原有的排序設置,那么可以調用無參數的 order_by() 方法。
對於排序項是否應該大小寫敏感,Django 並沒有提供設置方法,這完全取決於后端的數據庫對排序大小寫如何處理。
你可以令某個查詢結果是可排序的,也可以是不可排序的,這取決於 QuerySet.ordered 屬性。如果它的值是 True ,那么 QuerySet 就是可排序的。
reverse()
使用 reverse() 方法會對查詢結果進行反向排序。調用兩次 reverse() 方法相當於排序沒發生改過。
要得到查詢結果中最后五個對象,可以這樣寫:
my_queryset.reverse()[:5]
要注意這種方式與 Python 語法中的從尾部切片是完全不一樣的。在上面的例子中,是先得到最后一個元素,然后是倒數第二個,依次處理。但是如果我們有一個 Python 隊列,使用 seq[-5:]時,卻是先得到倒數第五個元素。Django 之所以采用 reverse 來獲取倒數的記錄,而不支持切片的方法,原因就是后者在 SQL 中難以做好。
還有一點要注意,就是 reverse() 方法應該只作用於已定義了排序項 QuerySet (例如,在查詢時使用了order_by()方法,或是在 model 類當中直接定義了排序項). 如果並沒有明確定義排序項,那么調用 QuerySet, calling reverse() 就沒什么實際意義(因為在調用 reverse() 之前,數據沒有定義排序,所以在這之后也不會進行排序。)
distinct()
返回一個新的 QuerySet ,它會在執行 SQL 查詢時使用 SELECT DISTINCT。這意味着返回結果中的重復記錄將被剔除。
默認情況下, QuerySet 並會剔除重復的記錄。在實際當中,這不是什么問題,因為象 Blog.objects.all() 這樣的查詢並不會產生重復的記錄。但是,如果你使用 QuerySet 做多表查詢時,就很可能會產生重復記錄。這時,就可以使用 distinct() 方法。
Note
在 order_by(*fields) 中出現的字段也會包含在 SQL SELECT 列中。如果和 distinct() 同時使用,有時返回的結果卻與預想的不同。這是因為:如果你對跨關系的關聯字段進行排序,這些字段就會被添加到被選取的列中,這就可能產生重復數據(比如,其他的列數據都相同,只是關聯字段的值不同)。但由於 order_by 中的關聯字段並不會出現在返回結果中(他們僅僅是用來實現order),所以有時返回的數據看上去就象是並沒有進行過 distinct 處理一樣。
同樣的原因,如果你用 values() 方法獲得被選取的列,就會發現包含在 order_by() (或是 model 類的 Meta 中設置的排序項)中的字段也包含在里面,就會對返回的結果產生影響。
這章節強調的就是在你使用 distinct() 時,要謹慎對待關聯字段排序。同樣的,在同時使用 distinct() 和 values() 時,如果排序字段並沒有出現在 values() 返回的結果中,那么也要引起注意。
values(*fields)
返回一個 ValuesQuerySet ----一個特殊的 QuerySet ,運行后得到的並不是一系列 model 的實例化對象,而是一個可迭代的字典序列。
每個字典都表示一個對象,而鍵名就是 model 對象的屬性名稱。
下面的例子就對 values() 得到的字典與傳統的 model 對象進行了對比:
# This list contains a Blog object. >>> Blog.objects.filter(name__startswith='Beatles') [<Blog: Beatles Blog>] # This list contains a dictionary. >>> Blog.objects.filter(name__startswith='Beatles').values() [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]
values() 可以接收可選的位置參數,*fields,就是字段的名稱,用來限制 SELECT 選取的數據。如果你指定了字段參數,每個字典就會以 Key-Value 的形式保存你所指定的字段信息;如果沒有指定,每個字典就會包含當前數據表當中的所有字段信息。
例如:
>>> Blog.objects.values() [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}], >>> Blog.objects.values('id', 'name') [{'id': 1, 'name': 'Beatles Blog'}]
下面這些細節值得注意:
如果你有一個名為 foo 的ForeignKey 字段,默認情況下調用 values() 返回的字典中包含鍵名為 foo_id 的字典項,因為它是一個隱含的 model 字段,用來保存關聯對象的主鍵值( foo 屬性用來聯系相關聯的 model )。當你使用 values() 並傳遞字段名稱時, 傳遞foo 或 foo_id 都會得到相同的結果 (字典中的鍵名會自動換成你傳遞的字段名稱)。
例如:
>>> Entry.objects.values() [{'blog_id': 1, 'headline': u'First Entry', ...}, ...] >>> Entry.objects.values('blog') [{'blog': 1}, ...] >>> Entry.objects.values('blog_id') [{'blog_id': 1}, ...]
在 values() 和 distinct() 同時使用時,要注意排序項會影響返回的結果,詳情請查看上面 distinct() 一節。
在values()之后使用defer()和only()是無用的。
ValuesQuerySet 是非常有用的。利用它,你就可以只獲得你所需的那部分數據,而不必同時讀取其他的無用數據。
最后,要提醒的是,ValuesQuerySet 是 QuerySet 的一個子類,所以它擁有 QuerySet 所有的方法。你可以對它調用 filter() 或是 order_by() 以及其他方法。所以下面倆種寫法是等價的:
Blog.objects.values().order_by('id') Blog.objects.order_by('id').values()
Django 的編寫者們更喜歡第二種寫法,就是先寫影響 SQL 的方法,再寫影響輸出的方法(比如例中先寫 order,再寫values ),但這些都無關緊要,完全視你個人喜好而定。
也可以指向一對一、多對多、外鍵關系對象的域:
Blog.objects.values('name', 'entry__headline') [{'name': 'My blog', 'entry__headline': 'An entry'}, {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]
當指向多對多關系時,因為關聯對象可能有很多,所以同一個對象會根據不同的多對多關系返回多次。
values_list(*fields)
它與 values() 非常相似,只不過后者返回的結果是字典序列,而 values() 返回的結果是元組序列。每個元組都包含傳遞給 values_list() 的字段名稱和內容。比如第一項就對應着第一個字段,例如:
>>> Entry.objects.values_list('id', 'headline') [(1, u'First entry'), ...]
如果你傳遞了一個字段做為參數,那么你可以使用 flat 參數。如果它的值是 True,就意味着返回結果都是單獨的值,而不是元組。下面的例子會講得更清楚:
>>> Entry.objects.values_list('id').order_by('id') [(1,), (2,), (3,), ...] >>> Entry.objects.values_list('id', flat=True).order_by('id') [1, 2, 3, ...]
如果傳遞的字段不止一個,使用 flat 就會導致錯誤。
如果你沒給 values_list() 傳遞參數,它就會按照字段在 model 類中定義的順序返回所有的字段。
注意這個方法返回的是 ValuesListQuerySet對象,和列表相似但並不是列表,需要列表操作時需list()轉為列表。
dates(field, kind, order='ASC')
返回一個 DateQuerySet ,就是提取 QuerySet 查詢中所包含的日期,將其組成一個新的 datetime.date 對象的列表。
field 是你的 model 中的 DateField 字段名稱。
kind 是 "year", "month" 或 "day" 之一。 每個 datetime.date對象都會根據所給的 type 進行截減。
"year" 返回所有時間值中非重復的年分列表。
"month" 返回所有時間值中非重復的年/月列表。
"day" 返回所有時間值中非重復的年/月/日列表。
order, 默認是 'ASC',只有兩個取值 '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(field, kind, order='ASC')
返回一個 DateTimeQuerySet ,就是提取 QuerySet 查詢中所包含的日期,將其組成一個新的 datetime.datetime 對象的列表。
none()
返回一個 EmptyQuerySet -- 它是一個運行時只返回空列表的 QuerySet。它經常用在這種場合:你要返回一個空列表,但是調用者卻需要接收一個 QuerySet 對象。(這時,就可以用它代替空列表)
例如:
>>> Entry.objects.none() [] >>> from django.db.models.query import EmptyQuerySet >>> isinstance(Entry.objects.none(), EmptyQuerySet) True
all()
返回當前 QuerySet (或者是傳遞的 QuerySet 子類)的一分拷貝。 這在某些場合是很用的,比如,你想對一個 model manager 或是一個 QuerySet 的查詢結果做進一步的過濾。你就可以調用 all() 獲得一分拷貝以繼續操作,從而保證原 QuerySet 的安全。
當一個QuerySet查詢后,它會緩存查詢結果。如果數據庫發生了改變,就可以調用all()來更新查詢過的QuerySet。
select_related()
返回一個 QuerySet ,它會在執行查詢時自動跟蹤外鍵關系,從而選取所關聯的對象數據。它是一個增效器,雖然會導致較大的數據查詢(有時會非常大),但是接下來再使用外鍵關系獲得關聯對象時,就會不再次讀取數據庫了。
下面的例子展示在獲得關聯對象時,使用 select_related() 和不使用的區別,首先是不使用的例子:
# Hits the database. e = Entry.objects.get(id=5) # Hits the database again to get the related Blog object. b = e.blog
接下來是使用 select_related 的例子:
# Hits the database. e = Entry.objects.select_related().get(id=5) # Doesn't hit the database, because e.blog has been prepopulated # in the previous query. b = e.blog
select_related() 會盡可能地深入遍歷外鍵連接。例如:
from django.db import models class City(models.Model): # ... pass class Person(models.Model): # ... hometown = models.ForeignKey(City) class Book(models.Model): # ... author = models.ForeignKey(Person)
接下來調用 Book.objects.select_related().get(id=4) 將緩存關聯的 Person 和 City:
b = Book.objects.select_related('person__city').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.
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處理他們之間的關系。
例如:
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().prefetch_related('toppings')
可以聯合查詢:
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.prefetch_related('best_pizza__toppings') >>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
有些情況下,Django 的查詢語法難以簡練地表達復雜的 WHERE 子句。對於這種情況,Django 提供了 extra() QuerySet 修改機制,它能在QuerySet 生成的 SQL 從句中注入新子句。
由於產品差異的原因,這些自定義的查詢難以保障在不同的數據庫之間兼容(因為你手寫 SQL 代碼的原因),而且違背了 DRY 原則,所以如非必要,還是盡量避免寫 extra。
在 extra 可以指定一個或多個 params 參數,如 select,where 或 tables。所有參數都是可選的,但你至少要使用一個。
select
select 參數可以讓你在 SELECT 從句中添加其他字段信息。它應該是一個字典,存放着屬性名到 SQL 從句的映射。
例如:
Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
結果中每個 Entry 對象都有一個額外的 is_recent 屬性,它是一個布爾值,表示 pub_date 是否晚於2006年1月1號。
Django 會直接在 SELECT 中加入對應的 SQL 片斷,所以轉換后的 SQL 如下:
SELECT blog_entry.*, (pub_date > '2006-01-01') FROM blog_entry;
下面這個例子更復雜一些;它會在每個 Blog 對象中添加一個 entry_count 屬性,它會運行一個子查詢,得到相關聯的 Entry 對象的數量:
Blog.objects.extra( select={ 'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id' }, )
(在上面這個特例中,我們要了解這個事實,就是 blog_blog 表已經存在於 FROM 從句中。)
翻譯成 SQL 如下:
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count FROM blog_blog;
要注意的是,大多數數據庫需要在子句兩端添加括號,而在 Django 的 select 從句中卻無須這樣。同樣要引起注意的是,在某些數據庫中,比如某些 MySQL 版本,是不支持子查詢的。
某些時候,你可能想給 extra(select=...) 中的 SQL 語句傳遞參數,這時就可以使用 select_params 參數。因為 select_params 是一個隊列,而 select 屬性是一個字典,所以兩者在匹配時應正確地一一對應。在這種情況下中,你應該使用 django.utils.datastructures.SortedDict 匹配 select 的值,而不是使用一般的 Python 隊列。
例如:
Blog.objects.extra( select=SortedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two'))
在使用 extra() 時要避免在 select 字串含有 "%%s" 子串, 這是因為在 Django 中,處理 select 字串時查找的是 %s 而並非轉義后的 % 字符。所以如果對 % 進行了轉義,反而得不到正確的結果。
where / tables
你可以使用 where 參數顯示定義 SQL 中的 WHERE 從句,有時也可以運行非顯式地連接。你還可以使用 tables 手動地給 SQL FROM 從句添加其他表。
where 和 tables 都接受字符串列表做為參數。所有的 where 參數彼此之間都是 "AND" 關系。
例如:
Entry.objects.extra(where=['id IN (3, 4, 5, 20)'])
大致可以翻譯為如下的 SQL:
SELECT * FROM blog_entry WHERE id IN (3, 4, 5, 20);
在使用 tables 時,如果你指定的表在查詢中已出現過,那么要格外小心。當你通過 tables 參數添加其他數據表時,如果這個表已經被包含在查詢中,那么 Django 就會認為你想再一次包含這個表。這就導致了一個問題:由於重復出現多次的表會被賦予一個別名,所以除了第一次之外,每個重復的表名都會分別由 Django 分配一個別名。所以,如果你同時使用了 where 參數,在其中用到了某個重復表,卻不知它的別名,那么就會導致錯誤。
一般情況下,你只會添加一個未在查詢中出現的新表。但是如果上面所提到的特殊情況發生了,那么可以采用如下措施解決。首先,判斷是否有必要要出現重復的表,能否將重復的表去掉。如果這點行不通,就試着把 extra() 調用放在查詢結構的起始處,因為首次出現的表名不會被重命名,所以可能能解決問題。如果這也不行,那就查看生成的 SQL 語句,從中找出各個數據庫的別名,然后依此重寫 where 參數,因為只要你每次都用同樣的方式調用查詢(queryset),表的別名都不會發生變化。所以你可以直接使用表的別名來構造 where。
order_by
如果你已通過 extra() 添加了新字段或是數據庫,此時若想對新字段進行排序,就可以給 extra() 中的 order_by 參數傳遞一個排序字符串序列。字符串可以是 model 原生的字段名(與使用普通的 order_by() 方法一樣),也可以是 table_name.column_name 這種形式,或者是你在 extra() 的 select 中所定義的字段。
例如:
q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) q = q.extra(order_by = ['-is_recent'])
這段代碼按照 is_recent 對記錄進行排序,字段值是 True 的排在前面,False 的排在后面。(True 在降序排序時是排在 False 的前面)。
順便說一下,上面這段代碼同時也展示出,可以依你所願的那樣多次調用 extra() 操作(每次添加新的語句結構即可)。
params
上面提到的 where 參數還可以用標准的 Python 占位符 -- '%s' ,它可以根據數據庫引擎自動決定是否添加引號。 params 參數是用來替換占位符的字符串列表。
例如:
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
使用 params 替換 where 的中嵌入值是一個非常好的做法,這是因為 params 可以根據你的數據庫判斷要不要給傳入值添加引號(例如,傳入值中的引號會被自動轉義)。
不好的用法:
Entry.objects.extra(where=["headline='Lennon'"])
優雅的用法:
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
defer(*fields)
在某些數據復雜的環境下,你的 model 可能包含非常多的字段,可能某些字段包含非常多的數據(比如,文檔字段),或者將其轉化為 Python 對象會消耗非常多的資源。在這種情況下,有時你可能並不需要這種字段的信息,那么你可以讓 Django 不讀取它們的數據。
將不想載入的字段的名稱傳給 defer() 方法,就可以做到延后載入:
Entry.objects.defer("lede", "body")
延后截入字段的查詢返回的仍是 model 類的實例。在你訪問延后載入字段時,你仍可以獲得字段的內容,所不同的是,內容是在你訪問延后字段時才讀取數據庫的,而普通字段是在運行查詢(queryset)時就一次性從數據庫中讀取數據的。
你可以多次調用 defer() 方法。每個調用都可以添加新的延后載入字段:
# Defers both the body and lede fields. Entry.objects.defer("body").filter(headline="Lennon").defer("lede")
對延后載入字段進行排序是不會起作用的;重復添加延后載入字段也不會有何不良影響。
你也可以延后載入關聯 model 中的字段(前提是你使用 select_related() 載入了關聯 model),用法也是用雙下划線連接關聯字段:
Blog.objects.select_related().defer("entry__lede", "entry__body")
如果你想清除延后載入的設置,只要使用將 None 做為參數傳給 defer() 即可:
# Load all fields immediately. my_queryset.defer(None)
有些字段無論你如何指定,都不會被延后加載。比如,你永遠不能延后加載主鍵字段。如果你使用 select_related() 獲得關聯 model 字段信息,那么你就不能延后載入關聯 model 的主鍵。(如果這樣做了,雖然不會拋出錯誤,事實上卻不完成延后加載)
Note
defer() 方法(和隨后提到的 only() 方法) 都只適用於特定情況下的高級屬性。它們可以提供性能上的優化,不過前提是你已經對你用到的查詢有過很深入細致的分析,非常清楚你需要的究竟是哪些信息,而且已經對你所需要的數據和默認情況下返回的所有數據進行比對,清楚兩者之間的差異。這完成了上述工作之后,再使用這兩種方法進行優化才是有意義的。所以當你剛開始構建你的應用時,先不要急着使用 defer() 方法,等你已經寫完查詢並且分析成哪些方面是熱點應用以后,再用也不遲。
only(*fields)
only() 方法或多或少與 defer() 的作用相反。如果你在提取數據時希望某個字段不應該被延后載入,而應該立即載入,那么你就可以做使用 only() 方法。如果你一個 model ,你希望它所有的字段都延后加載,只有某幾個字段是立即載入的,那么你就應該使用 only() 方法。
如果你有一個 model,它有 name, age 和 biography 三個字段,那么下面兩種寫法效果一樣的:
Person.objects.defer("age", "biography") Person.objects.only("name")
你無論何時調用 only(),它都會立刻更改載入設置。這與它的命名非常相符:只有 only 中的字段會立即載入,而其他的則都是延后載入的。因此,連續調用 only() 時,只有最后一個 only 方法才會生效:
# This will defer all fields except the headline. Entry.objects.only("body", "lede").only("headline")
由於 defer() 可以遞增(每次都添加字段到延后載入的列表中),所以你可以將 only() 和 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")
using(alias)
使用多個數據庫時使用,參數是數據庫的alias
# queries the database with the 'default' alias. >>> Entry.objects.all() # queries the database with the 'backup' alias >>> Entry.objects.using('backup')
select_for_update(nowait=False)
返回queryset,並將需要更新的行鎖定,類似於SELECT ... FOR UPDATE的操作。
entries = Entry.objects.select_for_update().filter(author=request.user)
所有匹配的entries都會被鎖定直到此次事務結束。
raw(raw_query, params=None, translations=None)
執行raw SQL queries
不返回查詢的方法(QuerySet methods that do not return QuerySets)
下面所列的 QuerySet 方法作用於 QuerySet,卻並不返回 other than a QuerySet。
這些方法並不使用緩存(請查看 緩存與查詢(Caching and QuerySets))。所以它們在運行時是立即讀取數據庫的。
get(**kwargs)
返回與所給的篩選條件相匹配的對象,篩選條件在 字段篩選條件(Field lookups) 一節中有詳細介紹。
在使用 get() 時,如果符合篩選條件的對象超過一個,就會拋出 MultipleObjectsReturned 異常。MultipleObjectsReturned 是 model 類的一個屬性。
在使用 get() 時,如果沒有找到符合篩選條件的對象,就會拋出 DoesNotExist 異常。這個異常也是 model 對象的一個屬性。例如:
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.")
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 當成一個新對象來創建。一般情況下,你不必擔心這一點,但是如果你的 model 的主鍵是你手動指定的,而且它的值已經在數據庫中存在,那么調用 create() 就會失敗,並拋出 IntegrityError。這是因為主鍵值必須是唯一的。所以當你手動指定主鍵時,記得要做好處理異常的准備。
get_or_create(defaults=None,**kwargs)
這是一個方便實際應用的方法,它根據所給的篩選條件查詢對象,如果對象不存在就創建一個新對象。
它返回的是一個 (object, created) 元組,其中的 object 是所讀取或是創建的對象,而 created 則是一個布爾值,它表示前面提到的 object 是否是新創建的。
這意味着它可以有效地減少代碼,並且對編寫數據導入腳本非常有用。例如:
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()
上面的代碼會隨着 model 中字段數量的激增而變得愈發庸腫。接下來用 get_or_create() 重寫:
obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)})
在這里要注意 defaults 是一個字典,它僅適用於創建對象時為字段賦值,而並不適用於查找已存在的對象。 get_or_create() 所接收的關鍵字參數都會在調用 get() 時被使用,有一個參數例外,就是 defaults。在使用get_or_create() 時如果找到了對象,就會返回這個對象和 False。如果沒有找到,就會實例化一個新對象,並將其保存;同時返回這個新對象和 True。創建新對象的步驟大致如下:
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) params.update(defaults) obj = self.model(**params) obj.save()
用自然語言描述:從非 'defaults' 關鍵字參數中排除含有雙下划線的參數(因為雙下划線表示非精確查詢),然后再添加 defaults 字典的內容,如果鍵名與已有的關鍵字參數重復,就以 defaults 中的內容為准, 然后將整理后的關鍵字參數傳遞給 model 類。當然,這只是算法的簡化描述,實際上對很多細節沒有提及,比如對異常和邊界條件的處理。如果你對此感興趣,不妨看一下原代碼。
如果你的 model 恰巧有一個字段,名稱正是 defaults,而且你想在 get_or_create() 中用它做為精確查詢的條件, 就得使用 'defaults__exact' (之前提過 defaults 只能在創建時對對象賦值,而不能進行查詢),象下面這樣:
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
如果你手動指定了主鍵,那么使用 get_or_create() 方法時也會象 create() 一樣,拋出類似的異常。當你手動指定了主鍵,若主鍵值已經在數據庫中存在,就會拋出一個 IntegrityError 異常。
最后提一下在 Django 視圖(views)中使用 get_or_create() 時要注意的一點。如上所說,對於在腳本中分析數據和添加新數據而言,get_or_create() 是非常有用的。但是如果你是在視圖中使用 get_or_create() ,那么就要格外留意,要確認是在 POST 請求中使用,除非你有很必要和很充分的理由才不這么做。而在 GET 請求中使用的話,不會對數據產生任何作用。而使用 POST 的話,每個發往頁面的請求都會對數據有一定的副作用。
Note
通過多對多關系使用時要注意:
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 = 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相關聯的Chapter就不會被查找到了。
update_or_create(defaults=None, **kwargs)
與上面類似
try: obj = Person.objects.get(first_name='John', last_name='Lennon') for key, value in updated_values.iteritems(): setattr(obj, key, value) obj.save() except Person.DoesNotExist: updated_values.update({'first_name': 'John', 'last_name': 'Lennon'}) obj = Person(**updated_values) obj.save()
可以簡寫為:
obj, created = Person.objects.update_or_create( first_name='John', last_name='Lennon', defaults=updated_values)
bulk_create(objs, batch_size=None)
批量創建
>>> Entry.objects.bulk_create([ ... Entry(headline="Django 1.0 Released"), ... Entry(headline="Django 1.1 Announced"), ... Entry(headline="Breaking: Django is awesome") ... ])
優於:
Entry.objects.create(headline="Python 1.0 Released") Entry.objects.create(headline="Python 1.1 Planned")
count()
返回數據庫中匹配查詢(QuerySet)的對象數量。 count() 不會拋出任何異常。
例如:
# Returns the total number of entries in the database. Entry.objects.count() # Returns the number of entries whose headline contains 'Lennon' Entry.objects.filter(headline__contains='Lennon').count()
count() 會在后端執行 SELECT COUNT(*) 操作,所以你應該盡量使用 count() 而不是對返回的查詢結果使用 len() 。
根據你所使用的數據庫(例如 PostgreSQL 和 MySQL),count() 可能會返回長整型,而不是普通的 Python 整數。這確實是一個很古怪的舉措,沒有什么實際意義。
in_bulk(id_list)
接收一個主鍵值列表,然后根據每個主鍵值所其對應的對象,返回一個主鍵值與對象的映射字典。
>>> 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([]) {}
如果你給 in_bulk() 傳遞的是一個空列表明,得到就是一個空字典。
iterator()
運行查詢(QuerySet),然后根據結果返回一個 迭代器(iterator。 做為比較,使用 QuerySet 時,從數據庫中讀取所有記錄后,一次性將所有記錄實例化為對應的對象;而 iterator() 則是讀取記錄后,是分多次對數據實例化,用到哪個對象才實例化哪個對象。相對於一次性返回很多對象的 QuerySet,使用迭代器不僅效率更高,而且更節省內存。
要注意的是,如果將 iterator() 作用於 QuerySet,那就意味着會再一次運行查詢,就是說會運行兩次查詢。
latest(field_name=None)
根據時間字段 field_name 得到最新的對象。
下面這個例子根據 pub_date 字段得到數據表中最新的 Entry 對象:
Entry.objects.latest('pub_date')
如果你在 model 中 Meta 定義了 get_latest_by 項, 那么你可以略去 field_name 參數。Django 會將 get_latest_by 做為默認設置。
和 get(), latest() 一樣,如果根據所給條件沒有找到匹配的對象,就會拋出 DoesNotExist 異常。
注意 latest()和earliest() 是純粹為了易用易讀而存在的方法。
earliest(field_name=None)
類似於latest()
first()
p = Article.objects.order_by('title', 'pub_date').first()
相當於:
try: p = Article.objects.order_by('title', 'pub_date')[0] except IndexError: p = None
last()
類似於first()
aggregate(*args, **kwargs)
通過對 QuerySet 進行計算,返回一個聚合值的字典。 aggregate() 中每個參數都指定一個包含在字典中的返回值。
聚合使用關鍵字參數做為注解的名稱。每個參數都有一個為其訂做的名稱,這個名稱取決於聚合函式的函數名和聚合字段的名稱。
例如,你正在處理博文,你想知道博客中一共有多少篇博文:
>>> from django.db.models import Count >>> q = Blog.objects.aggregate(Count('entry')) {'entry__count': 16}
通過在 aggregate 指定關鍵字參數,你可以控制返回的聚合名稱:
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) {'number_of_entries': 16}
exists()
如果 QuerySet 包含有數據,就返回 True 否則就返回 False。這可能是最快最簡單的查詢方法了.
update()
更新
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')
delete()
刪除
>>> b = Blog.objects.get(pk=1) # Delete all the entries belonging to this Blog. >>> Entry.objects.filter(blog=b).delete()
字段篩選條件(Field lookups)
字段篩選條件決定了你如何構造 SQL 語句中的 WHERE 從句。它們被指定為 QuerySet 中 filter(),exclude() 和 get() 方法的關鍵字參數。
exact
精確匹配。如果指定的值是 None,就會翻譯成 SQL 中的 NULL (詳情請查看 isnull )。
例如:
Entry.objects.get(id__exact=14) Entry.objects.get(id__exact=None)
等價的 SQL:
SELECT ... WHERE id = 14; SELECT ... WHERE id IS NULL;
iexact
忽略大小寫的匹配。
例如:
Blog.objects.get(name__iexact='beatles blog') Blog.objects.get(name__iexact=None)
等價於如下 SQL :
SELECT ... WHERE name ILIKE 'beatles blog'; SELECT ... WHERE name IS NULL;
SQLite 用戶要注意
在使用 SQLite 作為數據庫,並且應用 Unicode (non-ASCII) 字符串時,請先查看 database note 中關於字符串比對那一節內容。SQLite 對 Unicode 字符串,無法做忽略大小寫的匹配。
contains
大小寫敏感的包含匹配。
例如:
Entry.objects.get(headline__contains='Lennon')
等價於 SQL :
SELECT ... WHERE headline LIKE '%Lennon%';
要注意,上述語句將匹配大標題 'Today Lennon honored' ,但不能匹配 'today lennon honored'。
SQLite 不支持大小寫敏感的 LIKE 語句;所以對 SQLite 使用 contains 時就和使用 icontains 一樣。
icontains
忽略大小寫的包含匹配。
例如:
Entry.objects.get(headline__icontains='Lennon')
等價於 SQL:
SELECT ... WHERE headline ILIKE '%Lennon%';
SQLite 用戶請注意
使用 SQLite 數據庫並應用 Unicode (non-ASCII) 字符串時,請先查看 database note 文檔中關於字符串比對那一節內容。
in
是否在一個給定的列表中。
例如:
Entry.objects.filter(id__in=[1, 3, 4])
等價於 SQL:
SELECT ... WHERE id IN (1, 3, 4);
你也可以把查詢(queryset)結果當做動態的列表,從而代替固定的列表:
inner_qs = Blog.objects.filter(name__contains='Cheddar') entries = Entry.objects.filter(blog__in=inner_qs)
做動態列表的 queryset 運行時就會被做為一個子查詢:
SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
如果你傳遞了一個 ValuesQuerySet 或 ValuesListQuerySet (它們是調用查詢集上 values() 和 values_list() 方法的返回結果) 做為 __in 條件的值,那么你要確認只匹配返回結果中的一個字段。例如,下面的代碼能正常的工作(對博客名稱進行過濾):
inner_qs = Blog.objects.filter(name__contains='Ch').values('name') entries = Entry.objects.filter(blog__name__in=inner_qs)
下面的代碼卻會拋出異常,原因是內部的查詢會嘗試匹配兩個字段值,但只有一個是有用的:
# Bad code! Will raise a TypeError. inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id') entries = Entry.objects.filter(blog__name__in=inner_qs)
warning
query 屬性本是一個不公開的內部屬性,雖然他在上面的代碼中工作得很好,但是它的API很可能會在不同的 Django 版本中經常變動。
性能考慮
要謹慎使用嵌套查詢,並且要對你所采用的數據庫性能有所了解(如果不了解,就去做一下性能測試)。有些數據庫,比如著名的MySQL,就不能很好地優化嵌套查詢。所以在上面的案例中,先在第一個查詢中提取值列表,然后再將其傳遞給第二個查詢,會對性能有較高的提升。說白了,就是用兩個高效的查詢替換掉一個低效的查詢:
values = Blog.objects.filter( name__contains='Cheddar').values_list('pk', flat=True) entries = Entry.objects.filter(blog__in=list(values))
gt
大於。
gte
大於等於。
lt
小於。
lte
小於等於。
startswith
大小寫敏感的以....開頭。
istartswith
忽略大小寫的以....開頭。
endswith
大小寫敏感的以....結尾。
iendswith
忽略大小寫的以....結尾。
range
包含的范圍。
例如:
import datetime start_date = datetime.date(2005, 1, 1) end_date = datetime.date(2005, 3, 31) Entry.objects.filter(pub_date__range=(start_date, end_date))
等價於 SQL:
SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
你可以把 range 當成 SQL 中的 BETWEEN 來用,比如日期,數字,甚至是字符。
year
對日期/時間字段精確匹配年分,年分用四位數字表示。
例如:
Entry.objects.filter(pub_date__year=2005)
等價於 SQL:
SELECT ... WHERE EXTRACT('year' FROM pub_date) = '2005';
(不同的數據庫引擎中,翻譯得到的 SQL 也不盡相同。)
month
對日期/時間字段精確匹配月分,用整數表示月分,比如 1 表示一月,12 表示十二月。
day
對日期/時間字段精確匹配日期。
要注意的是,這個匹配只會得到所有 pub_date 字段內容是表示 某月的第三天 的記錄,如一月三號,六月三號。而十月二十三號就不在此列。
week_day
對日期/時間字段匹配星期幾
例如:
Entry.objects.filter(pub_date__week_day=2)
等價於 SQL:
SELECT ... WHERE EXTRACT('dow' FROM pub_date) = '2';
(不同的數據庫引擎中,翻譯得到的 SQL 也不盡相同。)
要注意的是,這段代碼將得到 pub_date 字段是星期一的所有記錄 (西方習慣於將星期一看做一周的第二天),與它的年月信息無關。星期以星期天做為第一天,以星期六做為最后一天。
hour
minute
second
isnull
根據 SQL 查詢是空 IS NULL 還是非空 IS NOT NULL,返回相應的 True 或 False。
例如:
Entry.objects.filter(pub_date__isnull=True)
等價於 SQL:
SELECT ... WHERE pub_date IS NULL;
search
利用全文索引做全文搜索。它與 contains 相似,但使用全文索引做搜索會更快一些。
例如:
Entry.objects.filter(headline__search="+Django -jazz Python")
等價於:
SELECT ... WHERE MATCH(tablename, headline) AGAINST (+Django -jazz Python IN BOOLEAN MODE);
要注意這個方法僅適用於 MySQL ,並且要求設置全文索引。默認情況下 Django 使用 BOOLEAN MODE 模式。詳見 Please check MySQL documentation for additional details.
regex
大小寫敏感的正則表達式匹配。
它要求數據庫支持正則表達式語法,而 SQLite 卻沒有內建正則表達式支持,因此 SQLite 的這個特性是由一個名為 REGEXP 的 Python 方法實現的,所以要用到 Python 的正則庫 re.
例如:
Entry.objects.get(title__regex=r'^(An?|The) +')
等價於 SQL:
SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'c'); -- Oracle SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite
建議使用原生字符串 (例如,用 r'foo' 替換 'foo') 做為正則表達式。
iregex
忽略大小寫的正則表達式匹配。
聚合函式(Aggregation Functions)
Avg
返回所給字段的平均值。
默認別名:<field>__avg
返回類型: float
Count
根據所給的關聯字段返回被關聯 model 的數量。
默認別名: <field>__count
返回類型: int
它有一個可選參數:
distinct
如果 distinct=True,那么只返回不重復的實例數量,相當於 SQL 中的 COUNT(DISTINCT field)。默認值是 False。
Max
默認別名: <field>__max
返回類型: 與所給字段值相同
Min
返回所給字段的最小值。
默認別名: <field>__min
返回類型: 與所給字段相同
StdDev
返回所給字段值的標准差。
默認別名: <field>__stddev
返回類型: float
它有一個可選參數:
sample
默認情況下, StdDev 返回一個總體偏差值,但是如果 sample=True,則返回一個樣本偏差值。
SQLite
SQLite 本身並不提供 StdDev 支持,可以使用 SQLite 的外置模塊實現這個功能。詳情請查看相應的 SQLite 文檔,了解如何獲得和安裝擴展。
Sum
計算所給字段值的總和
默認別名: <field>__sum
返回類型: 與所給字段相同
Variance
返回所給字段值的標准方差。
默認別名: <field>__variance
返回類型: float
它有一個可選參數:
sample
默認情況下, Variance 返回的是總體方差;如果 sample=True,返回的則是樣式方差。