Django中的ORM

weixin_33766168發表於2016-04-08

通過《如何建立一個Django網站》大概清楚瞭如何建立一個簡單的 Django 網站,並瞭解了Django 中模板模型使用方法。本篇文章主要在此基礎上,瞭解 Django 中 ORM 相關的用法。

一個 blog 的應用中 mysite/blog/models.py 有以下實體:

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

建立物件

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

你也可以修改實體,然後儲存:

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

儲存外來鍵資訊

下面例子更新 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 欄位:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
>>> 
>>> 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 和 QuerySet 兩個物件。從資料庫裡檢索物件,可以通過模型的 Manage 來建立 QuerySet,一個 QuerySet 表現為一個資料庫中物件的結合,他可以有0個一個或多個過濾條件,在 SQL裡 QuerySet 相當於 select 語句用 where 或 limit 過濾。你通過模型的 Manage 來獲取 QuerySet。

Manager

Manager 物件附在模型類裡,如果沒有特指定,每個模型類都會有一個 objects 屬性,它構成了這個模型在資料庫所有基本查詢。

Manager 的幾個常用方法:

  • all:返回一個包含模式裡所有資料庫記錄的 QuerySet
  • filter:返回一個包含符合指定條件的模型記錄的 QuerySet
  • exclude:和 filter 相反,查詢不符合條件的那些記錄
  • get:獲取單個符合條件的記錄(沒有找到或者又超過一個結果都會丟擲異常)
  • order_by:改變 QuerySet 預設的排序

你可以通過模型的 Manager 物件獲取 QuerySet 物件:

>>> 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 內容:

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

#正向排序
Entry.objects.all().order_by("headline")
#反向排序
Entry.objects.all().order_by("-headline")

獲取 headline 為 Python 開頭的 blog :

Entry.objects.filter(headline__startswith="Python")

#支援鏈式操作
Entry.objects.filter(headline__startswith="Python").exclude(pub_date__gte=datetime.now()).filter(pub_date__gte=datetime(2014, 1, 1))

QuerySet 類

QuerySet 接受動態的關鍵字引數,然後轉換成合適的 SQL 語句在資料庫上執行。

QuerySet 的幾個常用方法:

  • distinct
  • values
  • values_list
  • select_related
  • filter:返回一個包含符合指定條件的模型記錄的 QuerySet
  • extra:增加結果集以外的欄位

延時查詢

每次你完成一個 QuerySet,你獲得一個全新的結果集,不包括前面的。每次完成的結果集是可以貯存,使用或複用:

>>> 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 是分開的,第一個是 headline 以 “What” 單詞開頭的結果集,第二個是第一個的子集,即 pub_date 不大於現在的,第三個是第一個的子集 ,pub_date 大於現在的。

QuerySets 是延遲的,建立 QuerySets 不會觸及到資料庫操作,你可以多個過濾合併到一起,直到求值的時候 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 的時候,django 才開始查詢執行 SQL 到資料庫。

可以使用 python 的陣列限制語法限定 QuerySet,如:

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

>>> Entry.objects.all().order_by("headline")[:4]
>>> Entry.objects.all().order_by("headline")[4:8]

一般的,限制 QuerySet 返回新的 QuerySet,不會立即求值查詢,除非你使用了 “step” 引數

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

欄位過濾

欄位查詢是指定 SQL 語句的 WHERE 條件從句,通過 QuerySet 的方法 filter(), exclude()  get() 指定查詢關鍵字。

格式為:field__lookuptype=value

lookuptype 有以下幾種:

  • gt : 大於
  • gte : 大於等於
  • in : 包含
  • lt : 小於
  • lte : 小於等於
  • exact
  • iexact
  • contains:包含查詢,區分大小寫
  • icontains:不區分大小寫
  • startswith:匹配開頭
  • endswith:匹配結尾
  • istartswith:匹配開頭,不區分大小寫
  • iendswith:匹配結尾,不區分大小寫
>>> Entry.objects.filter(pub_date__lte='2006-01-01')

等價於:

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

當實體中存在 ForeignKey 時,其外來鍵欄位名稱為模型名稱加上 ‘_id’:

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

下面是一些舉例:

a、exact

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

相當於:

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

如果查詢沒有提供雙下劃線,那麼會預設 __exact:

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

#主鍵查詢
Entry.objects.get(pk=14) # pk implies id__exact

b、iexact——忽略大小寫

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

將要匹配 blog 名稱為 “Beatles Blog”, “beatles blog”, 甚至是 “BeAtlES blOG”。

c、contains——包含查詢,區分大小寫

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

轉化為 SQL:

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

如果有百分號,則會進行轉義:

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

轉義為:

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

d、in 查詢

# Get blogs  with id 1, 4 and 7
Entry.objects.filter(pk__in=[1,4,7])

跨關係查詢

跨關係查詢是針對有主外來鍵依賴關係的物件而言的,例如上面的 Author 和 Entry 物件是多對多的對映,可以通過 Entry 物件來過濾 Author的 name:

獲取所有 blog 名稱為 Beatles Blog 的 Entry 列表:

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

也可以反向查詢:

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

如果跨越多層關係查詢,中間模型沒有值,django會作為空對待不會發生異常。

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

也支援多條件跨關係查詢:

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

或者:

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

使用 Extra 調整 SQL

用extra可以修復QuerySet生成的原始SQL的各個部分,它接受四個關鍵字引數。如下:

  • select:修改select語句
  • where:提供額外的where子句
  • tables:提供額外的表
  • params:安全的替換動態引數

增加結果集以外的欄位:

queryset.extra(select={'成年':'age>18'}) 

提供額外的 where 條件:

queryset.extra(where=["first like '%小明%' "])

提供額外的表:

queryset.extra(tables=['myapp_person'])

安全的替換動態引數:

##'%s' is not replaced with normal string 
matches = Author.objects.all().extra(where=["first = '%s' "], params= [unknown-input ( ) ])

F 關鍵字引數

前面給的例子裡,我們建立了過濾,比照模型欄位值和一個固定的值,但是如果我們想比較同一個模型裡的一個欄位和另一個欄位的值,django 提供 F()——專門取物件中某列值的操作。

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

當然,還支援加減乘除和模計算:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
>>> 
>>> Entry.objects.filter(authors__name=F('blog__name'))

對於日期型別欄位,可以使用 timedelta 方法:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

還支援位操作 .bitand()  .bitor()

>>> F('somefield').bitand(16)

主鍵查詢

Django 支援使用 pk 代替主鍵:

>>> 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 還可以用於其他的查詢型別:

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

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

Q 關鍵字引數

QuerySet 可以通過一個叫 Q 的關鍵字引數封裝類進一步引數化,允許使用更復雜的邏輯查詢。其結果 Q對 象可以作為 filter 或 exclude 方法的關鍵字引數。

例子:

from django.db.models import Q
Q(question__startswith='What')
支援 & 和 操作符:
Q(question__startswith='Who') | Q(question__startswith='What')

上面的查詢翻譯成 sql 語句:

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

取反操作:

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

也可以用在 filter()exclude()get() 中:

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

刪除物件

>>>entry = Entry.objects.get(pk=1)
>>>entry.delete()
>>>Blog.objects.all().delete()

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

關係物件

當物件之間存在對映關係或者關聯時,該如何查詢呢?

當你在模型裡定義一個關係時,模型例項會有一個方便的 API 來訪問關係物件。以下分幾種對映關係分別描述。

One-to-many關係

如果一個物件有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()

如果關聯的物件可以為空,則可以將關聯物件職位 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.

你也可以通過 模型_set 來訪問關係物件的另一邊,在 Blog 物件並沒有維護 Entry 列表,但是你可以通過下面方式從 Blog 物件訪問 Entry 列表:

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

模型_set 可以通過 related_name 屬性來修改,例如將 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()

Many-to-many關係

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.

One-to-one關係

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

這時候如果沒有關聯物件,則會丟擲 DoesNotExist 異常。

並且還可以修改:

e.entrydetail = ed

相關文章