Django 1.8.2 文檔Home | Table of contents | Index | Modules? previous | up | next ?執(zhí)行查詢?一旦你建立好數(shù)據(jù)模型轮傍,Django 會自動為你生成一套數(shù)據(jù)庫抽象的API诺祸,可以讓你創(chuàng)建刚照、檢索故慈、更新和刪除對象合住。這篇文檔闡述如何使用這些API周崭。 關(guān)于模型查詢所有選項的完整細(xì)節(jié),請見數(shù)據(jù)模型參考扛点。在整個文檔(以及參考)中哥遮,我們將引用下面的模型,它構(gòu)成一個博客應(yīng)用:
from django.db import modelsclass Blog(models.Model):? ? name = models.CharField(max_length=100)? ? tagline = models.TextField()? ? def __str__(self):? ? ? ? ? ? ? # __unicode__ on Python 2? ? ? ? return self.nameclass Author(models.Model):? ? name = models.CharField(max_length=50)? ? email = models.EmailField()? ? def __str__(self):? ? ? ? ? ? ? # __unicode__ on Python 2? ? ? ? return self.nameclass 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)建對象
?Django 使用一種直觀的方式把數(shù)據(jù)庫表中的數(shù)據(jù)表示成Python 對象:一個模型類代表數(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()方法玖瘸。
保存對象的改動
?要保存對數(shù)據(jù)庫中已存在的對象的改動秸讹,請使用save()。假設(shè)Blog 的一個實例b5 已經(jīng)被保存在數(shù)據(jù)庫中雅倒,下面這個例子將更改它的name 并且更新數(shù)據(jù)庫中的記錄:>>> b5.name = 'New name'>>> b5.save()上面的代碼在背后執(zhí)行SQL 的UPDATE語句璃诀。在你顯式調(diào)用save()之前,Django不會訪問數(shù)據(jù)庫屯断。保存ForeignKey和ManyToManyField字段?更新ForeignKey 字段的方式和保存普通字段相同 —— 只要把一個正確類型的對象賦值給該字段文虏。下面的例子更新了Entry 類的實例entry 的blog 屬性,假設(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)聯(lián)關(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 將會在你賦值或添加錯誤類型的對象時報錯。獲取對象?通過模型中的管理器構(gòu)造一個查詢集搔确,來從你的數(shù)據(jù)庫中獲取對象彼棍。查詢集表示從數(shù)據(jù)庫中取出來的對象的集合。它可以含有零個膳算、一個或者多個過濾器座硕。過濾器基于所給的參數(shù)限制查詢的結(jié)果。 從SQL 的角度涕蜂,查詢集和SELECT 語句等價华匾,過濾器是像WHERE 和LIMIT 一樣的限制子句。你可以從模型的管理器那里取得查詢集机隙。每個模型都至少有一個管理器蜘拉,它默認(rèn)命名為objects。通過模型類來直接訪問它有鹿,像這樣:
>>> Blog.objects>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
注
管理器只可以通過模型的類訪問旭旭,而不可以通過模型的實例訪問,目的是為了強(qiáng)制區(qū)分“表級別”的操作和“記錄級別”的操作葱跋。
對于一個模型來說持寄,管理器是查詢集的主要來源源梭。例如,Blog.objects.all() 返回包含數(shù)據(jù)庫中所有Blog 對象的一個查詢集稍味。
獲取所有對象?
獲取一個表中所有對象的最簡單的方式是全部獲取咸产。可以使用管理器的all() 方法:
>>> all_entries = Entry.objects.all()
all()方法返回包含數(shù)據(jù)庫中所有對象的一個查詢集仲闽。
使用過濾器獲取特定對象?
all() 方法返回了一個包含數(shù)據(jù)庫表中所有記錄查詢集脑溢。但在通常情況下,你往往想要獲取的是完整數(shù)據(jù)集的一個子集赖欣。
要創(chuàng)建這樣一個子集屑彻,你需要在原始的的查詢集上增加一些過濾條件。兩個最普遍的途徑是:
filter(**kwargs)
返回一個新的查詢集顶吮,它包含滿足查詢參數(shù)的對象社牲。
exclude(**kwargs)
返回一個新的查詢集,它包含不滿足查詢參數(shù)的對象悴了。
查詢參數(shù)(上面函數(shù)定義中的**kwargs)需要滿足特定的格式搏恤,下面字段查詢一節(jié)中會提到。
舉個例子湃交,要獲取年份為2006的所有文章的查詢集熟空,可以使用filter()方法:
Entry.objects.filter(pub_date__year=2006)
利用默認(rèn)的管理器,它相當(dāng)于:
Entry.objects.all().filter(pub_date__year=2006)
鏈?zhǔn)竭^濾?
查詢集的篩選結(jié)果本身還是查詢集搞莺,所以可以將篩選語句鏈接在一起息罗。像這樣:
>>> Entry.objects.filter(
...? ? headline__startswith='What'
... ).exclude(
...? ? pub_date__gte=datetime.date.today()
... ).filter(
...? ? pub_date__gte=datetime(2005, 1, 30)
... )
這個例子最開始獲取數(shù)據(jù)庫中所有對象的一個查詢集,之后增加一個過濾器才沧,然后又增加一個排除迈喉,再之后又是另外一個過濾器。最后的結(jié)果仍然是一個查詢集温圆,它包含標(biāo)題以”What“開頭挨摸、發(fā)布日期在2005年1月30日至當(dāng)天之間的所有記錄。
過濾后的查詢集是獨立的?
每次你篩選一個查詢集岁歉,得到的都是全新的另一個查詢集得运,它和之前的查詢集之間沒有任何綁定關(guān)系。每次篩選都會創(chuàng)建一個獨立的查詢集刨裆,它可以被存儲及反復(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())
這三個查詢集都是獨立的。第一個是一個基礎(chǔ)的查詢集帆啃,包含所有標(biāo)題以“What”開頭的記錄瞬女。第二個查詢集是第一個的子集,它增加另外一個限制條件努潘,排除pub_date 為今天和將來的記錄诽偷。第三個查詢集同樣是第一個的子集坤学,它增加另外一個限制條件,只選擇pub_date 為今天或?qū)淼挠涗洷健T嫉牟樵兗?q1)不會受到篩選過程的影響深浮。
查詢集是惰性執(zhí)行的?
查詢集 是惰性執(zhí)行的 —— 創(chuàng)建查詢集不會帶來任何數(shù)據(jù)庫的訪問。你可以將過濾器保持一整天眠冈,直到查詢集 需要求值時飞苇,Django 才會真正運行這個查詢∥贤纾看下這個例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
雖然它看上去有三次數(shù)據(jù)庫訪問布卡,但事實上只有在最后一行(print(q))時才訪問一次數(shù)據(jù)庫。一般來說雇盖,只有在“請求”查詢集 的結(jié)果時才會到數(shù)據(jù)庫中去獲取它們忿等。當(dāng)你確實需要結(jié)果時,查詢集 通過訪問數(shù)據(jù)庫來求值崔挖。 關(guān)于求值發(fā)生的準(zhǔn)確時間贸街,參見何時計算查詢集。
通過get 獲取一個單一的對象?
filter() 始終給你一個查詢集狸相,即使只有一個對象滿足查詢條件 —— 這種情況下薛匪,查詢集將只包含一個元素。
如果你知道只有一個對象滿足你的查詢卷哩,你可以使用管理器的get() 方法蛋辈,它直接返回該對象:
>>> one_entry = Entry.objects.get(pk=1)
可以對get() 使用任何查詢表達(dá)式,和filter() 一樣 —— 同樣請查看下文的字段查詢将谊。
注意,使用get() 和使用filter() 的切片[0] 有一點區(qū)別渐白。如果沒有結(jié)果滿足查詢尊浓,get() 將引發(fā)一個DoesNotExist 異常。這個異常是正在查詢的模型類的一個屬性 —— 所以在上面的代碼中纯衍,如果沒有主鍵為1 的Entry 對象栋齿,Django 將引發(fā)一個Entry.DoesNotExist。
類似地襟诸,如果有多條記錄滿足get() 的查詢條件瓦堵,Django 也將報錯。這種情況將引發(fā)MultipleObjectsReturned歌亲,它同樣是模型類自身的一個屬性菇用。
其它查詢集方法?
大多數(shù)情況下,需要從數(shù)據(jù)庫中查找對象時陷揪,你會使用all()惋鸥、 get()杂穷、filter() 和exclude()。 然而卦绣,這只是冰山一角耐量;查詢集 方法的完整列表,請參見查詢集API 參考滤港。
限制查詢集?
可以使用Python 的切片語法來限制查詢集記錄的數(shù)目 廊蜒。它等同于SQL 的LIMIT 和OFFSET 子句。
例如溅漾,下面的語句返回前面5 個對象(LIMIT 5):
>>> Entry.objects.all()[:5]
下面這條語句返回第6 至第10 個對象(OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
不支持負(fù)的索引(例如Entry.objects.all()[-1])山叮。
通常,查詢集 的切片返回一個新的查詢集 —— 它不會執(zhí)行查詢樟凄。有一個例外聘芜,是如果你使用Python 切片語法中"step"參數(shù)。例如缝龄,下面的語句將返回前10 個對象中每隔2個對象汰现,它將真實執(zhí)行查詢:
>>> Entry.objects.all()[:10:2]
若要獲取一個單一的對象而不是一個列表(例如,SELECT foo FROM bar LIMIT 1)叔壤,可以簡單地使用一個索引而不是切片瞎饲。例如,下面的語句返回數(shù)據(jù)庫中根據(jù)標(biāo)題排序后的第一條Entry:
>>> Entry.objects.order_by('headline')[0]
它大體等同于:
>>> Entry.objects.order_by('headline')[0:1].get()
然而請注意炼绘,如果沒有對象滿足給定的條件嗅战,第一條語句將引發(fā)IndexError而第二條語句將引發(fā)DoesNotExist。 更多細(xì)節(jié)參見get()俺亮。
字段查詢?
字段查詢是指如何指定SQL WHERE 子句的內(nèi)容驮捍。它們通過查詢集方法filter()、exclude() 和 get() 的關(guān)鍵字參數(shù)指定脚曾。
查詢的關(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';
這是如何實現(xiàn)的
Python 定義的函數(shù)可以接收任意的鍵/值對參數(shù)本讥,這些名稱和參數(shù)可以在運行時求值珊泳。更多信息,參見Python 官方文檔中的關(guān)鍵字參數(shù)拷沸。
查詢條件中指定的字段必須是模型字段的名稱色查。但有一個例外,對于ForeignKey你可以使用字段名加上_id 后綴撞芍。在這種情況下秧了,該參數(shù)的值應(yīng)該是外鍵的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果你傳遞的是一個不合法的參數(shù)勤庐,查詢函數(shù)將引發(fā) TypeError示惊。
這些數(shù)據(jù)庫API 支持大約二十多種查詢的類型好港;在字段查詢參考 中可以找到完整的參考。為了讓你嘗嘗鮮米罚,下面是一些你可能用到的常見查詢:
exact
“精確”匹配钧汹。例如:
>>> Entry.objects.get(headline__exact="Man bites dog")
將生成下面的SQL:
SELECT ... WHERE headline = 'Man bites dog';
如果你沒有提供查詢類型 —— 即如果你的關(guān)鍵字參數(shù)不包含雙下劃線 —— 默認(rèn)假定查詢類型是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
大小寫敏感的包含關(guān)系測試尊剔。例如:
Entry.objects.get(headline__contains='Lennon')
大體可以翻譯成下面的SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
注意,這將匹配'Today Lennon honored' 但不能匹配'today lennon honored'菱皆。
還有一個大小寫不敏感的版本须误,icontains。
startswith, endswith
分別表示以XXX開頭和以XXX結(jié)尾仇轻。當(dāng)然還有大小寫不敏感的版本京痢,叫做istartswith 和 iendswith。
同樣篷店,這里只是表面祭椰。完整的參考可以在字段查詢參考中找到。
跨關(guān)聯(lián)關(guān)系的查詢?
Django 提供一種強(qiáng)大而又直觀的方式來“處理”查詢中的關(guān)聯(lián)關(guān)系疲陕,它在后臺自動幫你處理JOIN方淤。 若要跨越關(guān)聯(lián)關(guān)系,只需使用關(guān)聯(lián)的模型字段的名稱蹄殃,并使用雙下劃線分隔臣淤,直至你想要的字段:
下面這個例子獲取所有Blog 的name 為'Beatles Blog' 的Entry 對象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
這種跨越可以是任意的深度。
它還可以反向工作窃爷。若要引用一個“反向”的關(guān)系,只需要使用該模型的小寫的名稱姓蜂。
下面的示例獲取所有的Blog 對象按厘,它們至少有一個Entry 的headline 包含'Lennon':
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果你在多個關(guān)聯(lián)關(guān)系直接過濾而且其中某個中介模型沒有滿足過濾條件的值,Django 將把它當(dāng)做一個空的(所有的值都為NULL)但是合法的對象钱慢。這意味著不會有錯誤引發(fā)逮京。例如,在下面的過濾器中:
Blog.objects.filter(entry__authors__name='Lennon')
(如果有一個相關(guān)聯(lián)的Author 模型)束莫,如果Entry 中沒有找到對應(yīng)的author懒棉,那么它將當(dāng)作其沒有name草描,而不會因為沒有author 引發(fā)一個錯誤。通常策严,這就是你想要的穗慕。唯一可能讓你困惑的是當(dāng)你使用isnull 的時候。因此:
Blog.objects.filter(entry__authors__name__isnull=True)
返回的Blog 對象包括author __name 為空的Blog對象,以及author__name不為空但author__name關(guān)聯(lián)的entry __author 為空的對象妻导。如果你不需要后者逛绵,你可以這樣寫:
Blog.objects.filter(entry__authors__isnull=False,
entry__authors__name__isnull=True)
跨越多值的關(guān)聯(lián)關(guān)系?
當(dāng)你基于ManyToManyField 或反向的ForeignKey 來過濾一個對象時,有兩種不同種類的過濾器倔韭∈趵耍考慮Blog/Entry 關(guān)聯(lián)關(guān)系(Blog 和 Entry 是一對多的關(guān)系)。我們可能想找出headline為“Lennon” 并且pub_date為'2008'年的Entry寿酌∫人眨或者我們可能想查詢headline為“Lennon” 的Entry或者pub_date為'2008'的Entry。因為實際上有和單個Blog 相關(guān)聯(lián)的多個Entry醇疼,所以這兩個查詢在某些場景下都是有可能并有意義的硕并。
ManyToManyField 有類似的情況。例如僵腺,如果Entry 有一個ManyToManyField 叫做 tags鲤孵,我們可能想找到tag 叫做“music” 和“bands” 的Entry,或者我們想找一個tag 名為“music” 且狀態(tài)為“public”的Entry辰如。
對于這兩種情況普监,Django 有種一致的方法來處理filter() 調(diào)用。一個filter() 調(diào)用中的所有參數(shù)會同時應(yīng)用以過濾出滿足所有要求的記錄琉兜。接下來的filter() 調(diào)用進(jìn)一步限制對象集凯正,但是對于多值關(guān)系,它們應(yīng)用到與主模型關(guān)聯(lián)的對象豌蟋,而不是應(yīng)用到前一個filter() 調(diào)用選擇出來的對象廊散。
這些聽起來可能有點混亂,所以希望展示一個例子使它變得更清晰梧疲。選擇所有包含同時滿足兩個條件的entry的blog允睹,這兩個條件是headline 包含Lennon 和發(fā)表時間是2008 (同一個entry 滿足兩個條件),我們的代碼是:
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
從所有的blog模型實例中選擇滿足以下條件的blog實例:blog的enrty的headline屬性值是“Lennon”幌氮,或者entry的發(fā)表時間是2008(兩個條件至少滿足一個缭受,也可以同時滿足),我們的代碼是:
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)
假設(shè)這里有一個blog擁有一條包含'Lennon'的entries條目和一條來自2008的entries條目,但是沒有一條來自2008并且包含"Lennon"的entries條目该互。第一個查詢不會返回任何blog米者,第二個查詢將會返回一個blog。
在第二個例子中, 第一個filter 限定查詢集中的blog 與headline 包含“Lennon” 的entry 關(guān)聯(lián)蔓搞。第二個filter 又 限定查詢集中的blog 胰丁,這些blog關(guān)聯(lián)的entry 的發(fā)表時間是2008。(譯者注:難點在如何理解further這個詞N狗帧)第二個filter 過濾出來的entry 與第一個filter 過濾出來的entry 可能相同也可能不同锦庸。每個filter 語句過濾的是Blog,而不是Entry妻顶。
注
跨越多值關(guān)系的filter() 查詢的行為酸员,與exclude() 實現(xiàn)的不同。單個exclude() 調(diào)用中的條件不必引用同一個記錄讳嘱。
例如幔嗦,下面的查詢排除headline 中包含“Lennon”的Entry和在2008 年發(fā)布的Entry:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
然而,這與使用filter() 的行為不同沥潭,它不是排除同時滿足兩個條件的Entry邀泉。為了實現(xiàn)這點,即選擇的Blog中不包含在2008年發(fā)布且healine 中帶有“Lennon” 的Entry钝鸽,你需要編寫兩個查詢:
Blog.objects.exclude(
entry=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Filter 可以引用模型的字段?
到目前為止給出的示例中汇恤,我們構(gòu)造過將模型字段與常量進(jìn)行比較的filter。但是拔恰,如果你想將模型的一個字段與同一個模型的另外一個字段進(jìn)行比較該怎么辦因谎?
Django 提供F 表達(dá)式 來允許這樣的比較。F() 返回的實例用作查詢內(nèi)部對模型字段的引用颜懊。這些引用可以用于查詢的filter 中來比較相同模型實例上不同字段之間值的比較财岔。
例如,為了查找comments 數(shù)目多于pingbacks 的Entry河爹,我們將構(gòu)造一個F() 對象來引用pingback 數(shù)目匠璧,并在查詢中使用該F() 對象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持對F() 對象使用加法、減法咸这、乘法夷恍、除法、取模以及冪計算等算術(shù)操作媳维,兩個操作數(shù)可以都是常數(shù)和其它F() 對象酿雪。為了查找comments 數(shù)目比pingbacks 兩倍還要多的Entry,我們將查詢修改為:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
New in Django 1.7:
添加 ** 操作符侄刽。
為了查詢rating 比pingback 和comment 數(shù)目總和要小的Entry执虹,我們將這樣查詢:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
你還可以在F() 對象中使用雙下劃線標(biāo)記來跨越關(guān)聯(lián)關(guān)系。帶有雙下劃線的F() 對象將引入任何需要的join 操作以訪問關(guān)聯(lián)的對象唠梨。例如,如要獲取author 的名字與blog 名字相同的Entry侥啤,我們可以這樣查詢:
>>> Entry.objects.filter(authors__name=F('blog__name'))
對于date 和date/time 字段当叭,你可以給它們加上或減去一個timedelta 對象茬故。下面的例子將返回發(fā)布超過3天后被修改的所有Entry:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F() 對象支持.bitand() 和.bitor() 兩種位操作,例如:
>>> F('somefield').bitand(16)
查詢的快捷方式pk?
為了方便蚁鳖,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 結(jié)合來完成一個模型上對主鍵的查詢:
# 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查詢在join 中也可以工作。例如讥裤,下面三個語句是等同的:
>>> 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
轉(zhuǎn)義LIKE 語句中的百分號和下劃線?
與LIKE SQL 語句等同的字段查詢(iexact放棒、 contains、icontains己英、startswith间螟、 istartswith、endswith 和iendswith)將自動轉(zhuǎn)義在LIKE 語句中使用的兩個特殊的字符 —— 百分號和下劃線损肛。(在LIKE 語句中厢破,百分號通配符表示多個字符,下劃線通配符表示單個字符)治拿。
這意味著語句將很直觀摩泪,不會顯得太抽象。例如劫谅,要獲取包含一個百分號的所有的Entry见坑,只需要像其它任何字符一樣使用百分號:
>>> Entry.objects.filter(headline__contains='%')
Django 會幫你轉(zhuǎn)義;生成的SQL 看上去會是這樣:
SELECT ... WHERE headline LIKE '%\%%';
對于下劃線是同樣的道理同波。百分號和下劃線都會透明地幫你處理鳄梅。
緩存和查詢集?
每個查詢集都包含一個緩存來最小化對數(shù)據(jù)庫的訪問。理解它是如何工作的將讓你編寫最高效的代碼未檩。
在一個新創(chuàng)建的查詢集中戴尸,緩存為空。首次對查詢集進(jìn)行求值 —— 同時發(fā)生數(shù)據(jù)庫查詢 ——Django 將保存查詢的結(jié)果到查詢集的緩存中并返回明確請求的結(jié)果(例如冤狡,如果正在迭代查詢集孙蒙,則返回下一個結(jié)果)。接下來對該查詢集 的求值將重用緩存的結(jié)果悲雳。
請牢記這個緩存行為挎峦,因為對查詢集使用不當(dāng)?shù)脑挘鼤幽愕暮掀啊@缣菇海旅娴恼Z句創(chuàng)建兩個查詢集,對它們求值,然后扔掉它們:
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
這意味著相同的數(shù)據(jù)庫查詢將執(zhí)行兩次顿苇,顯然倍增了你的數(shù)據(jù)庫負(fù)載峭咒。同時,還有可能兩個結(jié)果列表并不包含相同的數(shù)據(jù)庫記錄纪岁,因為在兩次請求期間有可能有Entry被添加進(jìn)來或刪除掉凑队。
為了避免這個問題,只需保存查詢集并重新使用它:
>>> queryset = Entry.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.
何時查詢集不會被緩存?
查詢集不會永遠(yuǎn)緩存它們的結(jié)果幔翰。當(dāng)只對查詢集的部分進(jìn)行求值時會檢查緩存漩氨, 但是如果這個部分不在緩存中,那么接下來查詢返回的記錄都將不會被緩存遗增。特別地叫惊,這意味著使用切片或索引來限制查詢集將不會填充緩存。
例如贡定,重復(fù)獲取查詢集對象中一個特定的索引將每次都查詢數(shù)據(jù)庫:
>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database
>>> print queryset[5] # Queries the database again
然而赋访,如果已經(jīng)對全部查詢集求值過,則將檢查緩存:
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print queryset[5] # Uses cache
>>> print queryset[5] # Uses cache
下面是一些其它例子缓待,它們會使得全部的查詢集被求值并填充到緩存中:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
注
簡單地打印查詢集不會填充緩存蚓耽。因為__repr__() 調(diào)用只返回全部查詢集的一個切片。
使用Q 對象進(jìn)行復(fù)雜的查詢?
filter() 等方法中的關(guān)鍵字參數(shù)查詢都是一起進(jìn)行“AND” 的旋炒。 如果你需要執(zhí)行更復(fù)雜的查詢(例如OR 語句)步悠,你可以使用Q 對象。
Q 對象 (django.db.models.Q) 對象用于封裝一組關(guān)鍵字參數(shù)瘫镇。這些關(guān)鍵字參數(shù)就是上文“字段查詢” 中所提及的那些鼎兽。
例如,下面的Q 對象封裝一個LIKE 查詢:
from django.db.models import Q
Q(question__startswith='What')
Q 對象可以使用& 和| 操作符組合起來铣除。當(dāng)一個操作符在兩個Q 對象上使用時谚咬,它產(chǎn)生一個新的Q 對象。
例如,下面的語句產(chǎn)生一個Q 對象,表示兩個"question__startswith" 查詢的“OR” :
Q(question__startswith='Who') | Q(question__startswith='What')
它等同于下面的SQL WHERE 子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
你可以組合& 和|? 操作符以及使用括號進(jìn)行分組來編寫任意復(fù)雜的Q 對象吼旧。同時,Q 對象可以使用~ 操作符取反秉继,這允許組合正常的查詢和取反(NOT) 查詢:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每個接受關(guān)鍵字參數(shù)的查詢函數(shù)(例如filter()、exclude()泽铛、get())都可以傳遞一個或多個Q 對象作為位置(不帶名的)參數(shù)尚辑。如果一個查詢函數(shù)有多個Q 對象參數(shù),這些參數(shù)的邏輯關(guān)系為“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')
查詢函數(shù)可以混合使用Q 對象和關(guān)鍵字參數(shù)杠茬。所有提供給查詢函數(shù)的參數(shù)(關(guān)鍵字參數(shù)或Q 對象)都將"AND”在一起月褥。但是,如果出現(xiàn)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 查詢示例演示了幾種Q 的用法盐杂。
比較對象?
為了比較兩個模型實例逗载,只需要使用標(biāo)準(zhǔn)的Python 比較操作符,即雙等于符號:==链烈。在后臺厉斟,它會比較兩個模型主鍵的值。
利用上面的Entry 示例强衡,下面兩個語句是等同的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
如果模型的主鍵不叫id擦秽,也沒有問題。比較將始終使用主鍵漩勤,無論它叫什么感挥。例如,如果模型的主鍵字段叫做name越败,下面的兩條語句是等同的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
刪除對象?
刪除方法触幼,為了方便,就取名為delete()究飞。這個方法將立即刪除對象且沒有返回值置谦。例如:
e.delete()
你還可以批量刪除對象。每個查詢集 都有一個delete() 方法亿傅,它將刪除該查詢集中的所有成員媒峡。
例如,下面的語句刪除pub_date 為2005 的所有Entry 對象:
Entry.objects.filter(pub_date__year=2005).delete()
記住葵擎,這將盡可能地使用純SQL 執(zhí)行谅阿,所以這個過程中不需要調(diào)用每個對象實例的delete()方法。如果你給模型類提供了一個自定義的delete() 方法并希望確保它被調(diào)用坪蚁,你需要手工刪除該模型的實例(例如奔穿,迭代查詢集并調(diào)用每個對象的delete())而不能使用查詢集的批量delete() 方法。
當(dāng)Django 刪除一個對象時敏晤,它默認(rèn)使用SQL ON DELETE CASCADE 約束 —— 換句話講贱田,任何有外鍵指向要刪除對象的對象將一起刪除。例如:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
這種級聯(lián)的行為可以通過的ForeignKey 的on_delete 參數(shù)自定義嘴脾。
注意男摧,delete() 是唯一沒有在管理器 上暴露出來的查詢集方法蔬墩。這是一個安全機(jī)制來防止你意外地請求Entry.objects.delete(),而刪除所有 的條目耗拓。如果你確實想刪除所有的對象拇颅,你必須明確地請求一個完全的查詢集:
Entry.objects.all().delete()
拷貝模型實例?
雖然沒有內(nèi)建的方法用于拷貝模型實例,但還是很容易創(chuàng)建一個新的實例并讓它的所有字段都拷貝過來乔询。最簡單的方法是樟插,只需要將pk 設(shè)置為None。利用我們的Blog 示例:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
如果你用繼承竿刁,那么會復(fù)雜一些黄锤。考慮下面Blog 的子類:
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
由于繼承的工作方式食拜,你必須設(shè)置pk 和 id 都為None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
這個過程不會拷貝關(guān)聯(lián)的對象鸵熟。如果你想拷貝關(guān)聯(lián)關(guān)系,你必須編寫一些更多的代碼负甸。在我們的例子中流强,Entry 有一個到Author 的多對多字段:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations
一次更新多個對象?
有時你想為一個查詢集中所有對象的某個字段都設(shè)置一個特定的值。這時你可以使用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)聯(lián)字段和ForeignKey 字段使用這個方法打月。若要更新一個非關(guān)聯(lián)字段,只需提供一個新的常數(shù)值带污。若要更新ForeignKey 字段僵控,需設(shè)置新的值為你想指向的新的模型實例。例如:
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
update() 方法會立即執(zhí)行并返回查詢匹配的行數(shù)(如果有些行已經(jīng)具有新的值鱼冀,返回的行數(shù)可能和被更新的行數(shù)不相等)报破。更新查詢集 唯一的限制是它只能訪問一個數(shù)據(jù)庫表,也就是模型的主表千绪。你可以根據(jù)關(guān)聯(lián)的字段過濾充易,但是你只能更新模型主表中的列。例如:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
要注意update() 方法會直接轉(zhuǎn)換成一個SQL 語句荸型。它是一個批量的直接更新操作盹靴。它不會運行模型的save() 方法,或者發(fā)出pre_save 或 post_save信號(調(diào)用save()方法產(chǎn)生)或者查看auto_now 字段選項瑞妇。如果你想保存查詢集中的每個條目并確保每個實例的save() 方法都被調(diào)用稿静,你不需要使用任何特殊的函數(shù)來處理。只需要迭代它們并調(diào)用save():
for item in my_queryset:
item.save()
對update 的調(diào)用也可以使用F 表達(dá)式 來根據(jù)模型中的一個字段更新另外一個字段辕狰。這對于在當(dāng)前值的基礎(chǔ)上加上一個值特別有用改备。例如,增加Blog 中每個Entry 的pingback 個數(shù):
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然而蔓倍,與filter 和exclude 子句中的F() 對象不同悬钳,在update 中你不可以使用F() 對象引入join —— 你只可以引用正在更新的模型的字段盐捷。如果你嘗試使用F() 對象引入一個join,將引發(fā)一個FieldError:
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))
關(guān)聯(lián)的對象?
當(dāng)你在一個模型中定義一個關(guān)聯(lián)關(guān)系時(例如默勾,F(xiàn)oreignKey碉渡、 OneToOneField 或ManyToManyField),該模型的實例將帶有一個方便的API 來訪問關(guān)聯(lián)的對象母剥。
利用本頁頂部的模型滞诺,一個Entry 對象e 可以通過blog 屬性e.blog 獲取關(guān)聯(lián)的Blog 對象。
(在幕后环疼,這個功能是通過Python 的描述器實現(xiàn)的铭段。這應(yīng)該不會對你有什么真正的影響,但是這里我們指出它以滿足你的好奇)秦爆。
Django 還會創(chuàng)建API 用于訪問關(guān)聯(lián)關(guān)系的另一頭 —— 從關(guān)聯(lián)的模型訪問定義關(guān)聯(lián)關(guān)系的模型。例如憔披,Blog 對象b 可以通過entry_set 屬性 b.entry_set.all()訪問與它關(guān)聯(lián)的所有Entry 對象等限。
這一節(jié)中的所有示例都將使用本頁頂部定義的Blog、 Author 和Entry 模型芬膝。
一對多關(guān)系?
前向查詢?
如果一個模型具有ForeignKey望门,那么該模型的實例將可以通過屬性訪問關(guān)聯(lián)的(外部)對象。
例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
你可以通過外鍵屬性獲取和設(shè)置锰霜。和你預(yù)期的一樣筹误,對外鍵的修改不會保存到數(shù)據(jù)庫中直至你調(diào)用save()。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
如果ForeignKey 字段有null=True 設(shè)置(即它允許NULL 值)癣缅,你可以分配None 來刪除對應(yīng)的關(guān)聯(lián)性厨剪。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
一對多關(guān)聯(lián)關(guān)系的前向訪問在第一次訪問關(guān)聯(lián)的對象時被緩存。以后對同一個對象的外鍵的訪問都使用緩存友存。例如:
>>> 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.
注意select_related() 查詢集方法遞歸地預(yù)填充所有的一對多關(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.
反向查詢?
如果模型有一個ForeignKey,那么該ForeignKey 所指的模型實例可以通過一個管理器返回前一個有ForeignKey的模型的所有實例屡立。默認(rèn)情況下直晨,這個管理器的名字為foo_set,其中foo 是源模型的小寫名稱膨俐。該管理器返回的查詢集可以用上一節(jié)提到的方式進(jìn)行過濾和操作勇皇。
例如:
>>> 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 參數(shù)來覆蓋foo_set 的名稱。例如焚刺,如果Entry 模型改成blog = ForeignKey(Blog, related_name='entries')敛摘,那么上面的示例代碼應(yīng)該改成這樣:
>>> 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()
使用自定義的反向管理器?
New in Django 1.7.
默認(rèn)情況下,用于反向關(guān)聯(lián)關(guān)系的RelatedManager 是該模型默認(rèn)管理器 的子類檩坚。如果你想為一個查詢指定一個不同的管理器着撩,你可以使用下面的語法:
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager()? # Default Manager
entries = EntryManager()? ? # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
如果EntryManager 在它的get_queryset() 方法中使用默認(rèn)的過濾诅福,那么該過濾將適用于all() 調(diào)用。
當(dāng)然拖叙,指定一個自定義的管理器還可以讓你調(diào)用自定義的方法:
b.entry_set(manager='entries').is_published()
處理關(guān)聯(lián)對象的其它方法?
除了在上面”獲取對象“一節(jié)中定義的查詢集 方法之外氓润,F(xiàn)oreignKey 管理器 還有其它方法用于處理關(guān)聯(lián)的對象集合。下面是每個方法的大概薯鳍,完整的細(xì)節(jié)可以在關(guān)聯(lián)對象參考 中找到咖气。
add(obj1, obj2, ...)
添加一指定的模型對象到關(guān)聯(lián)的對象集中。
create(**kwargs)
創(chuàng)建一個新的對象挖滤,將它保存并放在關(guān)聯(lián)的對象集中崩溪。返回新創(chuàng)建的對象。
remove(obj1, obj2, ...)
從關(guān)聯(lián)的對象集中刪除指定的模型對象斩松。
clear()
從關(guān)聯(lián)的對象集中刪除所有的對象伶唯。
若要一次性給關(guān)聯(lián)的對象集賦值,只需要給它賦值一個可迭代的對象惧盹。這個可迭代的對象可以包含對象的實例乳幸,或者一個主鍵值的列表。例如:
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
在這個例子中钧椰,e1 和e2 可以是Entry 實例粹断,也可以是主鍵的整數(shù)值。
如果有clear() 方法嫡霞,那么在將可迭代對象中的成員添加到集合中之前瓶埋,將從entry_set 中刪除所有已經(jīng)存在的對象。如果沒有clear() 方法诊沪,那么將直接添加可迭代對象中的成員而不會刪除所有已存在的對象养筒。
這一節(jié)中提到的每個”反向“操作都會立即對數(shù)據(jù)庫產(chǎn)生作用。每個添加娄徊、創(chuàng)建和刪除操作都會立即并自動保存到數(shù)據(jù)庫中闽颇。
多對多關(guān)系?
多對多關(guān)系的兩端都會自動獲得訪問另一端的API。這些API 的工作方式與上面提到的“方向”一對多關(guān)系一樣寄锐。
唯一的區(qū)別在于屬性的名稱:定義 ManyToManyField 的模型使用該字段的屬性名稱兵多,而“反向”模型使用源模型的小寫名稱加上'_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 實例將使用 entries 屬性而不是entry_set怠褐。
一對一關(guān)系?
一對一關(guān)系與多對一關(guān)系非常相似。如果你在模型中定義一個OneToOneField您宪,該模型的實例將可以通過該模型的一個簡單屬性訪問關(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.
在“反向”查詢中有所不同奠涌。一對一關(guān)系中的關(guān)聯(lián)模型同樣具有一個管理器對象,但是該管理器表示一個單一的對象而不是對象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果沒有對象賦值給這個關(guān)聯(lián)關(guān)系磷杏,Django 將引發(fā)一個DoesNotExist 異常溜畅。
實例可以賦值給反向的關(guān)聯(lián)關(guān)系,方法和正向的關(guān)聯(lián)關(guān)系一樣:
e.entrydetail = ed
反向的關(guān)聯(lián)關(guān)系是如何實現(xiàn)的极祸??
其它對象關(guān)系映射要求你在關(guān)聯(lián)關(guān)系的兩端都要定義慈格。Django 的開發(fā)人員相信這是對DRY(不要重復(fù)你自己的代碼)原則的違背,所以Django 只要求你在一端定義關(guān)聯(lián)關(guān)系遥金。
但是這怎么可能浴捆?因為一個模型類直到其它模型類被加載之后才知道哪些模型類是關(guān)聯(lián)的。
答案在app registry 中稿械。當(dāng)Django 啟動時选泻,它導(dǎo)入INSTALLED_APPS 中列出的每個應(yīng)用,然后導(dǎo)入每個應(yīng)用中的models 模塊美莫。每創(chuàng)建一個新的模型時滔金,Django 添加反向的關(guān)系到所有關(guān)聯(lián)的模型。如果關(guān)聯(lián)的模型還沒有導(dǎo)入茂嗓,Django 將保存關(guān)聯(lián)關(guān)系的記錄并在最終關(guān)聯(lián)的模型導(dǎo)入時添加這些關(guān)聯(lián)關(guān)系。
由于這個原因科阎,你使用的所有模型都定義在INSTALLED_APPS 列出的應(yīng)用中就顯得特別重要述吸。否則,反向的關(guān)聯(lián)關(guān)系將不能正確工作锣笨。
通過關(guān)聯(lián)的對象進(jìn)行查詢?
在關(guān)聯(lián)對象字段上的查詢與正常字段的查詢遵循同樣的規(guī)則蝌矛。當(dāng)你指定查詢需要匹配的一個值時,你可以使用一個對象實例或者對象的主鍵的值错英。
例如入撒,如果你有一個id=5 的Blog 對象b,下面的三個查詢將是完全一樣的:
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ù)庫映射機(jī)制太復(fù)雜椭岩,你可以回歸到手工編寫SQL茅逮。Django 對于編寫原始的SQL 查詢有多個選項;參見執(zhí)行原始的SQL 查詢判哥。
最后献雅,值得注意的是Django 的數(shù)據(jù)庫層只是數(shù)據(jù)庫的一個接口。你可以利用其它工具塌计、編程語言或數(shù)據(jù)庫框架來訪問數(shù)據(jù)庫挺身;對于數(shù)據(jù)庫,Django 沒有什么特別的地方锌仅。
2015年5月13日
? previous | up | next ?