1. 程式人生 > >Django學習小記[3] —— Query

Django學習小記[3] —— Query

今天學習的是Django的Model Query,前一篇已經學習過Model了,講述的主要是Django中是如何處理關係型資料的模型的,一對一,多對一,多對多等,這篇則主要是描述的查詢,能夠將資料存進去,還得取出來,Django給每一個Model自動提供了豐富的查詢介面,而且能夠進行關聯查詢,基本上,能夠滿足絕大多數的查詢需求。

在Django的文件中,有一句話說的非常好:

A model class represents a database table, and an instance of that class represents a particular record in the database table.

Model類的例項代表的是資料庫中的記錄,我們在進行查詢的時候,得到結果一般都是一個數據庫記錄集合,這對應到Django裡,就是QuerySet,Django提供的查詢介面全都封裝在QuerySet中,比如filter(), order_by()等,這對應到SQL語句,就是SELECT語句。這裡我們只對經常用到的QuerySet方法進行簡單描述,起到一個勾起回憶的作用,至於更多的內容還請參考QuerySet詳細的API:QuerySet API

先構造幾個有關聯關係的Model:

from django.db import models

class Blog(models.Model)
:
name = models.CharField(max_length=100) tagline = models.TextField() def __unicode__(self): return self.name class Author(models.Model): name = models.CharField(max_length=50) email = models.EmailField() def __unicode__(self): 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 __unicode__(self): return self.headline class EntryDetail(models.Model): entry = models.OneToOneField(Entry) details = models.TextField()

這幾個Model中,Blog和Entry是一對多的關係,Entry和Author是多對多的關係,Entry和EntryDetail是一對一的關係,分別通過ForeignKey, ManyToManyField, OneToOneField進行關聯。現在我們來看看怎麼對這些進行查詢。

1. 首先來看最簡單的,all(),一次獲取所有的資料庫記錄:
>>> all_entries = Entry.objects.all()

objects是什麼呢?每一個Model都有一個Manager物件,Manager也是Model的查詢介面,那它和QuerySet之間是什麼關係呢?其實Manager可以看成是QuerySet的代理類,最開始的QuerySet物件就是通過Manager類來得到的,objects就是Manager物件在Model類中的屬性名。至於為什麼有Manager類,為什麼QuerySet需要代理,我現在還不是很清楚,這些需要看Django的原始碼才能知道,現在我們先知道怎麼用就可以了。

呼叫objects.all()方法,得到就是一個QuerySet物件,如:

>>> type(Entry.objects.all())
<class 'django.db.models.query.QuerySet'>
>>> Entry.objects.all()
[<Entry: headline1>, <Entry: headline2>]
2. filters,過濾結果

即給Select語句加上Where查詢條件,選出符合條件的子集,QuerySet中提供了兩個方法:

  • filter(**kwargs)
    Returns a new QuerySet containing objects that match the given lookup parameters.
  • exclude(**kwargs)
    Returns a new QuerySet containing objects that do not match the given lookup parameters.

一個是過濾出符合條件的結果,一個是過濾出不符合套件的結果。注意,這裡有一個很特殊的地方,就是這兩方法的引數kwargs,有一定的約定,要符合:field__lookuptype=value這樣的約束,field是Model的屬性名,然後是兩個下劃線,然後是由Django定義的查詢條件,比如exact, contains, startwith, gte, lt等,更多詳細的內容,可以檢視QuerySet API。下面給出兩個例子:

>>> Blog.objects.filter(tagline__startswith="All")
[<Blog: Beatles>]
>>> Entry.objects.filter(pub_date__gte="2014-04-20")
[<Entry: headline1>, <Entry: headline2>]

需要注意的是,filter()和exclude()返回的結果仍然是一個QuerySet物件,所以,可以在一個語句中串聯的使用filter,如:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )
3. 接下來,來說一說QuerySet的一些特性
  • 首先,QuerySet是各自獨立的,上面例子中,使用了3個串聯的filter方法,每一個filter都會得到一個QuerySet,這3個QuerySet之間並沒有什麼直接的關係,相互之間也沒有影響。
  • 其次,是QuerySet有一個很重要的特性就是延遲載入,並不是每一次呼叫filter()方法就會去查詢一次資料庫,只有在真正需要資料的時候才會去查詢資料庫,比如:
    >>> q = Entry.objects.filter(pub_date__gte="2014-04-20")
    >>> q = q.filter(mod_date__gte="2014-05-20")
    >>> q
    [<Entry: headline1>, <Entry: headline2>]
    
    前兩個語句並沒有去查資料庫,第3個語句才會查了資料庫,這種機制避免了不必要的資料庫查詢,減輕了資料庫的壓力。
  • 然後,就是QuerySet的快取特性了。每一個QuerySet都有快取,它第一次訪問資料庫時,會將結果快取起來,這樣當再次用到這個QuerySet時,就不用再次訪問資料庫了。比如:
    >>> print([e.headline for e in Entry.objects.all()])
    >>> print([e.pub_date for e in Entry.objects.all()])
    
    這樣就是訪問了兩次資料庫,因為建立了兩個不同的QuerySet物件,但是:
    >>> queryset = Entry.objects.all()
    >>> print([p.headline for p in queryset])
    >>> print([p.pub_date for p in queryset])
    
    這樣在執行第二個語句時,只訪問了一次資料庫,第3個語句就用的QuerySet快取的資料了。
4. 直接得到一個Model物件:get()

filter()的返回結果總是得到一個QuerySet物件,QuerySet是Model物件集合,當你明確知道Model物件的集合只有一個時,可能就沒必要用filter這麼“麻煩”了,可以使用get()方法直接得到Model物件。如:

>>> one_entry = Entry.objects.get(pk=1)
>>> type(one_entry)
<class 'query_test.models.Entry'>

需要注意的是,當get()查詢的結果沒有,會丟擲 DoesNotExist 異常,或者是有多個時,會丟擲MultipleObjectsReturned 異常。

5. limit and offset

當我們在做分頁時,會用到SQL的limit和offset特性,從哪開始讀取多少個記錄。那這個在Django裡是怎麼用的呢?很簡單,就是直接使用python中array-slicing的語法就行,如:

>>> Blog.objects.all()
[<Blog: Beatles>, <Blog: blog2>, <Blog: blog3>]
>>> Blog.objects.all()[0]
<Blog: Beatles>
>>> Blog.objects.all()[1:3]
[<Blog: blog2>, <Blog: blog3>]

第2個例子,通過[0]得到的就直接是一個Model物件,而第三個例子,通過[1:3]得到的是一個QuerySet物件,也就是說是一個Model物件的集合。第三個例子中,可以理解為limit等於2,offset等於1。

上面簡單地介紹了一下Django提供的Model查詢介面,雖然簡單,但是基本上也就是我們平常經常用到的方法。下面我們再從一對一,多對一,多對多這三個方面,來看看怎麼進行稍微複雜點的聯合查詢。

1. One-to-many relationships

一對多,從“多”的這一方,也就是ForeignKey所在的Model,向“一”的這一方查,比較容易,只需要指定屬性名稱就可以了,比如:

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

如果從“一”的這一方向“多”的那一方查,那麼“多”的一方是“一”的一方的一個集合,Django會自動為“一”的這一方建立一個叫做"Foo_set"的Manager,它就是“多”的這一方的集合,可以直接通過這個Manager對關聯的Model進行增刪查改操作。比如:

>>> 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()
2. Many-to-many relationships

多對多,其實和一對多非常的像,定義ManyToManyField的Model訪問和它關聯的Model時,直接訪問該Model的名字就可以了,反過來,就需要加上“_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.

是不是和一對多非常的像?

3. One-to-one relationships

一對一,和前兩個也是非常像的,唯一不同的地方,還是在“反過來”,也就是由沒有定義OneToOneField的那一方向定義了OneToOneField的那一方進行的查詢,如:

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

好,最後說一點,在沒有定義xxxToxxxField的那一方,是怎麼知道都是有誰和它關聯呢?也就是是在什麼時候,它的Model裡面多了一個xxx_set屬性呢?答案就是任何一個Model唄載入的時候,Django會遍歷INSTALLED_APPS中的所有APP的所有Model,根據xxxToxxxField來對應的給關聯類物件建立xxx_set這樣的Manager屬性。

這樣雖然有些暴力,但是使得Django程式看上去更簡潔,沒有無用的重複的程式碼了。

好,Model Query就介紹到這裡,這裡只是一個入門的流水而已,更加豐富的內容,還需要移步到Django的文件中去: