鍍金池/ 教程/ Python/ 執(zhí)行查詢
點擊劫持保護
安全問題歸檔
Model 類參考
將遺留數(shù)據(jù)庫整合到Django
關(guān)聯(lián)對象參考
內(nèi)建基于類的視圖的API
聚合
Django 中的用戶認(rèn)證
django.contrib.humanize
Django管理文檔生成器
分頁
使用Django輸出CSV
加密簽名
文件儲存API
安全
Django中的測試
國際化和本地化
為Django編寫首個補丁
條件表達(dá)式
日志
模型元選項
部署靜態(tài)文件
執(zhí)行查詢
使用Django認(rèn)證系統(tǒng)
基于類的視圖
中間件
編寫自定義的django-admin命令
Django 的設(shè)置
格式本地化
數(shù)據(jù)庫訪問優(yōu)化
錯誤報告
基于類的內(nèi)建通用視圖
編寫自定義存儲系統(tǒng)
編寫你的第一個 Django 程序 第3部分
編寫數(shù)據(jù)庫遷移
使用表單
編寫你的第一個 Django 程序 第2部分
編寫你的第一個 Django 程序 第1部分
如何使用會話
系統(tǒng)檢查框架
新手入門
信號
編寫視圖
如何使用WSGI 部署
編寫你的第一個Django應(yīng)用,第6部分
常見的網(wǎng)站應(yīng)用工具
Widgets
內(nèi)建的視圖
模型實例參考
視圖層
Django中的密碼管理
高級教程:如何編寫可重用的應(yīng)用
國際化和本地化
"本地特色"附加功能
TemplateResponse 和 SimpleTemplateResponse
模式編輯器
文件上傳
快速安裝指南
部署 Django
表單 API
表單素材 ( <code>Media</code> 類)
管理文件
其它核心功能
查找 API 參考
表單
Admin
數(shù)據(jù)庫函數(shù)
自定義查找
使用基于類的視圖處理表單
管理操作
開發(fā)過程
編寫你的第一個Django應(yīng)用,第5部分
進(jìn)行原始的sql查詢
模型層
多數(shù)據(jù)庫
編寫你的第一個 Django 程序 第4部分
Django安全
Django 初探
Django異常
重定向應(yīng)用
按需內(nèi)容處理
管理器
視圖裝飾器
驗證器
使用Django輸出PDF
File對象
Django 的快捷函數(shù)
基于類的通用視圖 —— 索引
為模型提供初始數(shù)據(jù)
模板層
URL調(diào)度器
中間件
模型

執(zhí)行查詢

一旦你建立好數(shù)據(jù)模型之后,django會自動生成一套數(shù)據(jù)庫抽象的API,可以讓你執(zhí)行增刪改查的操作。這篇文檔闡述了如何使用這些API。關(guān)于所有模型檢索選項的詳細(xì)內(nèi)容,請見數(shù)據(jù)模型參考。

在整個文檔(以及參考)中,我們會大量使用下面的模型,它構(gòu)成了一個博客應(yīng)用。

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

創(chuàng)建對象

為了把數(shù)據(jù)庫表中的數(shù)據(jù)表示成python對象,django使用一種直觀的方式:一個模型類代表數(shù)據(jù)庫的一個表,一個模型的實例代表數(shù)據(jù)庫表中的一條特定的記錄。

使用關(guān)鍵詞參數(shù)實例化一個對象來創(chuàng)建它,然后調(diào)用save()把它保存到數(shù)據(jù)庫中。

假設(shè)模型存放于文件mysite/blog/models.py中,下面是一個例子:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

上面的代碼在背后執(zhí)行了sql的INSERT操作。在你顯式調(diào)用save()之前,django不會訪問數(shù)據(jù)庫。

save()方法沒有返回值。

請參見

save()方法帶有一些高級選項,它們沒有在這里給出,完整的細(xì)節(jié)請見save()文檔。

如果你想只用一條語句創(chuàng)建并保存一個對象,使用create()方法。

保存對象的改動

調(diào)用save()方法,來保存已經(jīng)存在于數(shù)據(jù)庫中的對象的改動。

假設(shè)一個Blog的實例b5已經(jīng)被保存在數(shù)據(jù)庫中,這個例子更改了它的名字,并且在數(shù)據(jù)庫中更新它的記錄:

>>> b5.name = 'New name'
>>> b5.save()

上面的代碼在背后執(zhí)行了sql的UPDATE操作。在你顯式調(diào)用save()之前,django不會訪問數(shù)據(jù)庫。

保存ForeignKeyManyToManyField字段

更新ForeignKey字段的方式和保存普通字段相同--只是簡單地把一個類型正確的對象賦值到字段中。下面的例子更新了Entry類的實例entryblog屬性,假設(shè)Entry的一個合適的實例以及Blog已經(jīng)保存在數(shù)據(jù)庫中(我們可以像下面那樣獲取他們):

>>> 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()方法來增加關(guān)系的記錄。這個例子向entry對象添加Author類的實例joe

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

為了在一條語句中,向ManyToManyField添加多條記錄,可以在調(diào)用add()方法時傳入多個參數(shù),像這樣:

>>> 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構(gòu)造一個QuertSet,來從你的數(shù)據(jù)庫中獲取對象。

QuerySet表示你數(shù)據(jù)庫中取出來的一個對象的集合。它可以含有零個、一個或者多個過濾器,過濾器根據(jù)所給的參數(shù)限制查詢結(jié)果的范圍。在sql的角度,QuerySetSELECT命令等價,過濾器是像WHERELIMIT一樣的限制子句。

你可以從模型的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."

注意

管理器通常只可以通過模型類來訪問,不可以通過模型實例來訪問。這是為了強制區(qū)分表級別和記錄級別的操作。

對于一個模型來說,ManagerQuerySet的主要來源。例如, Blog.objects.all() 會返回持有數(shù)據(jù)庫中所有Blog對象的一個QuerySet。

獲取所有對象

獲取一個表中所有對象的最簡單的方式是全部獲取。使用Managerall()方法:

>>> all_entries = Entry.objects.all()

all()方法返回包含數(shù)據(jù)庫中所有對象的QuerySet

使用過濾器獲取特定對象

all()方法返回的結(jié)果集中包含全部對象,但是更普遍的情況是你需要獲取完整集合的一個子集。

要創(chuàng)建這樣一個子集,需要精煉上面的結(jié)果集,增加一些過濾器作為條件。兩個最普遍的途徑是:

filter(**kwargs) 返回一個包含對象的集合,它們滿足參數(shù)中所給的條件。

exclude(**kwargs) 返回一個包含對象的集合,它們滿足參數(shù)中所給的條件。

查詢參數(shù)(上面函數(shù)定義中的**kwargs)需要滿足特定的格式,字段檢索一節(jié)中會提到。

舉個例子,要獲取年份為2006的所有文章的結(jié)果集,可以這樣使用filter()方法:

Entry.objects.filter(pub_date__year=2006)

在默認(rèn)的管理器類中,它相當(dāng)于:

Entry.objects.all().filter(pub_date__year=2006)

鏈?zhǔn)竭^濾

QuerySet的精煉結(jié)果還是QuerySet,所以你可以把精煉用的語句組合到一起,像這樣:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )

最開始的QuerySet包含數(shù)據(jù)庫中的所有對象,之后增加一個過濾器去掉一部分,在之后又是另外一個過濾器。最后的結(jié)果的一個QuerySet,包含所有標(biāo)題以”word“開頭的記錄,并且日期是2005年一月,日為當(dāng)天的值。

過濾后的結(jié)果集是獨立的

每次你篩選一個結(jié)果集,得到的都是全新的另一個結(jié)果集,它和之前的結(jié)果集之間沒有任何綁定關(guān)系。每次篩選都會創(chuàng)建一個獨立的結(jié)果集,可以被存儲及反復(fù)使用。

例如:

>>> 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 包含大標(biāo)題以"What"開頭的所有記錄。第二個則是第一個的子集,用一個附加的條件排除了出版日期 pub_date 是今天的記錄。 第三個也是第一個的子集,它只保留出版日期 pub_date 是今天的記錄。 最初的 QuerySet (q1) 沒有受到篩選的影響。

查詢集是延遲的

QuerySets 是惰性的 -- 創(chuàng)建 QuerySet 的動作不涉及任何數(shù)據(jù)庫操作。你可以一直添加過濾器,在這個過程中,Django 不會執(zhí)行任何數(shù)據(jù)庫查詢,除非 QuerySet 被執(zhí)行. 看看下面這個例子:

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q

雖然上面的代碼看上去象是三個數(shù)據(jù)庫操作,但實際上只在最后一行 (print q) 執(zhí)行了一次數(shù)據(jù)庫操作,。一般情況下, QuerySet 不能從數(shù)據(jù)庫中主動地獲得數(shù)據(jù),得被動地由你來請求。對 QuerySet 求值就意味著 Django 會訪問數(shù)據(jù)庫。想了解對查詢集何時求值,請查看 何時對查詢集求值 (When QuerySets are evaluated).

其他查詢集方法

大多數(shù)情況使用 all(), filter() 和 exclude() 就足夠了。 但也有一些不常用的;請查看 查詢API參考 (QuerySet API Reference) 中完整的 QuerySet 方法列表。

限制查詢集范圍

可以用 python 的數(shù)組切片語法來限制你的 QuerySet 以得到一部分結(jié)果。它等價于SQL中的 LIMIT 和 OFFSET 。

例如,下面的這個例子返回前五個對象 (LIMIT 5):

>>> Entry.objects.all()[:5]

這個例子返回第六到第十之間的對象 (OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

Django 不支持對查詢集做負(fù)數(shù)索引 (例如 Entry.objects.all()[-1]) 。

一般來說,對 QuerySet 切片會返回新的 QuerySet -- 這個過程中不會對運行查詢。不過也有例外,如果你在切片時使用了 "step" 參數(shù),查詢集就會被求值,就在數(shù)據(jù)庫中運行查詢。舉個例子,使用下面這個這個查詢集返回前十個對象中的偶數(shù)次對象,就會運行數(shù)據(jù)庫查詢:

>>> Entry.objects.all()[:10:2]

要檢索單獨的對象,而非列表 (比如 SELECT foo FROM bar LIMIT 1),可以直接使用索引來代替切片。舉個例子,下面這段代碼將返回大標(biāo)題排序后的第一條記錄 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() 方法中的關(guān)鍵字參數(shù)。

篩選條件的形式是 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 形式的參數(shù),并在運行時才確定name和value的值。詳情請參閱官方Python教程中的 關(guān)鍵字參數(shù)(Keyword Arguments)。

如果你傳遞了一個無效的關(guān)鍵字參數(shù),會拋出 TypeError 導(dǎo)常。

數(shù)據(jù)庫 API 支持24種查詢類型;可以在 字段篩選參考(field lookup reference) 查看詳細(xì)的列表。為了給您一個直觀的認(rèn)識,這里我們列出一些常用的查詢類型:

exact

"exact" 匹配。例如:

>>> Entry.objects.get(headline__exact="Man bites dog")

會生成如下的 SQL 語句:

SELECT ... WHERE headline = 'Man bites dog';

如果你沒有提供查詢類型 -- 也就是說關(guān)鍵字參數(shù)中沒有雙下劃線,那么查詢類型就會被指定為 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")

會匹配標(biāo)題是 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog

contains

大小寫敏感的模糊匹配。 例如:

Entry.objects.get(headline__contains='Lennon')

大體可以翻譯為如下的 SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

要注意這段代碼匹配大標(biāo)題 'Today Lennon honored' ,而不能匹配 'today lennon honored'。

它也有一個忽略大小寫的版本,就是 icontains。

startswith, endswith

分別匹配開頭和結(jié)尾,同樣也有忽略大小寫的版本 istartswith 和 iendswith。 再強調(diào)一次,這僅僅是簡短介紹。完整的參考請參見 字段篩選條件參考(field lookup reference)。

跨關(guān)系查詢

Django 提供了一種直觀而高效的方式在查詢(lookups)中表示關(guān)聯(lián)關(guān)系,它能自動確認(rèn) SQL JOIN 聯(lián)系。要做跨關(guān)系查詢,就使用兩個下劃線來鏈接模型(model)間關(guān)聯(lián)字段的名稱,直到最終鏈接到你想要的 model 為止。

這個例子檢索所有關(guān)聯(lián) Blog 的 name 值為 'Beatles Blog' 的所有 Entry 對象:

>>> Entry.objects.filter(blog__name__exact='Beatles Blog')

跨關(guān)系的篩選條件可以一直延展。

關(guān)系也是可逆的??梢栽谀繕?biāo) model 上使用源 model 名稱的小寫形式得到反向關(guān)聯(lián)。

下面這個例子檢索至少關(guān)聯(lián)一個 Entry 且大標(biāo)題 headline 包含 'Lennon' 的所有 Blog 對象:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

如果在某個關(guān)聯(lián) model 中找不到符合過濾條件的對象,Django 將視它為一個空的 (所有的值都是 NULL), 但是可用的對象。這意味著不會有異常拋出,在這個例子中:

Blog.objects.filter(entry__author__name='Lennon')

(假設(shè)關(guān)聯(lián)到 Author 類), 如果沒有哪個 author 與 entry 相關(guān)聯(lián),Django 會認(rèn)為它沒有 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)

跨一對多/多對多關(guān)系(Spanning multi-valued relationships)

這部分是Django 1.0中新增的: 請查看版本記錄 如果你的過濾是基于 ManyToManyField 或是逆向 ForeignKeyField 的,你可能會對下面這兩種情況感興趣?;仡?Blog/Entry 的關(guān)系(Blog 到 Entry 是一對多關(guān)系),如果要查找這樣的 blog:它關(guān)聯(lián)一個大標(biāo)題包含"Lennon",且在2008年出版的 entry ;或者要查找這樣的 blogs:它關(guān)聯(lián)一個大標(biāo)題包含"Lennon"的 entry ,同時它又關(guān)聯(lián)另外一個在2008年出版的 entry 。因為一個 Blog 會關(guān)聯(lián)多個的Entry,所以上述兩種情況在現(xiàn)實應(yīng)用中是很有可能出現(xiàn)的。

同樣的情形也出現(xiàn)在 ManyToManyField 上。例如,如果 Entry 有一個 ManyToManyField 字段,名字是 tags,我們想得到 tags 是"music"和"bands"的 entries,或者我們想得到包含名為"music" 的標(biāo)簽而狀態(tài)是"public"的 entry。

針對這兩種情況,Django 用一種很方便的方式來使用 filter() 和 exclude()。對于包含在同一個 filter() 中的篩選條件,查詢集要同時滿足所有篩選條件。而對于連續(xù)的 filter() ,查詢集的范圍是依次限定的。但對于跨一對多/多對多關(guān)系查詢來說,在第二種情況下,篩選條件針對的是主 model 所有的關(guān)聯(lián)對象,而不是被前面的 filter() 過濾后的關(guān)聯(lián)對象。

這聽起來會讓人迷糊,舉個例子會講得更清楚。要檢索這樣的 blog:它要關(guān)系一個大標(biāo)題中含有 "Lennon" 并且在2008年出版的 entry (這個 entry 同時滿足這兩個條件),可以這樣寫:

Blog.objects.filter(entry__headline__contains='Lennon',
        entry__pub_date__year=2008)

要檢索另外一種 blog:它關(guān)聯(lián)一個大標(biāo)題含有"Lennon"的 entry ,又關(guān)聯(lián)一個在2008年出版的 entry (一個 entry 的大標(biāo)題含有 Lennon,同一個或另一個 entry 是在2008年出版的)??梢赃@樣寫:

Blog.objects.filter(entry__headline__contains='Lennon').filter(
        entry__pub_date__year=2008)

在第二個例子中,第一個過濾器(filter)先檢索與符合條件的 entry 的相關(guān)聯(lián)的所有 blogs。第二個過濾器在此基礎(chǔ)上從這些 blogs 中檢索與第二種 entry 也相關(guān)聯(lián)的 blog。第二個過濾器選擇的 entry 可能與第一個過濾器所選擇的完全相同,也可能不同。 因為過濾項過濾的是 Blog,而不是 Entry。

上述原則同樣適用于 exclude():一個單獨 exclude() 中的所有篩選條件都是作用于同一個實例 (如果這些條件都是針對同一個一對多/多對多的關(guān)系)。連續(xù)的 filter() 或 exclude() 卻根據(jù)同樣的篩選條件,作用于不同的關(guān)聯(lián)對象。

在過濾器中引用 model 中的字段(Filters can reference fields on the model)

這部分是 Django 1.1 新增的: 請查看版本記錄 在上面所有的例子中,我們構(gòu)造的過濾器都只是將字段值與某個常量做比較。如果我們要對兩個字段的值做比較,那該怎么做呢?

Django 提供 F() 來做這樣的比較。F() 的實例可以在查詢中引用字段,來比較同一個 model 實例中兩個不同字段的值。

例如:要查詢回復(fù)數(shù)(comments)大于廣播數(shù)(pingbacks)的博文(blog entries),可以構(gòu)造一個 F() 對象在查詢中引用評論數(shù)量:

>>> from django.db.models import F
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments'))

Django 支持 F() 對象之間以及 F() 對象和常數(shù)之間的加減乘除和取模的操作。例如,要找到廣播數(shù)等于評論數(shù)兩倍的博文,可以這樣修改查詢語句:

>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2)

要查找閱讀數(shù)量小于評論數(shù)與廣播數(shù)之和的博文,查詢?nèi)缦?

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你也可以在 F() 對象中使用兩個下劃線做跨關(guān)系查詢。F() 對象使用兩個下劃線引入必要的關(guān)聯(lián)對象。例如,要查詢博客(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 來構(gòu)造基于主鍵的查詢:

# 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 查詢也可以跨關(guān)系,下面三個語句是等價的:

>>> 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語句中轉(zhuǎn)義百分號%和下劃線_

字段篩選條件相當(dāng)于 LIKE SQL 語句 (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) ,它會自動轉(zhuǎn)義兩個特殊符號 -- 百分號%和下劃線_。(在 LIKE 語句中,百分號%表示多字符匹配,而下劃線_表示單字符匹配。)

這就意味著我們可以直接使用這兩個字符,而不用考慮他們的 SQL 語義。例如,要查詢大標(biāo)題中含有一個百分號%的 entry:

>>> Entry.objects.filter(headline__contains='%')

Django 會處理轉(zhuǎn)義;最終的 SQL 看起來會是這樣:

SELECT ... WHERE headline LIKE '%\%%';

下劃線_和百分號%的處理方式相同,Django 都會自動轉(zhuǎn)義。

緩存和查詢

每個 QuerySet 都包含一個緩存,以減少對數(shù)據(jù)庫的訪問。要編寫高效代碼,就要理解緩存是如何工作的。

一個 QuerySet 時剛剛創(chuàng)建的時候,緩存是空的。 QuerySet 第一次運行時,會執(zhí)行數(shù)據(jù)庫查詢,接下來 Django 就在 QuerySet 的緩存中保存查詢的結(jié)果,并根據(jù)請求返回這些結(jié)果(比如,后面再次調(diào)用這個 QuerySet 的時候)。再次運行 QuerySet 時就會重用這些緩存結(jié)果。

要牢住上面所說的緩存行為,否則在使用 QuerySet 時可能會給你造成不小的麻煩。例如,創(chuàng)建下面兩個 QuerySet ,并對它們求值,然后釋放:

>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]

這就意味著相同的數(shù)據(jù)庫查詢將執(zhí)行兩次,事實上讀取了兩次數(shù)據(jù)庫。而且,這兩次讀出來的列表可能并不完全相同,因為存在這種可能:在兩次讀取之間,某個 Entry 被添加到數(shù)據(jù)庫中,或是被刪除了。

要避免這個問題,只要簡單地保存 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 對象實現(xiàn)復(fù)雜查找 (Complex lookups with Q objects)

在 filter() 等函式中關(guān)鍵字參數(shù)彼此之間都是 "AND" 關(guān)系。如果你要執(zhí)行更復(fù)雜的查詢(比如,實現(xiàn)篩選條件的 OR 關(guān)系),可以使用 Q 對象。

Q 對象(django.db.models.Q)是用來封裝一組查詢關(guān)鍵字的對象。這里提到的查詢關(guān)鍵字請查看上面的 "Field lookups"。

例如,下面這個 Q 對象封裝了一個單獨的 LIKE 查詢:

Q(question__startswith='What')

Q 對象可以用 & 和 | 運算符進(jìn)行連接。當(dāng)某個操作連接兩個 Q 對象時,就會產(chǎn)生一個新的等價的 Q 對象。

例如,下面這段語句就產(chǎn)生了一個 Q ,這是用 "OR" 關(guān)系連接的兩個 "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()) 除了能接收關(guān)鍵字參數(shù)以外,也能以位置參數(shù)的形式接受一個或多個 Q 對象。如果你給查詢函式傳遞了多個 Q 對象,那么它們彼此間都是 "AND" 關(guān)系。例如:

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 對象和關(guān)鍵字參數(shù)。查詢函式的所有參數(shù)(Q 關(guān)系和關(guān)鍵字參數(shù)) 都是 "AND" 關(guān)系。但是,如果參數(shù)中有 Q 對象,它必須排在所有的關(guān)鍵字參數(shù)之前。例如:

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,也沒關(guān)系。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() 方法,就要自行調(diào)用每個對象的delete 方法。(例如,遍歷 QuerySet,在每個對象上調(diào)用 delete()方法),而不是使用 QuerySet 中的 delete()方法。

在 Django 刪除對象時,會模仿 SQL 約束 ON DELETE CASCADE 的行為,換句話說,刪除一個對象時也會刪除與它相關(guān)聯(lián)的外鍵對象。例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

要注意的是: delete() 方法是 QuerySet 上的方法,但并不適用于 Manager 本身。這是一種保護機制,是為了避免意外地調(diào)用 Entry.objects.delete() 方法導(dǎo)致 所有的 記錄被誤刪除。如果你確認(rèn)要刪除所有的對象,那么你必須顯式地調(diào)用:

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')

這種方法僅適用于非關(guān)系字段和 ForeignKey 外鍵字段。更新非關(guān)系字段時,傳入的值應(yīng)該是一個常量。更新 ForeignKey 字段時,傳入的值應(yīng)該是你想關(guān)聯(lián)的那個類的某個實例。例如:

>>> 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 進(jìn)行更新時,唯一的限制就是一次只能更新一個數(shù)據(jù)表,就是當(dāng)前 model 的主表。所以不要嘗試更新關(guān)聯(lián)表和與此類似的操作,因為這是不可能運行的。

要小心的是: update() 方法是直接翻譯成一條 SQL 語句的。因此它是直接地一次完成所有更新。它不會調(diào)用你的 model 中的 save() 方法,也不會發(fā)出 pre_save 和 post_save 信號(這些信號在調(diào)用 save() 方法時產(chǎn)生)。如果你想保存 QuerySet 中的每個對象,并且調(diào)用每個對象各自的 save() 方法,那么你不必另外多寫一個函式。只要遍歷這些對象,依次調(diào)用 save() 方法即可:

for item in my_queryset:
    item.save()

這部分是在 Django 1.1 中新增的: 請查看版本文檔 在調(diào)用 update 時可以使用 F() 對象 來把某個字段的值更新為另一個字段的值。這對于自增記數(shù)器是非常有用的。例如,給所有的博文 (entry) 的廣播數(shù) (pingback) 加一:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

但是,與 F() 對象在查詢時所不同的是,在filter 和 exclude子句中,你不能在 F() 對象中引入關(guān)聯(lián)關(guān)系(NO-Join),你只能引用當(dāng)前 model 中要更新的字段。如果你在 F() 對象引入了Join 關(guān)系object,就會拋出 FieldError 異常:

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

對象關(guān)聯(lián)

當(dāng)你定義在 model 定義關(guān)系時 (例如, ForeignKey, OneToOneField, 或 ManyToManyField),model 的實例自帶一套很方便的API以獲取關(guān)聯(lián)的對象。

以最上面的 models 為例,一個 Entry 對象 e 能通過 blog 屬性獲得相關(guān)聯(lián)的 Blog 對象: e.blog。

(在場景背后,這個功能是由 Python 的 descriptors 實現(xiàn)的。如果你對此感興趣,可以了解一下。)

Django 也提供反向獲取關(guān)聯(lián)對象的 API,就是由從被關(guān)聯(lián)的對象得到其定義關(guān)系的主對象。例如,一個 Blog 類的實例 b 對象通過 entry_set 屬性得到所有相關(guān)聯(lián)的 Entry 對象列表: b.entry_set.all()。

這一節(jié)所有的例子都使用本頁頂部所列出的 Blog, Author 和 Entry model。

一對多關(guān)系

正向

如果一個 model 有一個 ForeignKey字段,我們只要通過使用關(guān)聯(lián) model 的名稱就可以得到相關(guān)聯(lián)的外鍵對象。

例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.

你可以設(shè)置和獲得外鍵屬性。正如你所期望的,改變外鍵的行為并不引發(fā)數(shù)據(jù)庫操作,直到你調(diào)用 save()方法時,才會保存到數(shù)據(jù)庫。例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()

如果外鍵字段 ForeignKey 有一個 null=True 的設(shè)置(它允許外鍵接受空值 NULL),你可以賦給它空值 None 。例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

在一對多關(guān)系中,第一次正向獲取關(guān)聯(lián)對象時,關(guān)聯(lián)對象會被緩存。其后根據(jù)外鍵訪問時這個實例,就會從緩存中獲得它。例如:

>>> 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() 方法提前將所有的一對多關(guān)系放入緩存中。例如:

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

逆向關(guān)聯(lián)

如果 model 有一個 ForeignKey外鍵字段,那么外聯(lián) model 的實例可以通過訪問 Manager 來得到所有相關(guān)聯(lián)的源 model 的實例。默認(rèn)情況下,這個 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() 的定義中設(shè)置 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()

你不能在一個類當(dāng)中訪問 ForeignKey Manager ;而必須通過類的實例來訪問:

>>> Blog.entry_set
Traceback:
    ...
AttributeError: "Manager must be accessed via instance".

除了在上面 "對象獲取Retrieving objects" 一節(jié)中提到的 QuerySet 方法之外,F(xiàn)oreignKey Manager 還有如下一些方法。下面僅僅對它們做一個簡短介紹,詳情請查看 related objects reference。

add(obj1, obj2, ...)

將某個特定的 model 對象添加到被關(guān)聯(lián)對象集合中。

create(**kwargs)

創(chuàng)建并保存一個新對象,然后將這個對象加被關(guān)聯(lián)對象的集合中,然后返回這個新對象。

remove(obj1, obj2, ...)

將某個特定的對象從被關(guān)聯(lián)對象集合中去除。

clear()

清空被關(guān)聯(lián)對象集合。 想一次指定關(guān)聯(lián)集合的成員,那么只要給關(guān)聯(lián)集合分配一個可迭代的對象即可。它可以包含對象的實例,也可以只包含主鍵的值。例如:

b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]

在這個例子中,e1 和 e2 可以是完整的 Entry 實例,也可以是整型的主鍵值。

如果 clear() 方法是可用的,在迭代器(上例中就是一個列表)中的對象加入到 entry_set 之前,已存在于關(guān)聯(lián)集合中的所有對象將被清空。如果 clear() 方法 不可用,原有的關(guān)聯(lián)集合中的對象就不受影響,繼續(xù)存在。

這一節(jié)提到的每一個 "reverse" 操作都是實時操作數(shù)據(jù)庫的,每一個添加,創(chuàng)建,刪除操作都會及時保存將結(jié)果保存到數(shù)據(jù)庫中。

多對多關(guān)系

在多對多關(guān)系的任何一方都可以使用 API 訪問相關(guān)聯(lián)的另一方。多對多的 API 用起來和上面提到的 "逆向" 一對多關(guān)系關(guān)系非常相象。

唯一的差雖就在于屬性的命名: ManyToManyField 所在的 model (為了方便,我稱之為源model A) 使用字段本身的名稱來訪問關(guān)聯(lián)對象;而被關(guān)聯(lián)的另一方則使用 A 的小寫名稱加上 '_set' 后綴(這與逆向的一對多關(guān)系非常相象)。

下面這個例子會讓人更容易理解:

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 所代替。

一對一關(guān)系

相對于多對一關(guān)系而言,一對一關(guān)系不是非常簡單的。如果你在 model 中定義了一個 OneToOneField 關(guān)系,那么你就可以用這個字段的名稱做為屬性來訪問其所關(guān)聯(lián)的對象。

例如:

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" 查詢不同的是,一對一關(guān)系的關(guān)聯(lián)對象也可以訪問 Manager 對象,但是這個 Manager 表現(xiàn)一個單獨的對象,而不是一個列表:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

如果一個空對象被賦予關(guān)聯(lián)關(guān)系,Django 就會拋出一個 DoesNotExist 異常。

和你定義正向關(guān)聯(lián)所用的方式一樣,類的實例也可以賦予逆向關(guān)聯(lián)方系:

e.entrydetail = ed

關(guān)系中的反向連接是如何做到的?

其他對象關(guān)系的映射(ORM)需要你在關(guān)聯(lián)雙方都定義關(guān)系。而 Django 的開發(fā)者則認(rèn)為這違背了 DRY 原則 (Don't Repeat Yourself),所以 Django 只需要你在一方定義關(guān)系即可。

但僅由一個 model 類并不能知道其他 model 類是如何與它關(guān)聯(lián)的,除非是其他 model 也被載入,那么這是如何辦到的?

答案就在于 INSTALLED_APPS 設(shè)置中。任何一個 model 在第一次調(diào)用時,Django 就會遍歷所有的 INSTALLED_APPS 的所有 models,并且在內(nèi)存中創(chuàng)建中必要的反向連接。本質(zhì)上來說,INSTALLED_APPS 的作用之一就是確認(rèn) Django 完整的 model 范圍。

在關(guān)聯(lián)對象上的查詢

包含關(guān)聯(lián)對象的查詢與包含普通字段值的查詢都遵循相同的規(guī)則。為某個查詢指定某個值的時候,你可以使用一個類實例,也可以使用對象的主鍵值。

例如,如果你有一個 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

如果你發(fā)現(xiàn)某個 SQL 查詢用 Django 的數(shù)據(jù)庫映射來處理會非常復(fù)雜的話,你可以使用直接寫 SQL 來完成。

建議的方式是在你的 model 自定義方法或是自定義 model 的 manager 方法來運行查詢。雖然 Django 不要求數(shù)據(jù)操作必須在 model 層中執(zhí)行。但是把你的商業(yè)邏輯代碼放在一個地方,從代碼組織的角度來看,也是十分明智的。詳情請查看 執(zhí)行原生SQL查詢(Performing raw SQL queries).

最后,要注意的是,Django的數(shù)據(jù)操作層僅僅是訪問數(shù)據(jù)庫的一個接口。你可以用其他的工具,編程語言,數(shù)據(jù)庫框架來訪問數(shù)據(jù)庫。對你的數(shù)據(jù)庫而言,沒什么是非用 Django 不可的。

譯者:Django 文檔協(xié)作翻譯小組,原文:Executing queries

本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。

Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。