1. 程式人生 > >Django資料庫查詢相關

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

>>> p = Publisher.objects.get(name="O'Reilly")

>>> p.delete()

>>> Publisher.objects.all()

[<Publisher: Apress Publishing>]

同樣我們可以在結果集上呼叫delete()方法同時刪除多條記錄。這一點與我們上一小節提到的update()方法相似:

?

1

2

3

4

>>> Publisher.objects.filter(country='USA').delete()

>>> Publisher.objects.all().delete()

>>> Publisher.objects.all()

[]

刪除資料時要謹慎! 為了預防誤刪除掉某一個表內的所有資料,Django要求在刪除表內所有資料時顯示使用all()。 比如,下面的操作將會出錯:

?

1

2

3

4

>>> Publisher.objects.delete()

Traceback (most recent call last):

File "<console>", line 1, in <module>

AttributeError: 'Manager' object has no attribute 'delete'

而一旦使用all()方法,所有資料將會被刪除:

?

1

>>> Publisher.objects.all().delete()

如果只需要刪除部分的資料,就不需要呼叫all()方法。再看一下之前的例子:

?

1

>>> Publisher.objects.filter(country='USA').delete()

資料更新

Python程式碼

  1. >>> Publisher.objects.filter(id=52).update(name='Apress Publishing')  
  1. >>> Publisher.objects.filter(id=52).update(name='Apress Publishing')