django 1.8 官方文檔翻譯: 2-2-1 執(zhí)行查詢(初稿)

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

交流群:467338606

網(wǎng)站:http://python.usyiyi.cn/django/index.html

執(zhí)行查詢

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

在整個(gè)文檔(以及參考)中,我們會(huì)大量使用下面的模型,它構(gòu)成了一個(gè)博客應(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)建對(duì)象

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

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

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

>>> 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不會(huì)訪問(wèn)數(shù)據(jù)庫(kù)。

save()方法沒(méi)有返回值。

請(qǐng)參見

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

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

保存對(duì)象的改動(dòng)

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

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

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

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

保存ForeignKeyManyToManyField字段

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

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

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

為了在一條語(yǔ)句中,向ManyToManyField添加多條記錄,可以在調(diào)用add()方法時(shí)傳入多個(gè)參數(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將會(huì)在你添加錯(cuò)誤類型的對(duì)象時(shí)拋出異常。

獲取對(duì)象

通過(guò)模型中的Manager構(gòu)造一個(gè)QuertSet,來(lái)從你的數(shù)據(jù)庫(kù)中獲取對(duì)象。

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

你可以從模型的Manager那里取得QuerySet。每個(gè)模型都至少有一個(gè)Manager,它通常命名為objects。通過(guò)模型類直接訪問(wèn)它,像這樣:

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

注意

管理器通常只可以通過(guò)模型類來(lái)訪問(wèn),不可以通過(guò)模型實(shí)例來(lái)訪問(wèn)。這是為了強(qiáng)制區(qū)分表級(jí)別和記錄級(jí)別的操作。

對(duì)于一個(gè)模型來(lái)說(shuō),ManagerQuerySet的主要來(lái)源。例如,** Blog.objects.all() 會(huì)返回持有數(shù)據(jù)庫(kù)中所有Blog對(duì)象的一個(gè)QuerySet**。

獲取所有對(duì)象

獲取一個(gè)表中所有對(duì)象的最簡(jiǎn)單的方式是全部獲取。使用Managerall()方法:

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

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

使用過(guò)濾器獲取特定對(duì)象

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

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

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

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

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

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

Entry.objects.filter(pub_date__year=2006)

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

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

鏈?zhǔn)竭^(guò)濾

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

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

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

過(guò)濾后的結(jié)果集是獨(dú)立的

每次你篩選一個(gè)結(jié)果集,得到的都是全新的另一個(gè)結(jié)果集,它和之前的結(jié)果集之間沒(méi)有任何綁定關(guān)系。每次篩選都會(huì)創(chuàng)建一個(gè)獨(dú)立的結(jié)果集,可以被存儲(chǔ)及反復(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())

這三個(gè) QuerySets 是不同的。 第一個(gè) QuerySet 包含大標(biāo)題以"What"開頭的所有記錄。第二個(gè)則是第一個(gè)的子集,用一個(gè)附加的條件排除了出版日期 pub_date 是今天的記錄。 第三個(gè)也是第一個(gè)的子集,它只保留出版日期 pub_date 是今天的記錄。 最初的 QuerySet (q1) 沒(méi)有受到篩選的影響。

查詢集是延遲的

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

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

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

其他查詢集方法

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

限制查詢集范圍

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

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

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

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

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

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

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

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

要檢索單獨(dú)的對(duì)象,而非列表 (比如 SELECT foo FROM bar LIMIT 1),可以直接使用索引來(lái)代替切片。舉個(gè)例子,下面這段代碼將返回大標(biāo)題排序后的第一條記錄 Entry:

>>> Entry.objects.order_by('headline')[0]

大約等價(jià)于:

>>> Entry.objects.order_by('headline')[0:1].get()

要注意的是:如果找不到符合條件的對(duì)象,第一種方法會(huì)拋出 IndexError ,而第二種方法會(huì)拋出 DoesNotExist。 詳看 get() 。

字段篩選條件

字段篩選條件就是 SQL 語(yǔ)句中的 WHERE 從句。就是 Django 中的 QuerySet 的 filter(), exclude() 和 get() 方法中的關(guān)鍵字參數(shù)。

篩選條件的形式是 field__lookuptype=value 。 (注意:這里是雙下劃線)。例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

大體可以翻譯為如下的 SQL 語(yǔ)句:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

這是怎么辦到的?

Python 允許函式接受任意多 name-value 形式的參數(shù),并在運(yùn)行時(shí)才確定name和value的值。詳情請(qǐng)參閱官方Python教程中的 關(guān)鍵字參數(shù)(Keyword Arguments)。

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

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

exact

"exact" 匹配。例如:

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

會(huì)生成如下的 SQL 語(yǔ)句:

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

如果你沒(méi)有提供查詢類型 -- 也就是說(shuō)關(guān)鍵字參數(shù)中沒(méi)有雙下劃線,那么查詢類型就會(huì)被指定為 exact。

舉個(gè)例子,這兩個(gè)語(yǔ)句是相等的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied

這樣做很方便,因?yàn)?exact 是最常用的。

iexact

忽略大小寫的匹配。所以下面的這個(gè)查詢:

>>> Blog.objects.get(name__iexact="beatles blog")

會(huì)匹配標(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'。

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

startswith, endswith

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

跨關(guān)系查詢

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

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

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

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

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

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

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

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

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

(假設(shè)關(guān)聯(lián)到 Author 類), 如果沒(méi)有哪個(gè) author 與 entry 相關(guān)聯(lián),Django 會(huì)認(rèn)為它沒(méi)有 name 屬性,而不會(huì)因?yàn)椴淮嬖?author 拋出異常。通常來(lái)說(shuō),這正是你所希望的機(jī)制。唯一的例外是使用 isnull 的情況。如下:

Blog.objects.filter(entry__author__name__isnull=True)

這段代碼會(huì)得到 author 的 name 為空的 Blog 或 entry 的 author為空的 Blog。 如果不嫌麻煩,可以這樣寫:

Blog.objects.filter (entry__author__isnull=False,
        entry__author__name__isnull=True)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

你也可以在 F() 對(duì)象中使用兩個(gè)下劃線做跨關(guān)系查詢。F() 對(duì)象使用兩個(gè)下劃線引入必要的關(guān)聯(lián)對(duì)象。例如,要查詢博客(blog)名稱與作者(author)名稱相同的博文(entry),查詢就可以這樣寫:

>>> Entry.objects.filter(author__name=F('blog__name'))

主鍵查詢的簡(jiǎn)捷方式

為使用方便考慮,Django 用 pk 代表主鍵"primary key"。

以 Blog 為例, 主鍵是 id 字段,所以下面三個(gè)語(yǔ)句都是等價(jià)的:

>>> 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 對(duì) __exact 查詢同樣有效,任何查詢項(xiàng)都可以用 pk 來(lái)構(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)系,下面三個(gè)語(yǔ)句是等價(jià)的:

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

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

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

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

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

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

下劃線_和百分號(hào)%的處理方式相同,Django 都會(huì)自動(dòng)轉(zhuǎn)義。

緩存和查詢

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

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

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

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

這就意味著相同的數(shù)據(jù)庫(kù)查詢將執(zhí)行兩次,事實(shí)上讀取了兩次數(shù)據(jù)庫(kù)。而且,這兩次讀出來(lái)的列表可能并不完全相同,因?yàn)榇嬖谶@種可能:在兩次讀取之間,某個(gè) Entry 被添加到數(shù)據(jù)庫(kù)中,或是被刪除了。

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

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

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

例如,下面這個(gè) Q 對(duì)象封裝了一個(gè)單獨(dú)的 LIKE 查詢:

Q(question__startswith='What')

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

例如,下面這段語(yǔ)句就產(chǎn)生了一個(gè) Q ,這是用 "OR" 關(guān)系連接的兩個(gè) "question__startswith" 查詢:

Q(question__startswith='Who') | Q(question__startswith='What')

上面的例子等價(jià)于下面的 SQL WHERE 從句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你可以用 & 和 | 連接任意多的 Q 對(duì)象,而且可以用括號(hào)分組。Q 對(duì)象也可以用 ~ 操作取反,而且普通查詢和取反查詢(NOT)可以連接在一起使用:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每種查詢函式(比如 filter(), exclude(), get()) 除了能接收關(guān)鍵字參數(shù)以外,也能以位置參數(shù)的形式接受一個(gè)或多個(gè) Q 對(duì)象。如果你給查詢函式傳遞了多個(gè) Q 對(duì)象,那么它們彼此間都是 "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 對(duì)象和關(guān)鍵字參數(shù)。查詢函式的所有參數(shù)(Q 關(guān)系和關(guān)鍵字參數(shù)) 都是 "AND" 關(guān)系。但是,如果參數(shù)中有 Q 對(duì)象,它必須排在所有的關(guān)鍵字參數(shù)之前。例如:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who')

... 是一個(gè)有效的查詢。但下面這個(gè)查詢雖然看上去和前者等價(jià):

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

... 但這個(gè)查詢卻是無(wú)效的。

參見

在 Django 的單元測(cè)試 OR查詢實(shí)例(OR lookups examples) 中展示了 Q 的用例。

對(duì)象比較

要比較兩個(gè)對(duì)象,就和 Python 一樣,使用雙等號(hào)運(yùn)算符:==。實(shí)際上比較的是兩個(gè) model 的主鍵值。

以上面的 Entry 為例,下面兩個(gè)語(yǔ)句是等價(jià)的:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

如果 model 的主鍵名稱不是 id,也沒(méi)關(guān)系。Django 會(huì)自動(dòng)比較主鍵的值,而不管他們的名稱是什么。例如,如果一個(gè) model 的主鍵字段名稱是 name,那么下面兩個(gè)語(yǔ)句是等價(jià)的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

對(duì)象刪除

刪除方法就是 delete()。它運(yùn)行時(shí)立即刪除對(duì)象而不返回任何值。例如:

e.delete()

你也可以一次性刪除多個(gè)對(duì)象。每個(gè) QuerySet 都有一個(gè) delete() 方法,它一次性刪除 QuerySet 中所有的對(duì)象。

例如,下面的代碼將刪除 pub_date 是2005年的 Entry 對(duì)象:

Entry.objects.filter(pub_date__year=2005).delete()

要牢記這一點(diǎn):無(wú)論在什么情況下,QuerySet 中的 delete() 方法都只使用一條 SQL 語(yǔ)句一次性刪除所有對(duì)象,而并不是分別刪除每個(gè)對(duì)象。如果你想使用在 model 中自定義的 delete() 方法,就要自行調(diào)用每個(gè)對(duì)象的delete 方法。(例如,遍歷 QuerySet,在每個(gè)對(duì)象上調(diào)用 delete()方法),而不是使用 QuerySet 中的 delete()方法。

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

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

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

Entry.objects.all().delete()

一次更新多個(gè)對(duì)象 (Updating multiple objects at once)

這部分是 Django 1.0 中新增的: 請(qǐng)查看版本文檔
有時(shí)你想對(duì) QuerySet 中的所有對(duì)象,一次更新某個(gè)字段的值。這個(gè)要求可以用 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)系字段時(shí),傳入的值應(yīng)該是一個(gè)常量。更新 ForeignKey 字段時(shí),傳入的值應(yīng)該是你想關(guān)聯(lián)的那個(gè)類的某個(gè)實(shí)例。例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

update() 方法也是即時(shí)生效,不返回任何值的(與 delete() 相似)。 在 QuerySet 進(jìn)行更新時(shí),唯一的限制就是一次只能更新一個(gè)數(shù)據(jù)表,就是當(dāng)前 model 的主表。所以不要嘗試更新關(guān)聯(lián)表和與此類似的操作,因?yàn)檫@是不可能運(yùn)行的。

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

for item in my_queryset:
    item.save()

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

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

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

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

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

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

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

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

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

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

一對(duì)多關(guān)系

正向

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

例如:

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

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

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

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

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

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

>>> 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() 方法提前將所有的一對(duì)多關(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 有一個(gè) ForeignKey外鍵字段,那么外聯(lián) model 的實(shí)例可以通過(guò)訪問(wèn) Manager 來(lái)得到所有相關(guān)聯(lián)的源 model 的實(shí)例。默認(rèn)情況下,這個(gè) Manager 被命名為 FOO_set, 這里面的 FOO 就是源 model 的小寫名稱。這個(gè) Manager 返回 QuerySets,它是可過(guò)濾和可操作的,在上面 "對(duì)象獲取(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()

你可以通過(guò)在 ForeignKey() 的定義中設(shè)置 related_name 的值來(lái)覆寫 FOO_set 的名稱。例如,如果 Entry model 中做一下更改: blog = ForeignKey(Blog, related_name='entries'),那么接下來(lái)就會(huì)如我們看到這般:

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

你不能在一個(gè)類當(dāng)中訪問(wèn) ForeignKey Manager ;而必須通過(guò)類的實(shí)例來(lái)訪問(wèn):

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

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

add(obj1, obj2, ...)

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

create(**kwargs)

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

remove(obj1, obj2, ...)

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

clear()

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

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

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

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

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

多對(duì)多關(guān)系

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

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

下面這個(gè)例子會(huì)讓人更容易理解:

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',那么接下來(lái)每個(gè) Author 實(shí)例的 entry_set 屬性都被 entries 所代替。

一對(duì)一關(guān)系

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

例如:

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

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

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

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

e.entrydetail = ed

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

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

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

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

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

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

例如,如果你有一個(gè) Blog 對(duì)象 b ,它的 id=5, 下面三個(gè)查詢是一樣的:

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)某個(gè) SQL 查詢用 Django 的數(shù)據(jù)庫(kù)映射來(lái)處理會(huì)非常復(fù)雜的話,你可以使用直接寫 SQL 來(lái)完成。

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

最后,要注意的是,Django的數(shù)據(jù)操作層僅僅是訪問(wèn)數(shù)據(jù)庫(kù)的一個(gè)接口。你可以用其他的工具,編程語(yǔ)言,數(shù)據(jù)庫(kù)框架來(lái)訪問(wèn)數(shù)據(jù)庫(kù)。對(duì)你的數(shù)據(jù)庫(kù)而言,沒(méi)什么是非用 Django 不可的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容