# 執行查詢 #
一旦你建立好*數據模型*之后,django會自動生成一套數據庫抽象的API,可以讓你執行增刪改查的操作。這篇文檔闡述了如何使用這些API。關于所有模型檢索選項的詳細內容,請見*[數據模型參考](https://docs.djangoproject.com/en/1.8/ref/models/)*。
在整個文檔(以及參考)中,我們會大量使用下面的模型,它構成了一個博客應用。
```
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
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 __str__(self): # __unicode__ on Python 2
return self.headline
```
## 創建對象 ##
為了把數據庫表中的數據表示成python對象,django使用一種直觀的方式:一個模型類代表數據庫的一個表,一個模型的實例代表數據庫表中的一條特定的記錄。
使用關鍵詞參數實例化一個對象來創建它,然后調用**save()**把它保存到數據庫中。
假設模型存放于文件**mysite/blog/models.py**中,下面是一個例子:
```
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
```
上面的代碼在背后執行了sql的**INSERT**操作。在你顯式調用**save()**之前,django不會訪問數據庫。
**save()**方法沒有返回值。
> 請參見
>
> **save()**方法帶有一些高級選項,它們沒有在這里給出,完整的細節請見**save()**文檔。
>
> 如果你想只用一條語句創建并保存一個對象,使用**create()**方法。
## 保存對象的改動 ##
調用**save()**方法,來保存已經存在于數據庫中的對象的改動。
假設一個**Blog**的實例**b5**已經被保存在數據庫中,這個例子更改了它的名字,并且在數據庫中更新它的記錄:
```
>>> b5.name = 'New name'
>>> b5.save()
```
上面的代碼在背后執行了sql的**UPDATE**操作。在你顯式調用**save()**之前,django不會訪問數據庫。
### 保存**ForeignKey**和**ManyToManyField**字段 ###
更新**ForeignKey**字段的方式和保存普通字段相同--只是簡單地把一個類型正確的對象賦值到字段中。下面的例子更新了**Entry**類的實例**entry**的**blog**屬性,假設**Entry**的一個合適的實例以及**Blog**已經保存在數據庫中(我們可以像下面那樣獲取他們):
```
>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
```
更新**ManyToManyField**的方式有一些不同--使用字段的**add()**方法來增加關系的記錄。這個例子向**entry**對象添加**Author**類的實例**joe**:
```
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
```
為了在一條語句中,向**ManyToManyField**添加多條記錄,可以在調用**add()**方法時傳入多個參數,像這樣:
```
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
```
Django將會在你添加錯誤類型的對象時拋出異常。
## 獲取對象 ##
通過模型中的**Manager**構造一個**QuertSet**,來從你的數據庫中獲取對象。
**QuerySet**表示你數據庫中取出來的一個對象的集合。它可以含有零個、一個或者多個過濾器,過濾器根據所給的參數限制查詢結果的范圍。在sql的角度,**QuerySet**和**SELECT**命令等價,過濾器是像**WHERE**和**LIMIT**一樣的限制子句。
你可以從模型的**Manager**那里取得**QuerySet**。每個模型都至少有一個**Manager**,它通常命名為**objects**。通過模型類直接訪問它,像這樣:
```
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
```
> **注意**
>
> 管理器通常只可以通過模型類來訪問,不可以通過模型實例來訪問。這是為了強制區分表級別和記錄級別的操作。
對于一個模型來說,**Manager**是**QuerySet**的主要來源。例如,** Blog.objects.all() **會返回持有數據庫中所有**Blog**對象的一個**QuerySet**。
### 獲取所有對象 ###
獲取一個表中所有對象的最簡單的方式是全部獲取。使用**Manager**的**all()**方法:
```
>>> all_entries = Entry.objects.all()
```
**all()**方法返回包含數據庫中所有對象的**QuerySet**。
### 使用過濾器獲取特定對象 ###
**all()**方法返回的結果集中包含全部對象,但是更普遍的情況是你需要獲取完整集合的一個子集。
要創建這樣一個子集,需要精煉上面的結果集,增加一些過濾器作為條件。兩個最普遍的途徑是:
**filter(\*\*kwargs)**
返回一個包含對象的集合,它們滿足參數中所給的條件。
**exclude(\*\*kwargs)**
返回一個包含對象的集合,它們***不***滿足參數中所給的條件。
查詢參數(上面函數定義中的**\*\*kwargs**)需要滿足特定的格式,*字段檢索*一節中會提到。
舉個例子,要獲取年份為2006的所有文章的結果集,可以這樣使用**filter()**方法:
```
Entry.objects.filter(pub_date__year=2006)
```
在默認的管理器類中,它相當于:
```
Entry.objects.all().filter(pub_date__year=2006)
```
#### 鏈式過濾 ####
**QuerySet**的精煉結果還是**QuerySet**,所以你可以把精煉用的語句組合到一起,像這樣:
```
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime(2005, 1, 30)
... )
```
最開始的**QuerySet**包含數據庫中的所有對象,之后增加一個過濾器去掉一部分,在之后又是另外一個過濾器。最后的結果的一個**QuerySet**,包含所有標題以”word“開頭的記錄,并且日期是2005年一月,日為當天的值。
#### 過濾后的結果集是獨立的 ####
每次你篩選一個結果集,得到的都是全新的另一個結果集,它和之前的結果集之間沒有任何綁定關系。每次篩選都會創建一個獨立的結果集,可以被存儲及反復使用。
例如:
```
>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
```
這三個 QuerySets 是不同的。 第一個 QuerySet 包含大標題以"What"開頭的所有記錄。第二個則是第一個的子集,用一個附加的條件排除了出版日期 pub_date 是今天的記錄。 第三個也是第一個的子集,它只保留出版日期 pub_date 是今天的記錄。 最初的 QuerySet (q1) 沒有受到篩選的影響。
## 查詢集是延遲的 ##
QuerySets 是惰性的 -- 創建 QuerySet 的動作不涉及任何數據庫操作。你可以一直添加過濾器,在這個過程中,Django 不會執行任何數據庫查詢,除非 QuerySet 被執行. 看看下面這個例子:
```
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q
```
雖然上面的代碼看上去象是三個數據庫操作,但實際上只在最后一行 (print q) 執行了一次數據庫操作,。一般情況下, QuerySet 不能從數據庫中主動地獲得數據,得被動地由你來請求。對 QuerySet 求值就意味著 Django 會訪問數據庫。想了解對查詢集何時求值,請查看 何時對查詢集求值 (When QuerySets are evaluated).
## 其他查詢集方法 ##
大多數情況使用 all(), filter() 和 exclude() 就足夠了。 但也有一些不常用的;請查看 查詢API參考 (QuerySet API Reference) 中完整的 QuerySet 方法列表。
## 限制查詢集范圍 ##
可以用 python 的數組切片語法來限制你的 QuerySet 以得到一部分結果。它等價于SQL中的 LIMIT 和 OFFSET 。
例如,下面的這個例子返回前五個對象 (LIMIT 5):
```
>>> Entry.objects.all()[:5]
```
這個例子返回第六到第十之間的對象 (OFFSET 5 LIMIT 5):
```
>>> Entry.objects.all()[5:10]
```
Django 不支持對查詢集做負數索引 (例如 Entry.objects.all()[-1]) 。
一般來說,對 QuerySet 切片會返回新的 QuerySet -- 這個過程中不會對運行查詢。不過也有例外,如果你在切片時使用了 "step" 參數,查詢集就會被求值,就在數據庫中運行查詢。舉個例子,使用下面這個這個查詢集返回前十個對象中的偶數次對象,就會運行數據庫查詢:
```
>>> Entry.objects.all()[:10:2]
```
要檢索單獨的對象,而非列表 (比如 SELECT foo FROM bar LIMIT 1),可以直接使用索引來代替切片。舉個例子,下面這段代碼將返回大標題排序后的第一條記錄 Entry:
```
>>> Entry.objects.order_by('headline')[0]
```
大約等價于:
```
>>> Entry.objects.order_by('headline')[0:1].get()
```
要注意的是:如果找不到符合條件的對象,第一種方法會拋出 IndexError ,而第二種方法會拋出 DoesNotExist。 詳看 get() 。
## 字段篩選條件 ##
字段篩選條件就是 SQL 語句中的 WHERE 從句。就是 Django 中的 QuerySet 的 filter(), exclude() 和 get() 方法中的關鍵字參數。
篩選條件的形式是 field__lookuptype=value 。 (注意:這里是雙下劃線)。例如:
```
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
```
大體可以翻譯為如下的 SQL 語句:
```
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
```
這是怎么辦到的?
Python 允許函式接受任意多 name-value 形式的參數,并在運行時才確定name和value的值。詳情請參閱官方Python教程中的 關鍵字參數(Keyword Arguments)。
如果你傳遞了一個無效的關鍵字參數,會拋出 TypeError 導常。
數據庫 API 支持24種查詢類型;可以在 字段篩選參考(field lookup reference) 查看詳細的列表。為了給您一個直觀的認識,這里我們列出一些常用的查詢類型:
**exact**
"exact" 匹配。例如:
```
>>> Entry.objects.get(headline__exact="Man bites dog")
```
會生成如下的 SQL 語句:
```
SELECT ... WHERE headline = 'Man bites dog';
```
如果你沒有提供查詢類型 -- 也就是說關鍵字參數中沒有雙下劃線,那么查詢類型就會被指定為 exact。
舉個例子,這兩個語句是相等的:
```
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
```
這樣做很方便,因為 exact 是最常用的。
**iexact**
忽略大小寫的匹配。所以下面的這個查詢:
```
>>> Blog.objects.get(name__iexact="beatles blog")
```
會匹配標題是 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog
**contains**
大小寫敏感的模糊匹配。 例如:
```
Entry.objects.get(headline__contains='Lennon')
```
大體可以翻譯為如下的 SQL:
```
SELECT ... WHERE headline LIKE '%Lennon%';
```
要注意這段代碼匹配大標題 'Today Lennon honored' ,而不能匹配 'today lennon honored'。
它也有一個忽略大小寫的版本,就是 icontains。
**startswith, endswith**
分別匹配開頭和結尾,同樣也有忽略大小寫的版本 istartswith 和 iendswith。
再強調一次,這僅僅是簡短介紹。完整的參考請參見 字段篩選條件參考(field lookup reference)。
## 跨關系查詢 ##
Django 提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關系,它能自動確認 SQL JOIN 聯系。要做跨關系查詢,就使用兩個下劃線來鏈接模型(model)間關聯字段的名稱,直到最終鏈接到你想要的 model 為止。
這個例子檢索所有關聯 Blog 的 name 值為 'Beatles Blog' 的所有 Entry 對象:
```
>>> Entry.objects.filter(blog__name__exact='Beatles Blog')
```
跨關系的篩選條件可以一直延展。
關系也是可逆的。可以在目標 model 上使用源 model 名稱的小寫形式得到反向關聯。
下面這個例子檢索至少關聯一個 Entry 且大標題 headline 包含 'Lennon' 的所有 Blog 對象:
```
>>> Blog.objects.filter(entry__headline__contains='Lennon')
```
如果在某個關聯 model 中找不到符合過濾條件的對象,Django 將視它為一個空的 (所有的值都是 NULL), 但是可用的對象。這意味著不會有異常拋出,在這個例子中:
```
Blog.objects.filter(entry__author__name='Lennon')
```
(假設關聯到 Author 類), 如果沒有哪個 author 與 entry 相關聯,Django 會認為它沒有 name 屬性,而不會因為不存在 author 拋出異常。通常來說,這正是你所希望的機制。唯一的例外是使用 isnull 的情況。如下:
```
Blog.objects.filter(entry__author__name__isnull=True)
```
這段代碼會得到 author 的 name 為空的 Blog 或 entry 的 author為空的 Blog。 如果不嫌麻煩,可以這樣寫:
```
Blog.objects.filter (entry__author__isnull=False,
entry__author__name__isnull=True)
```
跨一對多/多對多關系(Spanning multi-valued relationships)
這部分是Django 1.0中新增的: 請查看版本記錄
如果你的過濾是基于 ManyToManyField 或是逆向 ForeignKeyField 的,你可能會對下面這兩種情況感興趣。回顧 Blog/Entry 的關系(Blog 到 Entry 是一對多關系),如果要查找這樣的 blog:它關聯一個大標題包含"Lennon",且在2008年出版的 entry ;或者要查找這樣的 blogs:它關聯一個大標題包含"Lennon"的 entry ,同時它又關聯另外一個在2008年出版的 entry 。因為一個 Blog 會關聯多個的Entry,所以上述兩種情況在現實應用中是很有可能出現的。
同樣的情形也出現在 ManyToManyField 上。例如,如果 Entry 有一個 ManyToManyField 字段,名字是 tags,我們想得到 tags 是"music"和"bands"的 entries,或者我們想得到包含名為"music" 的標簽而狀態是"public"的 entry。
針對這兩種情況,Django 用一種很方便的方式來使用 filter() 和 exclude()。對于包含在同一個 filter() 中的篩選條件,查詢集要同時滿足所有篩選條件。而對于連續的 filter() ,查詢集的范圍是依次限定的。但對于跨一對多/多對多關系查詢來說,在第二種情況下,篩選條件針對的是主 model 所有的關聯對象,而不是被前面的 filter() 過濾后的關聯對象。
這聽起來會讓人迷糊,舉個例子會講得更清楚。要檢索這樣的 blog:它要關系一個大標題中含有 "Lennon" 并且在2008年出版的 entry (這個 entry 同時滿足這兩個條件),可以這樣寫:
```
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
```
要檢索另外一種 blog:它關聯一個大標題含有"Lennon"的 entry ,又關聯一個在2008年出版的 entry (一個 entry 的大標題含有 Lennon,同一個或另一個 entry 是在2008年出版的)。可以這樣寫:
```
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)
```
在第二個例子中,第一個過濾器(filter)先檢索與符合條件的 entry 的相關聯的所有 blogs。第二個過濾器在此基礎上從這些 blogs 中檢索與第二種 entry 也相關聯的 blog。第二個過濾器選擇的 entry 可能與第一個過濾器所選擇的完全相同,也可能不同。 因為過濾項過濾的是 Blog,而不是 Entry。
上述原則同樣適用于 exclude():一個單獨 exclude() 中的所有篩選條件都是作用于同一個實例 (如果這些條件都是針對同一個一對多/多對多的關系)。連續的 filter() 或 exclude() 卻根據同樣的篩選條件,作用于不同的關聯對象。
在過濾器中引用 model 中的字段(Filters can reference fields on the model)
這部分是 Django 1.1 新增的: 請查看版本記錄
在上面所有的例子中,我們構造的過濾器都只是將字段值與某個常量做比較。如果我們要對兩個字段的值做比較,那該怎么做呢?
Django 提供 F() 來做這樣的比較。F() 的實例可以在查詢中引用字段,來比較同一個 model 實例中兩個不同字段的值。
例如:要查詢回復數(comments)大于廣播數(pingbacks)的博文(blog entries),可以構造一個 F() 對象在查詢中引用評論數量:
```
>>> from django.db.models import F
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments'))
```
Django 支持 F() 對象之間以及 F() 對象和常數之間的加減乘除和取模的操作。例如,要找到廣播數等于評論數兩倍的博文,可以這樣修改查詢語句:
```
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2)
```
要查找閱讀數量小于評論數與廣播數之和的博文,查詢如下:
```
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
```
你也可以在 F() 對象中使用兩個下劃線做跨關系查詢。F() 對象使用兩個下劃線引入必要的關聯對象。例如,要查詢博客(blog)名稱與作者(author)名稱相同的博文(entry),查詢就可以這樣寫:
```
>>> Entry.objects.filter(author__name=F('blog__name'))
```
## 主鍵查詢的簡捷方式 ##
為使用方便考慮,Django 用 pk 代表主鍵"primary key"。
以 Blog 為例, 主鍵是 id 字段,所以下面三個語句都是等價的:
```
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact
```
pk 對 __exact 查詢同樣有效,任何查詢項都可以用 pk 來構造基于主鍵的查詢:
```
# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)
```
pk 查詢也可以跨關系,下面三個語句是等價的:
```
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3) # __exact is implied
>>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
```
## 在LIKE語句中轉義百分號%和下劃線_ ##
字段篩選條件相當于 LIKE SQL 語句 (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) ,它會自動轉義兩個特殊符號 -- 百分號%和下劃線_。(在 LIKE 語句中,百分號%表示多字符匹配,而下劃線_表示單字符匹配。)
這就意味著我們可以直接使用這兩個字符,而不用考慮他們的 SQL 語義。例如,要查詢大標題中含有一個百分號%的 entry:
```
>>> Entry.objects.filter(headline__contains='%')
```
Django 會處理轉義;最終的 SQL 看起來會是這樣:
```
SELECT ... WHERE headline LIKE '%\%%';
```
下劃線_和百分號%的處理方式相同,Django 都會自動轉義。
## 緩存和查詢 ##
每個 QuerySet 都包含一個緩存,以減少對數據庫的訪問。要編寫高效代碼,就要理解緩存是如何工作的。
一個 QuerySet 時剛剛創建的時候,緩存是空的。 QuerySet 第一次運行時,會執行數據庫查詢,接下來 Django 就在 QuerySet 的緩存中保存查詢的結果,并根據請求返回這些結果(比如,后面再次調用這個 QuerySet 的時候)。再次運行 QuerySet 時就會重用這些緩存結果。
要牢住上面所說的緩存行為,否則在使用 QuerySet 時可能會給你造成不小的麻煩。例如,創建下面兩個 QuerySet ,并對它們求值,然后釋放:
```
>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]
```
這就意味著相同的數據庫查詢將執行兩次,事實上讀取了兩次數據庫。而且,這兩次讀出來的列表可能并不完全相同,因為存在這種可能:在兩次讀取之間,某個 Entry 被添加到數據庫中,或是被刪除了。
要避免這個問題,只要簡單地保存 QuerySet 然后重用即可:
```
>>> queryset = Poll.objects.all()
>>> print [p.headline for p in queryset] # Evaluate the query set.
>>> print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.
```
用 Q 對象實現復雜查找 (Complex lookups with Q objects)
在 filter() 等函式中關鍵字參數彼此之間都是 "AND" 關系。如果你要執行更復雜的查詢(比如,實現篩選條件的 OR 關系),可以使用 Q 對象。
Q 對象(django.db.models.Q)是用來封裝一組查詢關鍵字的對象。這里提到的查詢關鍵字請查看上面的 "Field lookups"。
例如,下面這個 Q 對象封裝了一個單獨的 LIKE 查詢:
```
Q(question__startswith='What')
```
Q 對象可以用 & 和 | 運算符進行連接。當某個操作連接兩個 Q 對象時,就會產生一個新的等價的 Q 對象。
例如,下面這段語句就產生了一個 Q ,這是用 "OR" 關系連接的兩個 "question__startswith" 查詢:
```
Q(question__startswith='Who') | Q(question__startswith='What')
```
上面的例子等價于下面的 SQL WHERE 從句:
```
WHERE question LIKE 'Who%' OR question LIKE 'What%'
```
你可以用 & 和 | 連接任意多的 Q 對象,而且可以用括號分組。Q 對象也可以用 ~ 操作取反,而且普通查詢和取反查詢(NOT)可以連接在一起使用:
```
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
```
每種查詢函式(比如 filter(), exclude(), get()) 除了能接收關鍵字參數以外,也能以位置參數的形式接受一個或多個 Q 對象。如果你給查詢函式傳遞了多個 Q 對象,那么它們彼此間都是 "AND" 關系。例如:
```
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=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 對象,它必須排在所有的關鍵字參數之前。例如:
```
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who')
```
... 是一個有效的查詢。但下面這個查詢雖然看上去和前者等價:
```
# INVALID QUERY
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
```
... 但這個查詢卻是無效的。
> 參見
>
> 在 Django 的單元測試 OR查詢實例(OR lookups examples) 中展示了 Q 的用例。
## 對象比較 ##
要比較兩個對象,就和 Python 一樣,使用雙等號運算符:==。實際上比較的是兩個 model 的主鍵值。
以上面的 Entry 為例,下面兩個語句是等價的:
```
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
```
如果 model 的主鍵名稱不是 id,也沒關系。Django 會自動比較主鍵的值,而不管他們的名稱是什么。例如,如果一個 model 的主鍵字段名稱是 name,那么下面兩個語句是等價的:
```
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
```
## 對象刪除 ##
刪除方法就是 delete()。它運行時立即刪除對象而不返回任何值。例如:
```
e.delete()
```
你也可以一次性刪除多個對象。每個 QuerySet 都有一個 delete() 方法,它一次性刪除 QuerySet 中所有的對象。
例如,下面的代碼將刪除 pub_date 是2005年的 Entry 對象:
```
Entry.objects.filter(pub_date__year=2005).delete()
```
要牢記這一點:無論在什么情況下,QuerySet 中的 delete() 方法都只使用一條 SQL 語句一次性刪除所有對象,而并不是分別刪除每個對象。如果你想使用在 model 中自定義的 delete() 方法,就要自行調用每個對象的delete 方法。(例如,遍歷 QuerySet,在每個對象上調用 delete()方法),而不是使用 QuerySet 中的 delete()方法。
在 Django 刪除對象時,會模仿 SQL 約束 ON DELETE CASCADE 的行為,換句話說,刪除一個對象時也會刪除與它相關聯的外鍵對象。例如:
```
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
```
要注意的是: delete() 方法是 QuerySet 上的方法,但并不適用于 Manager 本身。這是一種保護機制,是為了避免意外地調用 Entry.objects.delete() 方法導致 所有的 記錄被誤刪除。如果你確認要刪除所有的對象,那么你必須顯式地調用:
```
Entry.objects.all().delete()
```
一次更新多個對象 (Updating multiple objects at once)
這部分是 Django 1.0 中新增的: 請查看版本文檔
有時你想對 QuerySet 中的所有對象,一次更新某個字段的值。這個要求可以用 update() 方法完成。例如:
```
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
```
這種方法僅適用于非關系字段和 ForeignKey 外鍵字段。更新非關系字段時,傳入的值應該是一個常量。更新 ForeignKey 字段時,傳入的值應該是你想關聯的那個類的某個實例。例如:
```
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
```
update() 方法也是即時生效,不返回任何值的(與 delete() 相似)。 在 QuerySet 進行更新時,唯一的限制就是一次只能更新一個數據表,就是當前 model 的主表。所以不要嘗試更新關聯表和與此類似的操作,因為這是不可能運行的。
要小心的是: update() 方法是直接翻譯成一條 SQL 語句的。因此它是直接地一次完成所有更新。它不會調用你的 model 中的 save() 方法,也不會發出 pre_save 和 post_save 信號(這些信號在調用 save() 方法時產生)。如果你想保存 QuerySet 中的每個對象,并且調用每個對象各自的 save() 方法,那么你不必另外多寫一個函式。只要遍歷這些對象,依次調用 save() 方法即可:
```
for item in my_queryset:
item.save()
```
這部分是在 Django 1.1 中新增的: 請查看版本文檔
在調用 update 時可以使用 F() 對象 來把某個字段的值更新為另一個字段的值。這對于自增記數器是非常有用的。例如,給所有的博文 (entry) 的廣播數 (pingback) 加一:
```
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
```
但是,與 F() 對象在查詢時所不同的是,在filter 和 exclude子句中,你不能在 F() 對象中引入關聯關系(NO-Join),你只能引用當前 model 中要更新的字段。如果你在 F() 對象引入了Join 關系object,就會拋出 FieldError 異常:
```
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))
```
## 對象關聯 ##
當你定義在 model 定義關系時 (例如, ForeignKey, OneToOneField, 或 ManyToManyField),model 的實例自帶一套很方便的API以獲取關聯的對象。
以最上面的 models 為例,一個 Entry 對象 e 能通過 blog 屬性獲得相關聯的 Blog 對象: e.blog。
(在場景背后,這個功能是由 Python 的 descriptors 實現的。如果你對此感興趣,可以了解一下。)
Django 也提供反向獲取關聯對象的 API,就是由從被關聯的對象得到其定義關系的主對象。例如,一個 Blog 類的實例 b 對象通過 entry_set 屬性得到所有相關聯的 Entry 對象列表: b.entry_set.all()。
這一節所有的例子都使用本頁頂部所列出的 Blog, Author 和 Entry model。
## 一對多關系 ##
### 正向 ###
如果一個 model 有一個 ForeignKey字段,我們只要通過使用關聯 model 的名稱就可以得到相關聯的外鍵對象。
例如:
```
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
```
你可以設置和獲得外鍵屬性。正如你所期望的,改變外鍵的行為并不引發數據庫操作,直到你調用 save()方法時,才會保存到數據庫。例如:
```
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
```
如果外鍵字段 ForeignKey 有一個 null=True 的設置(它允許外鍵接受空值 NULL),你可以賦給它空值 None 。例如:
```
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
```
在一對多關系中,第一次正向獲取關聯對象時,關聯對象會被緩存。其后根據外鍵訪問時這個實例,就會從緩存中獲得它。例如:
```
>>> e = Entry.objects.get(id=2)
>>> print e.blog # Hits the database to retrieve the associated Blog.
>>> print e.blog # Doesn't hit the database; uses cached version.
```
要注意的是,QuerySet 的 select_related() 方法提前將所有的一對多關系放入緩存中。例如:
```
>>> e = Entry.objects.select_related().get(id=2)
>>> print e.blog # Doesn't hit the database; uses cached version.
>>> print e.blog # Doesn't hit the database; uses cached version.
```
### 逆向關聯 ###
如果 model 有一個 ForeignKey外鍵字段,那么外聯 model 的實例可以通過訪問 Manager 來得到所有相關聯的源 model 的實例。默認情況下,這個 Manager 被命名為 FOO_set, 這里面的 FOO 就是源 model 的小寫名稱。這個 Manager 返回 QuerySets,它是可過濾和可操作的,在上面 "對象獲取(Retrieving objects)" 有提及。
例如:
```
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.
# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
```
你可以通過在 ForeignKey() 的定義中設置 related_name 的值來覆寫 FOO_set 的名稱。例如,如果 Entry model 中做一下更改: blog = ForeignKey(Blog, related_name='entries'),那么接下來就會如我們看到這般:
```
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.
# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
```
你不能在一個類當中訪問 ForeignKey Manager ;而必須通過類的實例來訪問:
```
>>> Blog.entry_set
Traceback:
...
AttributeError: "Manager must be accessed via instance".
```
除了在上面 "對象獲取Retrieving objects" 一節中提到的 QuerySet 方法之外,ForeignKey Manager 還有如下一些方法。下面僅僅對它們做一個簡短介紹,詳情請查看 related objects reference。
`add(obj1, obj2, ...)`
將某個特定的 model 對象添加到被關聯對象集合中。
`create(**kwargs)`
創建并保存一個新對象,然后將這個對象加被關聯對象的集合中,然后返回這個新對象。
`remove(obj1, obj2, ...)`
將某個特定的對象從被關聯對象集合中去除。
`clear()`
清空被關聯對象集合。
想一次指定關聯集合的成員,那么只要給關聯集合分配一個可迭代的對象即可。它可以包含對象的實例,也可以只包含主鍵的值。例如:
```
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
```
在這個例子中,e1 和 e2 可以是完整的 Entry 實例,也可以是整型的主鍵值。
如果 clear() 方法是可用的,在迭代器(上例中就是一個列表)中的對象加入到 entry_set 之前,已存在于關聯集合中的所有對象將被清空。如果 clear() 方法 不可用,原有的關聯集合中的對象就不受影響,繼續存在。
這一節提到的每一個 "reverse" 操作都是實時操作數據庫的,每一個添加,創建,刪除操作都會及時保存將結果保存到數據庫中。
## 多對多關系 ##
在多對多關系的任何一方都可以使用 API 訪問相關聯的另一方。多對多的 API 用起來和上面提到的 "逆向" 一對多關系關系非常相象。
唯一的差雖就在于屬性的命名: ManyToManyField 所在的 model (為了方便,我稱之為源model A) 使用字段本身的名稱來訪問關聯對象;而被關聯的另一方則使用 A 的小寫名稱加上 '_set' 后綴(這與逆向的一對多關系非常相象)。
下面這個例子會讓人更容易理解:
```
e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')
a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.
```
與 ForeignKey 一樣, ManyToManyField 也可以指定 related_name。在上面的例子中,如果 Entry 中的 ManyToManyField 指定 related_name='entries',那么接下來每個 Author 實例的 entry_set 屬性都被 entries 所代替。
### 一對一關系 ###
相對于多對一關系而言,一對一關系不是非常簡單的。如果你在 model 中定義了一個 OneToOneField 關系,那么你就可以用這個字段的名稱做為屬性來訪問其所關聯的對象。
例如:
```
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
```
與 "reverse" 查詢不同的是,一對一關系的關聯對象也可以訪問 Manager 對象,但是這個 Manager 表現一個單獨的對象,而不是一個列表:
```
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
```
如果一個空對象被賦予關聯關系,Django 就會拋出一個 DoesNotExist 異常。
和你定義正向關聯所用的方式一樣,類的實例也可以賦予逆向關聯方系:
```
e.entrydetail = ed
```
## 關系中的反向連接是如何做到的? ##
其他對象關系的映射(ORM)需要你在關聯雙方都定義關系。而 Django 的開發者則認為這違背了 DRY 原則 (Don't Repeat Yourself),所以 Django 只需要你在一方定義關系即可。
但僅由一個 model 類并不能知道其他 model 類是如何與它關聯的,除非是其他 model 也被載入,那么這是如何辦到的?
答案就在于 INSTALLED_APPS 設置中。任何一個 model 在第一次調用時,Django 就會遍歷所有的 INSTALLED_APPS 的所有 models,并且在內存中創建中必要的反向連接。本質上來說,INSTALLED_APPS 的作用之一就是確認 Django 完整的 model 范圍。
## 在關聯對象上的查詢 ##
包含關聯對象的查詢與包含普通字段值的查詢都遵循相同的規則。為某個查詢指定某個值的時候,你可以使用一個類實例,也可以使用對象的主鍵值。
例如,如果你有一個 Blog 對象 b ,它的 id=5, 下面三個查詢是一樣的:
```
Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly
```
## 直接使用SQL ##
如果你發現某個 SQL 查詢用 Django 的數據庫映射來處理會非常復雜的話,你可以使用直接寫 SQL 來完成。
建議的方式是在你的 model 自定義方法或是自定義 model 的 manager 方法來運行查詢。雖然 Django 不要求數據操作必須在 model 層中執行。但是把你的商業邏輯代碼放在一個地方,從代碼組織的角度來看,也是十分明智的。詳情請查看 執行原生SQL查詢(Performing raw SQL queries).
最后,要注意的是,Django的數據操作層僅僅是訪問數據庫的一個接口。你可以用其他的工具,編程語言,數據庫框架來訪問數據庫。對你的數據庫而言,沒什么是非用 Django 不可的。
> 譯者:[Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html),原文:[Executing queries](https://docs.djangoproject.com/en/1.8/topics/db/queries/)。
>
> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 協議發布,轉載請保留作者署名和文章出處。
>
> [Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html)人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架