Django 執行查詢使用說明

執行查詢

一旦你創建好數據模型,Django 會自動爲你生成一套數據庫抽象的API,可讓你建立、檢索、更新和刪除對象。這篇文檔闡述如何使用這些API。 關於模型查詢全部選項的完整細節,請見數據模型參考html

在整個文檔(以及參考)中,咱們將引用下面的模型,它構成一個博客應用:python

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

建立對象

Django 使用一種直觀的方式把數據庫表中的數據表示成Python 對象:一個模型類表明數據庫中的一個表,一個模型類的實例表明這個數據庫表中的一條特定的記錄。git

使用關鍵字參數實例化模型實例來建立一個對象,而後調用save() 把它保存到數據庫中。github

假設模型存放於文件mysite/blog/models.py中,下面是一個例子:sql

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

上面的代碼在背後執行了SQL 的INSERT 語句。在你顯式調用save()以前,Django 不會訪問數據庫。數據庫

save() 方法沒有返回值。express

請參見django

save()方法帶有一些高級選項,它們沒有在這裏給出。完整的細節請見save() 文檔。編程

若是你想只用一條語句建立並保存一個對象,使用create()方法。api

保存對象的改動

要保存對數據庫中已存在的對象的改動,請使用save()

假設Blog 的一個實例b5 已經被保存在數據庫中,下面這個例子將更改它的name 而且更新數據庫中的記錄:

>>> 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 將會在你賦值或添加錯誤類型的對象時報錯。

獲取對象

經過模型中的管理器構造一個查詢集,來從你的數據庫中獲取對象。

查詢集表示從數據庫中取出來的對象的集合。它能夠含有零個、一個或者多個過濾器。過濾器基於所給的參數限制查詢的結果。 從SQL 的角度,查詢集和SELECT 語句等價,過濾器是像WHERE 和LIMIT 同樣的限制子句。

你能夠從模型的管理器那裏取得查詢集。每一個模型都至少有一個管理器,它默認命名爲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."

管理器只能夠經過模型的類訪問,而不能夠經過模型的實例訪問,目的是爲了強制區分「表級別」的操做和「記錄級別」的操做。

對於一個模型來講,管理器是查詢集的主要來源。例如,Blog.objects.all() 返回包含數據庫中全部Blog 對象的一個查詢集

獲取全部對象

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

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

all()方法返回包含數據庫中全部對象的一個查詢集

使用過濾器獲取特定對象

all() 方法返回了一個包含數據庫表中全部記錄查詢集。但在一般狀況下,你每每想要獲取的是完整數據集的一個子集。

要建立這樣一個子集,你須要在原始的的查詢集上增長一些過濾條件。兩個最廣泛的途徑是:

filter(**kwargs)

返回一個新的查詢集,它包含知足查詢參數的對象。

exclude(**kwargs)

返回一個新的查詢集,它包含知足查詢參數的對象。

查詢參數(上面函數定義中的**kwargs)須要知足特定的格式,下面字段查詢一節中會提到。

舉個例子,要獲取年份爲2006的全部文章的查詢集,可使用filter()方法:

Entry.objects.filter(pub_date__year=2006)

利用默認的管理器,它至關於:

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

鏈式過濾

查詢集的篩選結果自己仍是查詢集,因此能夠將篩選語句連接在一塊兒。像這樣:

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

這個例子最開始獲取數據庫中全部對象的一個查詢集,以後增長一個過濾器,而後又增長一個排除,再以後又是另一個過濾器。最後的結果仍然是一個查詢集,它包含標題以」What「開頭、發佈日期在2005年1月30日至當天之間的全部記錄。

過濾後的查詢集是獨立的

每次你篩選一個查詢集,獲得的都是全新的另外一個查詢集,它和以前的查詢集之間沒有任何綁定關係。每次篩選都會建立一個獨立的查詢集,它能夠被存儲及反覆使用。

例如:

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

這三個查詢集都是獨立的。第一個是一個基礎的查詢集,包含全部標題以「What」開頭的記錄。第二個查詢集是第一個的子集,它增長另一個限制條件,排除pub_date 爲今天和未來的記錄。第三個查詢集一樣是第一個的子集,它增長另一個限制條件,只選擇pub_date 爲今天或未來的記錄。原始的查詢集(q1)不會受到篩選過程的影響。

查詢集是惰性執行的

查詢集 是惰性執行的 —— 建立查詢集不會帶來任何數據庫的訪問。你能夠將過濾器保持一成天,直到查詢集 須要求值時,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)

雖然它看上去有三次數據庫訪問,但事實上只有在最後一行(print(q))時才訪問一次數據庫。通常來講,只有在「請求」查詢集 的結果時纔會到數據庫中去獲取它們。當你確實須要結果時,查詢集 經過訪問數據庫來求值。 關於求值發生的準確時間,參見什麼時候計算查詢集

經過get 獲取一個單一的對象

filter() 始終給你一個查詢集,即便只有一個對象知足查詢條件 —— 這種狀況下,查詢集將只包含一個元素。

若是你知道只有一個對象知足你的查詢,你可使用管理器get() 方法,它直接返回該對象:

>>> one_entry = Entry.objects.get(pk=1)

能夠對get() 使用任何查詢表達式,和filter() 同樣 —— 一樣請查看下文的字段查詢

注意,使用get() 和使用filter() 的切片[0] 有一點區別。若是沒有結果知足查詢,get() 將引起一個DoesNotExist 異常。這個異常是正在查詢的模型類的一個屬性 —— 因此在上面的代碼中,若是沒有主鍵爲1 的Entry 對象,Django 將引起一個Entry.DoesNotExist。

相似地,若是有多條記錄知足get() 的查詢條件,Django 也將報錯。這種狀況將引起MultipleObjectsReturned,它一樣是模型類自身的一個屬性。

其它查詢集方法

大多數狀況下,須要從數據庫中查找對象時,你會使用all()、 get()filter() 和exclude()。 然而,這只是冰山一角;查詢集 方法的完整列表,請參見查詢集API 參考

限制查詢集

可使用Python 的切片語法來限制查詢集記錄的數目 。它等同於SQL 的LIMIT 和OFFSET 子句。

例如,下面的語句返回前面5 個對象(LIMIT 5):

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

下面這條語句返回第6 至第10 個對象(OFFSET 5 LIMIT 5):

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

不支持負的索引(例如Entry.objects.all()[-1])。

一般,查詢集 的切片返回一個新的查詢集 —— 它不會執行查詢。有一個例外,是若是你使用Python 切片語法中"step"參數。例如,下面的語句將返回前10 個對象中每隔2個對象,它將真實執行查詢:

>>> 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 子句的內容。它們經過查詢集方法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 定義的函數能夠接收任意的鍵/值對參數,這些名稱和參數能夠在運行時求值。更多信息,參見Python 官方文檔中的關鍵字參數

查詢條件中指定的字段必須是模型字段的名稱。但有一個例外,對於ForeignKey你可使用字段名加上_id 後綴。在這種狀況下,該參數的值應該是外鍵的原始值。例如:

>>> Entry.objects.filter(blog_id=4)

若是你傳遞的是一個不合法的參數,查詢函數將引起 TypeError。

這些數據庫API 支持大約二十多種查詢的類型;在字段查詢參考 中能夠找到完整的參考。爲了讓你嚐嚐鮮,下面是一些你可能用到的常見查詢:

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

startswithendswith

分別表示以XXX開頭和以XXX結尾。固然還有大小寫不敏感的版本,叫作istartswith 和 iendswith

一樣,這裏只是表面。完整的參考能夠在字段查詢參考中找到。

跨關聯關係的查詢

Django 提供一種強大而又直觀的方式來「處理」查詢中的關聯關係,它在後臺自動幫你處理JOIN。 若要跨越關聯關係,只需使用關聯的模型字段的名稱,並使用雙下劃線分隔,直至你想要的字段:

下面這個例子獲取全部Blog 的name 爲'Beatles Blog' 的Entry 對象:

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

這種跨越能夠是任意的深度。

它還能夠反向工做。若要引用一個「反向」的關係,只須要使用該模型的小寫的名稱。

下面的示例獲取全部的Blog 對象,它們至少有一個Entry 的headline 包含'Lennon':

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

若是你在多個關聯關係直接過濾並且其中某個中介模型沒有知足過濾條件的值,Django 將把它當作一個空的(全部的值都爲NULL)可是合法的對象。這意味着不會有錯誤引起。例如,在下面的過濾器中:

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

(若是有一個相關聯的Author 模型),若是Entry 中沒有找到對應的author,那麼它將看成其沒有name,而不會由於沒有author 引起一個錯誤。一般,這就是你想要的。惟一可能讓你困惑的是當你使用isnull 的時候。所以:

Blog.objects.filter(entry__authors__name__isnull=True)

返回的Blog 對象包括author __name 爲空的Blog對象,以及author__name不爲空但author__name關聯的entry __author 爲空的對象。若是你不須要後者,你能夠這樣寫:

Blog.objects.filter(entry__authors__isnull=False,
        entry__authors__name__isnull=True)

跨越多值的關聯關係

當你基於ManyToManyField 或反向的ForeignKey 來過濾一個對象時,有兩種不一樣種類的過濾器。考慮Blog/Entry 關聯關係(Blog 和 Entry 是一對多的關係)。咱們可能想找出headline爲「Lennon」 或publish爲'2008'年的Entry。或者咱們可能想查詢包headline爲「Lennon」 的Entry以及published爲'2008'的Entry。由於實際上有和單個Blog 相關聯的多個Entry,因此這兩個查詢在某些場景下都是有可能並有意義的。

ManyToManyField 有相似的狀況。例如,若是Entry 有一個ManyToManyField 叫作 tags,咱們可能想找到tag 叫作「music」 和「bands」 的Entry,或者咱們想找一個tag 名爲「music」 且狀態爲「public」的Entry。

對於這兩種狀況,Django 有種一致的方法來處理filter() 調用。一個filter() 調用中的全部參數會同時應用以過濾出知足全部要求的記錄。接下來的filter() 調用進一步限制對象集,可是對於多值關係,它們應用到與主模型關聯的對象,而不是應用到前一個filter() 調用選擇出來的對象。

這些聽起來可能有點混亂,因此但願展現一個例子使它變得更清晰。選擇全部包含同時知足兩個條件的entry的blog,這兩個條件是headline 包含Lennon 和發表時間是2008 (同一個entry 知足兩個條件),咱們的代碼是:

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

從全部的blog模型實例中選擇知足如下條件的blog實例:blog的enrty的headline屬性值是「Lennon」,或者entry的發表時間是2008(兩個條件至少知足一個,也能夠同時知足),咱們的代碼是:

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

假如只有一個blog 對象同時含有兩種entries,其中一種headline 包含「Lennon」而另一種發表時間是2008,可是沒有發表在2008年且headline 包含「Lennon」 的entries。(註釋:即一個Entry中headline包含"Lennon"可是發表時間不是2008,另外一個Entry中headline不包含"Lennon"可是發表時間是2008.每個Entry只知足一個條件)第一個查詢不會返回任何blog,第二個查詢將會返回一個blog。

在第二個例子中, 第一個filter 限定查詢集中的blog 與headline 包含「Lennon」 的entry 關聯。第二個filter  限定查詢集中的blog ,這些blog關聯的entry 的發表時間是2008。(譯者注:難點在如何理解further這個詞!)第二個filter 過濾出來的entry 與第一個filter 過濾出來的entry 可能相同也可能不一樣。每一個filter 語句過濾的是Blog,而不是Entry。

跨越多值關係的filter() 查詢的行爲,與exclude() 實現的不一樣。單個exclude() 調用中的條件沒必要引用同一個記錄。

例如,下面的查詢排除headline 中包含「Lennon」的Entry在2008 年發佈的Entry:

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

然而,這與使用filter() 的行爲不一樣,它不是排除同時知足兩個條件的Entry。爲了實現這點,即選擇的Blog中不包含在2008年發佈且healine 中帶有「Lennon」 的Entry,你須要編寫兩個查詢:

Blog.objects.exclude(
    entry=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

Filter 能夠引用模型的字段

到目前爲止給出的示例中,咱們構造過將模型字段與常量進行比較的filter。可是,若是你想將模型的一個字段與同一個模型的另一個字段進行比較該怎麼辦?

Django 提供F 表達式 來容許這樣的比較。F() 返回的實例用做查詢內部對模型字段的引用。這些引用能夠用於查詢的filter 中來比較相同模型實例上不一樣字段之間值的比較。

例如,爲了查找comments 數目多於pingbacks 的Entry,咱們將構造一個F() 對象來引用pingback 數目,並在查詢中使用該F() 對象:

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django 支持對F() 對象使用加法、減法、乘法、除法、取模以及冪計算等算術操做,兩個操做數能夠都是常數和其它F() 對象。爲了查找comments 數目比pingbacks 兩倍還要多的Entry,咱們將查詢修改成:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

New in Django 1.7:

添加 ** 操做符。

爲了查詢rating 比pingback 和comment 數目總和要小的Entry,咱們將這樣查詢:

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

你還能夠在F() 對象中使用雙下劃線標記來跨越關聯關係。帶有雙下劃線的F() 對象將引入任何須要的join 操做以訪問關聯的對象。例如,如要獲取author 的名字與blog 名字相同的Entry,咱們能夠這樣查詢:

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

對於date 和date/time 字段,你能夠給它們加上或減去一個timedelta 對象。下面的例子將返回發佈超過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 結合來完成一個模型上對主鍵的查詢:

# 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

轉義LIKE 語句中的百分號和下劃線

與LIKE SQL 語句等同的字段查詢(iexact、 contains、icontains、startswith、 istartswith、endswith 和iendswith)將自動轉義在LIKE 語句中使用的兩個特殊的字符 —— 百分號和下劃線。(在LIKE 語句中,百分號通配符表示多個字符,下劃線通配符表示單個字符)。

這意味着語句將很直觀,不會顯得太抽象。例如,要獲取包含一個百分號的全部的Entry,只須要像其它任何字符同樣使用百分號:

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

Django 會幫你轉義;生成的SQL 看上去會是這樣:

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

對於下劃線是一樣的道理。百分號和下劃線都會透明地幫你處理。

緩存和查詢集

每一個查詢集都包含一個緩存來最小化對數據庫的訪問。理解它是如何工做的將讓你編寫最高效的代碼。

在一個新建立的查詢集中,緩存爲空。首次對查詢集進行求值 —— 同時發生數據庫查詢 ——Django 將保存查詢的結果到查詢集的緩存中並返回明確請求的結果(例如,若是正在迭代查詢集,則返回下一個結果)。接下來對該查詢集 的求值將重用緩存的結果。

請牢記這個緩存行爲,由於對查詢集使用不當的話,它會坑你的。例如,下面的語句建立兩個查詢集,對它們求值,而後扔掉它們:

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

這意味着相同的數據庫查詢將執行兩次,顯然倍增了你的數據庫負載。同時,還有可能兩個結果列表並不包含相同的數據庫記錄,由於在兩次請求期間有可能有Entry被添加進來或刪除掉。

爲了不這個問題,只需保存查詢集並從新使用它:

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

什麼時候查詢集不會被緩存

查詢集不會永遠緩存它們的結果。當只對查詢集的部分進行求值時會檢查緩存, 可是若是這個部分不在緩存中,那麼接下來查詢返回的記錄都將不會被緩存。特別地,這意味着使用切片或索引來限制查詢集將不會填充緩存。

例如,重複獲取查詢集對象中一個特定的索引將每次都查詢數據庫:

>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database
>>> print queryset[5] # Queries the database again

然而,若是已經對所有查詢集求值過,則將檢查緩存:

>>> 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__() 調用只返回所有查詢集的一個切片。

使用Q 對象進行復雜的查詢

filter() 等方法中的關鍵字參數查詢都是一塊兒進行「AND」 的。 若是你須要執行更復雜的查詢(例如OR 語句),你可使用Q 對象

Q 對象 (django.db.models.Q) 對象用於封裝一組關鍵字參數。這些關鍵字參數就是上文「字段查詢」 中所說起的那些。

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

from django.db.models import Q
Q(question__startswith='What')

Q 對象可使用& 和| 操做符組合起來。當一個操做符在兩個Q 對象上使用時,它產生一個新的Q 對象。

例如,下面的語句產生一個Q 對象,表示兩個"question__startswith" 查詢的「OR」 :

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 查詢示例演示了幾種Q 的用法。

比較對象

爲了比較兩個模型實例,只須要使用標準的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 執行,因此這個過程當中不須要調用每一個對象實例的delete()方法。若是你給模型類提供了一個自定義的delete() 方法並但願確保它被調用,你須要手工刪除該模型的實例(例如,迭代查詢集並調用每一個對象的delete())而不能使用查詢集的批量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()

這種級聯的行爲能夠經過的ForeignKey 的on_delete 參數自定義。

注意,delete() 是惟一沒有在管理器 上暴露出來的查詢集方法。這是一個安全機制來防止你意外地請求Entry.objects.delete(),而刪除全部 的條目。若是你確實想刪除全部的對象,你必須明確地請求一個徹底的查詢集:

Entry.objects.all().delete()

拷貝模型實例

雖然沒有內建的方法用於拷貝模型實例,但仍是很容易建立一個新的實例並讓它的全部字段都拷貝過來。最簡單的方法是,只須要將pk 設置爲None。利用咱們的Blog 示例:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

若是你用繼承,那麼會複雜一些。考慮下面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

因爲繼承的工做方式,你必須設置pk 和 id 都爲None:

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

這個過程不會拷貝關聯的對象。若是你想拷貝關聯關係,你必須編寫一些更多的代碼。在咱們的例子中,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

一次更新多個對象

有時你想爲一個查詢集中全部對象的某個字段都設置一個特定的值。這時你可使用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() 方法會當即執行並返回查詢匹配的行數(若是有些行已經具備新的值,返回的行數可能和被更新的行數不相等)。更新查詢集 惟一的限制是它只能訪問一個數據庫表,也就是模型的主表。你能夠根據關聯的字段過濾,可是你只能更新模型主表中的列。例如:

>>> 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() 方法會直接轉換成一個SQL 語句。它是一個批量的直接更新操做。它不會運行模型的save() 方法,或者發出pre_save 或 post_save信號(調用save()方法產生)或者查看auto_now 字段選項。若是你想保存查詢集中的每一個條目並確保每一個實例的save() 方法都被調用,你不須要使用任何特殊的函數來處理。只須要迭代它們並調用save()

for item in my_queryset:
    item.save()

對update 的調用也可使用F 表達式 來根據模型中的一個字段更新另一個字段。這對於在當前值的基礎上加上一個值特別有用。例如,增長Blog 中每一個Entry 的pingback 個數:

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

然而,與filter 和exclude 子句中的F() 對象不一樣,在update 中你不可使用F() 對象引入join —— 你只能夠引用正在更新的模型的字段。若是你嘗試使用F() 對象引入一個join,將引起一個FieldError:

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

關聯的對象

當你在一個模型中定義一個關聯關係時(例如,ForeignKey、 OneToOneField 或ManyToManyField),該模型的實例將帶有一個方便的API 來訪問關聯的對象。

利用本頁頂部的模型,一個Entry 對象e 能夠經過blog 屬性e.blog 獲取關聯的Blog 對象。

(在幕後,這個功能是經過Python 的描述器實現的。這應該不會對你有什麼真正的影響,可是這裏咱們指出它以知足你的好奇)。

Django 還會建立API 用於訪問關聯關係的另外一頭 —— 從關聯的模型訪問定義關聯關係的模型。例如,Blog 對象b 能夠經過entry_set 屬性 b.entry_set.all()訪問與它關聯的全部Entry 對象。

這一節中的全部示例都將使用本頁頂部定義的Blog、 Author 和Entry 模型。

一對多關係

前向查詢

若是一個模型具備ForeignKey,那麼該模型的實例將能夠經過屬性訪問關聯的(外部)對象。

例如:

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

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

反向查詢

若是模型有一個ForeignKey,那麼該ForeignKey 所指的模型實例能夠經過一個管理器返回前一個模型的全部實例。默認狀況下,這個管理器的名字爲foo_set,其中foo 是源模型的小寫名稱。該管理器返回的查詢集能夠用上一節提到的方式進行過濾和操做。

例如:

>>> 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 模型改爲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()

使用自定義的反向管理器

New in Django 1.7.

默認狀況下,用於反向關聯關係的RelatedManager 是該模型默認管理器 的子類。若是你想爲一個查詢指定一個不一樣的管理器,你可使用下面的語法:

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() 方法中使用默認的過濾,那麼該過濾將適用於all() 調用。

固然,指定一個自定義的管理器還可讓你調用自定義的方法:

b.entry_set(manager='entries').is_published()

處理關聯對象的其它方法

除了在上面」獲取對象「一節中定義的查詢集 方法以外,ForeignKey 管理器 還有其它方法用於處理關聯的對象集合。下面是每一個方法的大概,完整的細節能夠在關聯對象參考 中找到。

add(obj1, obj2, ...)

添加一指定的模型對象到關聯的對象集中。

create(**kwargs)

建立一個新的對象,將它保存並放在關聯的對象集中。返回新建立的對象。

remove(obj1, obj2, ...)

從關聯的對象集中刪除指定的模型對象。

clear()

從關聯的對象集中刪除全部的對象。

若要一次性給關聯的對象集賦值,只須要給它賦值一個可迭代的對象。這個可迭代的對象能夠包含對象的實例,或者一個主鍵值的列表。例如:

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

在這個例子中,e1 和e2 能夠是Entry 實例,也能夠是主鍵的整數值。

若是有clear() 方法,那麼在將可迭代對象中的成員添加到集合中以前,將從entry_set 中刪除全部已經存在的對象。若是沒有clear() 方法,那麼將直接添加可迭代對象中的成員而不會刪除全部已存在的對象。

這一節中提到的每一個」反向「操做都會當即對數據庫產生做用。每一個添加、建立和刪除操做都會當即並自動保存到數據庫中。

多對多關係

多對多關係的兩端都會自動得到訪問另外一端的API。這些API 的工做方式與上面提到的「方向」一對多關係同樣。

惟一的區別在於屬性的名稱:定義 ManyToManyField 的模型使用該字段的屬性名稱,而「反向」模型使用源模型的小寫名稱加上'_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.

相似ForeignKeyManyToManyField 能夠指定related_name。在上面的例子中,若是Entry 中的ManyToManyField 指定related_name='entries',那麼Author 實例將使用 entries 屬性而不是entry_set。

一對一關係

一對一關係與多對一關係很是類似。若是你在模型中定義一個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.

在「反向」查詢中有所不一樣。一對一關係中的關聯模型一樣具備一個管理器對象,可是該管理器表示一個單一的對象而不是對象的集合:

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

若是沒有對象賦值給這個關聯關係,Django 將引起一個DoesNotExist 異常。

實例能夠賦值給反向的關聯關係,方法和正向的關聯關係同樣:

e.entrydetail = ed

反向的關聯關係是如何實現的?

其它對象關係映射要求你在關聯關係的兩端都要定義。Django 的開發人員相信這是對DRY(不要重複你本身的代碼)原則的違背,因此Django 只要求你在一端定義關聯關係。

可是這怎麼可能?由於一個模型類直到其它模型類被加載以後才知道哪些模型類是關聯的。

答案在app registry 中。當Django 啓動時,它導入INSTALLED_APPS 中列出的每一個應用,而後導入每一個應用中的models 模塊。每建立一個新的模型時,Django 添加反向的關係到全部關聯的模型。若是關聯的模型尚未導入,Django 將保存關聯關係的記錄並在最終關聯的模型導入時添加這些關聯關係。

因爲這個緣由,你使用的全部模型都定義在INSTALLED_APPS 列出的應用中就顯得特別重要。不然,反向的關聯關係將不能正確工做。

經過關聯的對象進行查詢

在關聯對象字段上的查詢與正常字段的查詢遵循一樣的規則。當你指定查詢須要匹配的一個值時,你可使用一個對象實例或者對象的主鍵的值。

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

若是你發現須要編寫的SQL 查詢對於Django 的數據庫映射機制太複雜,你能夠迴歸到手工編寫SQL。Django 對於編寫原始的SQL 查詢有多個選項;參見執行原始的SQL 查詢

最後,值得注意的是Django 的數據庫層只是數據庫的一個接口。你能夠利用其它工具、編程語言或數據庫框架來訪問數據庫;對於數據庫,Django 沒有什麼特別的地方。

相關文章
相關標籤/搜索