Django資料庫查詢相關
一旦資料模型建立完畢,自然會有存取資料的需要.
本文件介紹了由models衍生而來的資料庫抽象API,及如何建立,得到及更新物件.
貫穿本參考, 我們都會引用下面的民意測驗(Poll)應用程式:
class Poll(models.Model):
slug = models.SlugField(unique_for_month='pub_date')
question = models.CharField(maxlength=255)
pub_date = models.DateTimeField()
expire_date = models.DateTimeField()
def __repr__(self):
return self.question
class Meta:
get_latest_by = 'pub_date'
class Choice(models.Model):
poll = models.ForeignKey(Poll, edit_inline=models.TABULAR,
num_in_admin=10, min_num_in_admin=5)
choice = models.CharField(maxlength=255, core=True)
votes = models.IntegerField(editable=False, default=0)
def __repr__(self):
return self.choice
及下面的簡單會話:
>>> from datetime import datetime
>>> p1 = Poll(slug='whatsup', question="What's up?",
... pub_date=datetime(2005, 2, 20), expire_date=datetime(2005, 4, 20))
>>> p1.save()
>>> p2 = Poll(slug='name', question="What's your name?", ... pub_date=datetime(2005, 3, 20), expire_date=datetime(2005, 3, 25))
>>> p2.save()
>>> Poll.objects.all()
[What's up?, What's your name?]
Django 的資料查詢基於構建結果集及對結果集進行取值. 結果集是獨立於資料庫的符合某個查詢條件的一組資料物件的集合.這是一個惰性集合:在對該集合取值之前,無法知道該集合有哪些成員.
要生成一個滿足你需求的結果集,首先要得到一個描述給定型別的所有物件的初始結果集.這個初始結果集可以通過一系列函式進行更精細的優化處理.當經 過處理後的結果集符合你的要求時, 就可以對它進行取值操作(使用迭代操作,slicing操作,或一系列其它技術), 以得到一個你需要的物件或物件的列表.
每個 Django model 都有一個與生俱來的管理器物件 objects, 管理器最重要的角色就是作為初始結果的來源. 一個管理器就是一個描述給定型別所有物件的特殊的初始結果集. Poll.objects 就是包含所有 Poll 物件的一個初始結果集. 它唯一特殊之處在於它不能被取值. 要克服此限制, 管理器物件有一個 all() 方法. 該方法生成一個 可以 被取值的初始結果集的拷貝:
all_polls = Poll.objects.all()
參閱 Model API 的 Managers 小節以瞭解管理器的定位及建立細節.
管理器提供的初始結果集描述了給定型別的所有物件.不過通常你只需要這個物件集合中的一小部分(一個子集).
要生這樣一個結果集,你需要對初始結果集進行優化定製處理, 增加一些限制條件直到描述的子集滿足你的需要.最常用的兩個定製結果集的方法是:
filter(**kwargs)
返回一個匹配查詢引數的新的結果集.
exclude(**kwargs)
返回一個不匹配查詢引數的新的結果集.
引數格式在下面 "欄位查詢" 小節有描述.
這兩個方法的返回值都是結果集物件,因此結果集可以進行鏈式處理:
Poll.objects.filter(question__startswith="What").exclude(
pub_date__gte=datetime.now()).
filter(pub_date__gte=datetime(2005,1,1))
...以一個初始結果集作為引數, 然後進行過濾, 再進行排除, 再進行另一個過濾. 這樣得到的最終結果就一個問題開頭單詞是 "What", 釋出日期在 2005年1月1日至今的所有民意測驗的集合.
每個結果集都是一個獨一無二的物件. 以上操作的每一步都生成了一個新的結果集:
q1 = Poll.objects.filter(question__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.now())
q3 = q1.filter(pub_date__gte=datetime.now())
這三步生成了三個結果集; 一個初始結果集包含所有的以"What"開頭的民意測驗, 兩個初始結果集的子集(一個排除條件,一個過濾條件).對原始結果集的改進過程並沒有影響到原始的結果集.
值得注意的是結果集的建立根本沒有訪問資料庫.只有當對結果集取值時才會訪問資料庫.
欄位查詢
以 field__lookuptype (注意是雙下線)形式進行基本的欄位查詢,舉例來說:
polls.objects.filter(pub_date__lte=datetime.now())
該查詢翻譯成SQL就是:
SELECT * FROM polls_polls WHERE pub_date <= NOW();
實現細節
Python 能夠在定義函式時接受任意的 name-value(names和values均可以在執行時通過計算得到)引數. 要了解更多資訊,參閱官方 Python 教程中的 關鍵字引數 .
DB API 支援下列查詢型別:
型別 描述 exact 精確匹配: polls.get_object(id__exact=14). iexact 忽略大小寫的精確匹配: polls.objects.filter(slug__iexact="foo") 匹配 foo, FOO, fOo, 等等. contains 大小寫敏感的內容包含測試: polls.objects.filter(question__contains="spam") 返回question 中包含 "spam" 的所有民意測驗.(僅PostgreSQL 和 MySQL支援. SQLite 的LIKE 語句不支援大小寫敏感特性. 對Sqlite 來說, contains 等於 icontains.) icontains 大小寫不敏感的內容包含測試: gt 大於: polls.objects.filter(id__gt=4). gte 大於等於. lt 小於. lte 小於等於. ne 不等於. in 位於給定列表中: polls.objects.filter(id__in=[1, 3, 4]) 返回一個 polls 列表(ID 值分別是 1或3或4). startswith 大小寫敏感的 starts-with: polls.objects.filter(question__startswith="Would").(僅PostgreSQL 和MySQL支援. SQLite 的LIKE 語句不支援大小寫敏感特性. 對Sqlite 來說,``startswith`` 等於 istartswith) endswith 大小寫敏感的 ends-with. (僅PostgreSQL 和 MySQL) istartswith 大小寫不敏感的 starts-with. iendswith 大小寫不敏感的 ends-with. range 範圍測試: polls.objects.filter(pub_date__range=(start_date, end_date)) 返回 pub_date 位於 start_date 和 end_date (包括)之間的所有民意測驗 year 對 date/datetime 欄位, 進行精確的 年 匹配: polls.get_count(pub_date__year=2005). month 對 date/datetime 欄位, 進行精確的 月 匹配: day 對 date/datetime 欄位, 進行精確的 日 匹配: isnull True/False; 做 IF NULL/IF NOT NULL 查詢:polls.objects.filter(expire_date__isnull=True).
如果未提供查詢型別, 系統就認為查詢型別是 exact . 下面兩個語句是等價的:
Poll.objects.get(id=14)
Poll.objects.get(id__exact=14)
查詢允許多個條件引數, 逗號分隔的多個條件引數會被 "AND" 起來使用:
polls.objects.filter(pub_date__year=2005,
pub_date__month=1,
question__startswith="Would" )
...得到2005年1月公佈的帶有一個"Would"開頭的問題的所有民意測驗.
為了使用更加方便, 還提供有一個 pk 查詢型別, 可以翻譯成 (primary_key)__exact. 在這個民意測試的例子裡, 下面兩個語句是等價的.:
polls.get_object(id__exact=3)
polls.get_object(pk=3)
pk 也可以通過連線進行查詢. 在這個民意測試的例子裡, 下面兩個語句是等價的:
choices.objects.filter(poll__id__exact=3)
choices.objects.filter(poll__pk=3)
如果傳遞的關鍵字引數非法, 將引發 TypeError 異常.
OR 查詢
關鍵字引數查詢的各個條件都是 "AND" 關係. 如果你需要一個複雜的查詢(舉例來說,你需要一個 OR語句), 你需要使用 Q 物件.
Q 物件是 django.core.meta.Q 的例項, 用來裝載一系列關鍵字引數.
這些關鍵字引數就象指定給 get() 和 filter() 函式的關鍵字引數一樣. 舉例來說:
Q(question__startswith='What')
Q 物件可以使用 & 和 | 運算子進行組合. 當兩個Q物件進行 & 或 | 運算時,會生成一個新的Q物件.
舉例來說語句:
Q(question__startswith='Who') | Q(question__startswith='What')
... 生成一個新的 Q 物件表示這兩個 "question__startswith" 查詢條件的 "OR" 關係.
等同於下面的 SQL WHERE 子句:
... WHERE question LIKE 'Who%' OR question LIKE 'What%'
通過對多個 Q 物件的 & 和 | 運算你能得到任意複雜的查詢語句. 也可以使用圓括號分組.
查詢函式可以接受一個或多個 Q 物件作為引數.如果提供有多個 Q 物件引數, 它們將被 "AND" 到一起. 舉例來說:
polls.get_object(
Q(question__startswith='Who'),
Q(pub_date__exact=date(2005, 5, 2)) |
Q(pub_date__exact=date(2005, 5, 6))
)
... 翻譯成 SQL 就是這樣:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
如果需要,查詢函式可以混合使用 Q 物件引數和關鍵字引數. 所有提供給查詢函式的引數(不管是關鍵字引數還是Q物件)都被 "AND" 到一起. 如果提供了 Q 物件作為引數,它就必須在其它關鍵字引數(如果有的話)的前面. 舉例來說:
polls.get_object(
Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6)),
question__startswith='Who')
... 這是一個合法的查詢, 等價於前一個例子,不過:
# INVALID QUERY
polls.get_object(
question__startswith='Who',
Q(pub_date__exact=date(2005, 5, 2)) | Q(pub_date__exact=date(2005, 5, 6)))
... 這個查詢則不符合我們的規則,會引發異常.
Q 物件也可以以 complex 關鍵字引數的形式使用.
舉例來說:
polls.get_object(
complex=Q(question__startswith='Who') &
(Q(pub_date__exact=date(2005, 5, 2)) |
Q(pub_date__exact=date(2005, 5, 6))
)
)
參閱 OR 查詢示例 以閱讀更多例項.
只有通過取值操作才能得到結果集包含的物件.取值操作可以通過迭代,切片,或其它專門的函式來實現.
一個結果集就是一個可迭代物件.因此,可以通過一個迴圈來取出它的值:
for p in Poll.objects.all():
print p
將使用 Poll 物件的 __repr__() 方法打印出所有的 Poll 物件.
一個結果集也可以被切片, 使用陣列符號操作:
fifth_poll = Poll.objects.all()[4]
all_polls_but_the_first_two = Poll.objects.all()[2:]
every_second_poll = Poll.objects.all()[::2]
結果集物件是惰性物件 - 也就是說,他們不是 真正的 包含他們表示物件的集合 (或列表).
Python 的協議魔法讓結果集看起來是一個可迭代,可切片的物件.
事實上在幕後, Django 使用了快取技術,如果你真的需要一個列表, 你可以強制對一個惰性物件取值:
querylist = list(Poll.objects.all())
不過,最好不要這麼做,尤其當一個結果集相當大時. 由於 Django 要建立每一個物件的記憶體表示,這將佔用相當大的記憶體.
每個結果集都包含一個 cache. 對一個新建立的結果集來說, 快取區是空的.當一個結果集第一次被取值, Django 會進行一次資料庫查詢,並將查詢結果放入快取中, 之後返回使用者需要的資料. 後面的取值操作會使用快取中的資料而不用再次訪問資料庫.
必須時刻記住:結果集具有快取行為. 下面兩行語句生成了兩個臨時的結果集,並進行了取值,之後捨棄:
print [p for p in Poll.objects.all()] # Evaluate the Query Set
print [p for p in Poll.objects.all()] # Evaluate the Query Set again
對一個小型的,低流量的站點來說,這不會造成嚴重問題.不過,對一個高訪問量的站點來說,它雙倍增加了資料庫伺服器的負擔.另外,由於在兩次操作之間可能有其它的使用者增加或刪除了投票,因此這兩次操作得到結果可能並不相同.
要避免這個問題, 儲存這個結果集並在後面重用該結果集:
queryset = Poll.objects.all()
print [p for p in queryset] # Evaluate the query set
print [p for p in queryset] # Re-use the cache from the evaluation
update()方法會返回一個整型數值,表示受影響的記錄條數。 在上面的例子中,這個值是2。 刪除物件
刪除資料庫中的物件只需呼叫該物件的delete()方法即可:
1 2 3 4 |
|
同樣我們可以在結果集上呼叫delete()方法同時刪除多條記錄。這一點與我們上一小節提到的update()方法相似:
1 2 3 4 |
|
刪除資料時要謹慎! 為了預防誤刪除掉某一個表內的所有資料,Django要求在刪除表內所有資料時顯示使用all()。 比如,下面的操作將會出錯:
1 2 3 4 |
|
而一旦使用all()方法,所有資料將會被刪除:
1 |
|
如果只需要刪除部分的資料,就不需要呼叫all()方法。再看一下之前的例子:
1 |
|
資料更新
Python程式碼
- >>> Publisher.objects.filter(id=52).update(name='Apress Publishing')
- >>> Publisher.objects.filter(id=52).update(name='Apress Publishing')