Django QuerySet 方法梳理 。model外鍵 多對多的保存


引用:https://feifeiyum.github.io/2017/03/28/python-django-queryset/

說明

Models 層是 Django 框架中最強大的部分之一, 大大方便了 Web 層與數據層的交互。由於對 Model 層缺少系統理解,在使用 model Api 時經常需要查找文檔, 在此做一次系統地整理。
本文主要是對 Django Model 文檔的翻譯, 文檔地址

說明

Models 層是 Django 框架中最強大的部分之一, 大大方便了 Web 層與數據層的交互。由於對 Model 層缺少系統理解,在使用 model Api 時經常需要查找文檔, 在此做一次系統地整理。
本文主要是對 Django Model 文檔的翻譯, 文檔地址

Model 層對象

一個model類對應數據庫中的一張表, 類中的屬性代表數據庫中的各個字段。 類實例化后的對象, 代表數據庫中的一條記錄。
本文將基於下面的 models 對象展開, 由 Blog, Author, Entry 三個 models 組成。 Blog, Author 是兩個獨立的 model(表), 沒有任何外鍵字段。 Entry 和 Blog 是多對一的關系, 通過外鍵關聯; Entry 和 Author 是多對多關系。
假設 Blog 類所在目錄為 mysite/blog/models.py, Author 類所在目錄為 mysite/author/models.py, Entry 類所在目錄為 mysite/entry/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
 
class Blog(models.Model):
name = models.CharField(max_length= 100)
tagline = models.TextField()
 
class Author(models.Model):
name = models.CharField(max_length= 200)
email = models.EmailField()
 
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()

創建 model 對象

Model 對象實例化之后,可以調用 save() 方法其寫入到數據庫。

1
2
3
4
5
6
from blog.models import Blog
 
# 實例化對象
b = Blog(name= 'Beatles Blog', tagline='All the latest Beatles news.')
# 將對象寫入數據庫
b.save()

 

當執行 save() 函數時, ORM 會執行 INSERT SQL 語句, 將數據寫入數據庫。在調用 save() 函數之前, 實例不會執行 INSERT 操作。

保存對象更新

假設: 上例中的 b 實例已經存入數據庫, 現在要更新 b 中的 tagline 字段。先更新 tagline 對應的值,然后執行 save() 操作。如下:

1
2
3
4
#更新 tagline 字段
b.tagline = 'All the latest Beatles new 03/28'
# 將更新寫入數據庫
b.save()

 

當執行 save() 操作時, ORM 會執行 UPDATE SQL 語句。

保存外鍵字段

保存外鍵(ForeignKey)字段同正常的字段類似, 也是通過 save() 函數來實現。
假設, 數據庫中已經存在對應的 Blog 和 Entry 對象, 現在要將這個兩個對象關聯起來, 可以進行如下操作:

1
2
3
4
5
6
7
8
9
10
from entry.models import Entry
 
# 獲取 id 為 1 的 entry 對象, 執行 SELECT 操作
entry = Entry.objects.get(pk= 1)
# 獲取 name 為 'Cheddar Talk' 的 blog 對象
cheese_blog = Blog.objects.get(name= 'Cheddar Talk')
# 更新 entry 的 blog 屬性
entry.blog = cheese_blog
# 寫入更新
entry.save()

 

保存多對多字段

更新一個多對多字段, 同外鍵字段不同, 其使用一個專門的 add() 函數添加對應的關聯, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from author.models import Author
 
# 創建 author 對象
# 采用 create() 方法創建對象,會將 create 和 save () 同時進行,
# 不需要單獨調用 save() 方法
joe = Author.objects.create(name= 'Joe')
# entry 為上例中實例的對象
entry.authors.add(joe)
 
# add() 方法可以添加對個參數
john = Author.objects.create(name= 'john')
paul = Author.objects.create(name= 'paul')
ringo = Author.objects.create(name= 'ringo')
entry.authors.add(john, paul, ringo)

 

對象檢索

對數據庫中對象的檢索, 是通過 model Manage 來構造一個 QuerySet 對象來實現。每個 model 類都有一個 Manage方法, Model 類通過 objects 來調用 Manage 方法。 model 對象中沒有 objects 屬性。QuerySet 對象是一個model 類對應的實例集合, 即數據庫對應表的子集。
QuerySet 可以是 空(zero), 單個對象(one), 多個對象(many).
QuerySet 通過 Filters 方法來實現查詢結果的過濾。 對於 SQL 來說, QuerySet 等同於 SELECT 聲明, Filter 等同於 LIMIT, WHERE 聲明。

檢索所有對象

查找 model 類對應表中的所有對象 (數據), 是通過 all() 方法來實現, 其返回一個 QuerySet 對象。

1
2
# Entry model,
all_entries = Entry.objects.all()

 

通過 filters 檢索特定對象

通常查詢數據庫是,只是檢索對應表中的一條或幾條數據, 其主要通過一下兩種方法來實現:

1、filter(**kwargs)

通過 filter 中的條件(kwargs) 進數據庫查詢特定的數據, 返回一個 QuerySet 對象。
2、exclude(**kwargs)
通過 exclude 中的條件, 排除特定的數據, 返回表中的剩余數據, 返回結果為 QuerySet 對象。

例: 查詢姓名為 paul 的作者:

1
2
# 返回的 QuerySet 對象中只包含 name='paul' 的 model 對象
Author.objects.filter(name='paul')

 

例: 查詢姓名不為 paul 的所有作者:

1
2
# 返回的 QuerySet 對象中,不包含 name='pual' 的 model 對象
Author.objects.exclude(name= 'paul')

 

3、filter 級聯

filter, exclude 等方法不僅僅可以單獨使用, 也可以級聯進行使用

例: 查找 entry 中 headline 由 What 開頭, 不是有今天發布的, 發布日期大於 2017/03/10 的數據, 共三個條件。

1
2
3
4
5
6
7
8
9
10
Entry.objects.filter(
#條件1, 由 What 開頭
headline__startswith= 'What'
).exclude(
#條件2, 不由今天(2017-03-28)發布
pub_data__gte=datetime.date.today()
).filter(
# 條件3, 發布日期大於 2017/03/10
pub_data__gte=datetime( 2017, 3, 10)
)

 

上面所列的三個條件最終會整合成一條 SQL 語句去執行:
SELECT * FROM entry(表名) WHERE headline LIKE ‘What%’ AND NOT pub_date = ‘2017-3-28’ AND pub_date > ‘2017-3-10’

4、每個 filter 返回的對象都是不相關的

每次查詢生成的 QuerySet 對象都是相互獨立的, 可以保存或重復使用

1
2
3
4
q1 = Entry.objects.filter(headline__startwith= 'What')
# QuerySet 對象中有 filter, exclude 方法
q2 = q1.exclude(pub_data__gte=datetime.date.today())
q3 = q1.filter(pub_data__gte=datetime.date.today())

 

上面的三個 QuerySet 對象 q1, q2, q3 中, 是相互獨立的,擁有各自獨立的內存空間。

5、QuerySet 懶加載 (lazy)

QuerySet are lazy - 暫且套用前端的懶加載名詞稱之為懶加載。其意思為,在 QuerySet 對象創建的時候,是不會進行數據庫查詢操作的。只有在使用這個對象的時候才會進行數據庫查詢操作。

1
2
3
4
q = Entry.objects.filter(headline__startwith= 'What')
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains= 'food')
print(q)

 

上例中, 雖然在 print(q) 有三次 QuerySet 對象的 filter 操作。但是,他們都不會實際進行數據庫查詢操作, 知道在使用這個 QuerySet 對象的時候。即,在 print(q) 的時候執行真正的數據庫查詢操作。

檢索單個 model 對象

上面介紹的 filter() 等方法返回的結果是 QuerySet 對象。如果明確知道有且只有一個對象可以查詢到時,可以使用 get() 方法進行查詢, 其返回結果為一個 model 對象。

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

 

返回的 one_entry 為 model 對象, 而不是 QuerySet 對象。
采用這種方法查詢是,如果查詢結果為空,將拋出 DoesNotExist 異常。在一般情況下,不建議使用可以使用如下方式代替:

1
2
3
one_entry = Entry.objects.filter(pk= 1)
if one_entry.exists():
one_entry = one_entry[ 0]

 

QuerySet 的其他方法

當進行數據庫查詢時,常用的也就是 all(), get(), filter() 和 exclude() 等方法。但是這些方法,無法完成一些復雜的查詢方法。下面將介紹一些 QuerySet 的復雜的查詢方法。

Limiting 查詢

利用 Python Array 中的分片, 可以限制返回查詢結果的數量。 其對應的 SQL 語法為 LIMIT 和 OFFSET。
返回結果為 QuerySet 對象

例如:
限制返回查詢結果集中的前5組數據 (LIMIT 5)

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

 

返回查詢結果集中5~9這5組數據 ( OFFSET 5 LIMIT 5)

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

 

在查詢結果集中前10組數據中取5組數據, 取數據的 step 等於 2

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

 

返回查詢結果集中的第一個數據, 即 SELECT * FROM table1 LIMIT 1

1
2
3
4
5
6
7
# 返回長度為 1 的 QuerySet 對象
Entry.objects.all()[ 0:1]
# 注意其返回的不是 QuerySet 對象, 而是 Entry 的 model 對象
# 如果查詢結果不存在, 將會拋出 DoesNotExist 異常
Entry.objects.all()[ 0]
# Entry.objects.all()[0] 等同於如下:
Entry.objects.all()[ 0:1].get()

 

按字段查詢

按字段查詢, 在 SQL 中對應的 WHERE 條件。這些查詢條件是以參數形式出入 QuerySet 方法 (filter, exclude, get) 中, 這個在前面例子中有涉及。
基本查詢條件配置方式: fieldlookuptype=value, field 為字段名稱, lookuptype 為查詢類型, value為類型值。 field 和 lookuptype 通過 雙下划線連接。
例如: 查詢在今天之前發表的 Entry

1
2
3
Entry.objects.filter(pub_date__lte=datetime.date.today())
# 其對應如下 SQL 語句, 假設今天為 2017-03-30
# SELECT * FROM entry WHERE pub_date <= '2017-03-30';

 

如果根據外鍵的查詢時, 可以根據對應的字段名稱加上 _id 后綴進行查詢, 其實在 model migration 之后, 數據庫中外鍵所在字段名稱就是 model 中的屬性名加上后綴 _id。
例如查詢 id 為 4 的 blog 對象所關聯的 Entry:

1
Entry.objects.filter(blog_id= 4)

 

如果傳入的查詢條件不對時, 其將會拋出 TypeError 異常。

常見查詢類型

1、exact 精確匹配
根據對應字段值查詢,大小寫敏感

1
2
Entry.objects.filter(headline__excat= 'cat bites dog')
# SQL: SELECT * from entry WHERE headline = 'cat bites dog'

 

如果不添加 __ 對應的查詢類型, 則默認是 exact 匹配。

1
2
3
# 以下兩條查找語句等價
Entry.objects.filter(id__excat= 13)
Entry.objects.filter(id= 13)

 

2、 iexact 匹配
根據對應字段值查詢,但大小寫不敏感

1
2
3
4
Entry.objects.filter(headline__iexact= 'cat Bites Dog')
# 估計類型下列查詢語句
# SQL: SELECT * FROM entry WHERE lower(headline) = lower('cat Bites Dog')
# 或者SQL: SELECT * FROM entry WHERE UPPER(headline) = UPPER('cat Bites Dog')

 

即: 表中 headline 字段值為 Cat Bites dog 或 Cat BITES dog 等都會命中返回

3、contains 查詢
查詢對應字段值包含某些字符的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__contains= 'Lennon')
# SQL: SELECT * FROM entry WHERE headline LIKE '%Lennon%'

 

4、icontains 查詢
同 3、 contains 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__icontains= 'Lennon')

 

5、startswith 查詢
查詢對應字段值中, 以特定某些字符開頭的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__startswith= 'cat')
# SQL: SELECT * FROM entry WHERE headline LIKE 'cat%'

 

6、istartswith 查詢
同 5、startswith 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__istartswith= 'Cat')

 

7、endswith 查詢
查詢對應字段值中, 以特定某些字符結尾的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__startswith= 'dog')
# SQL: SELECT * FROM entry WHERE headline LIKE '%dog'

 

8、iendswith 查詢
同 7、endswith 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__istartswith= 'Cat')

 

跨表查詢

Django 提供了強大的跨表查詢方式(lookup span relationship), 完成 SQL 中的 JOINs 查詢。
使用與外表關聯的字段名(field name) 和 對應表中對應的字段名通過雙下划線聯接起來,即可完成兩張表的 JOIN 查詢
例如: 查詢名為 beatles blog 的 blog 對應的 entry

1
2
3
4
5
# blog 為 Entry mode 外鍵字段, name 為 Blog model 中的字段, __ 聯接
# 分解成兩步理解: 1、根據 blog 表中 name='beatles blog' 得到對應的數據,
# 再 1、中獲得的結果,到 entry 表中查詢出最終的結果
Entry.objects.filter(blog__name= 'beatles blog')
# SQL 估計為: SELECT a.id, a.blog, a.headline ... FROM entry a INNER JOIN blog b ON a.blog = b.id WHERE b.name = 'beatles blog'

 

上例中是根據 blog 查詢對應的 entry, 從 entry 反推 blog 也是可以實現的,
例如: 查詢 headline 中包含 Lennon 的 entry 對應的 blog
查詢方式, 使用小寫的 Model 名(即: entry) 關聯對應的字段名(headline) 再添加相應的查詢類型和其值: 如下:

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

 

更進一步, 跨三張表的查詢, 也很容易實現:
例如: 根據 auther 名查詢對應的 blog, 這個過程中關系到三張表, entry, author, blog。簡單分析:根據 author 表中的查詢結果, 找出 entry 表中的符合條件的數據, 再根據從 entry 表中獲得結果, 獲取 blog 表中對應的數據。
查詢方式, 小寫的 Model 名(entry) 關聯對應的字段名 authors(不是表 author) 關聯對應 author 表中的字段名(name) 再添加相應的查詢類型和其值:

1
2
#
Blog.objects.filter(entry__authors__name= 'Lennon')

 

在跨表查詢時, 在執行 filter 操作是,可以添加多個過濾參數,以達到更復雜的查詢邏輯
例如:

1
2
3
4
# 查詢 entry 中 headline 包含 Lennon 的 Blog 和 發表日期在 2013 年的 blog, 兩個查詢條件結果取並集
Blog.objects.filter(entry__headline__contains= 'Lennon', entry__pub_date__year=2013)
# 查詢 entry 中 headline 包含 Lennon 的 Blog 並且 發表日期是 2013 年的, 兩個查詢條件取交集
Blog.objects.filter(entry__headline__contains= 'Lennon').filter(entry__pub_date__year=2013)

 

根據同一表(model)內字段查詢

Django 提供了 F 表達式(expressions) 來實現, 同一個表內不同的兩個字段的對比。F() 表現一次查詢實例的引用。
例: 查詢 entry 表中 n_comments 值大於 n_pingbacks 的數據:

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

 

F() 支持邏輯運算: 加,減,乘,除, 取模, 冪等。

1
2
Entry.objects.filter(n_comments__gt=F( 'n_pingbacks') * 2)
Entry.objects.filter(rating__gt=F( 'n_pingbacks') + F('n_comments'))

 

F() 也支持雙下划線的表關聯操作,

1
2
3
4
5
#查詢: entry 表中 author name 等於 blog name 的數據
Entry.objects.filter(authors__name=F( 'blog__name'))
#查詢, entry 表中發表3天后還有修改的數據
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F( 'pub_date') + timedelta(day=3))

 

F() 可以通過方法 .bitand() 和 .bitor() 實現按位與運算和或運算。

pk 縮寫

在 Django 中采用 pk 縮寫來表示 ‘primary key’, 表中的 id (primary key) 字段,可以使用 pk 來表示
例如: 下面的查詢語句等價

1
2
3
Blog.objects.get(id__exact= 13)
Blog.objects.get(id= 13)
Blog.objects.get(pk= 13)

 

pk 除 exact 查詢類型外,也可以使用其他的查詢類型

1
2
3
4
# 查找 id 為 1,2,3 的 blog 數據
Blog.objects.filter(pk__in=[ 1,2,3])
# 查找 id 大於 13 的數據
Blog.objects.filter(pk_gt= 13)

 

pk 可以使用在關聯表的查詢中

1
2
3
#查找 entry 表中 blog id 為 13 的數據
Entry.objects.filter(blog__id= 13)
Entry.objects.filter(blog__pk= 13)

 

QuerySet 緩存

由於 QuerySet 懶加載的原因,在一個新生成的 QuerySet 對象中是沒有緩存的。 當其第一次使用的時候才會去請求數據庫拉取數據。 此時 Django 會將查詢到的結果緩存起來,當第二次使用該 QuerySet 對象時,就不需要重新請求數據庫。
當然要獲得緩存帶來的便利,要保持正確的使用姿勢才行, 例如:

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

 

上面兩句代碼中, QuerySet 對象將會查詢兩次數據庫。因為,每使用一次 Entry.objects.all() 都會生成一個新的 QuerySet 對象。 其本質上, 第一 print 語句執行時使用的 是一個 QuerySet 對象, 第二個 print 語句執行的是另一個 QuerySet 對象, 雖然說前后兩個 QuerySet 對象是擁有相同的值, 但他們不是共享一個內存空間,是相互獨立的。
正確的使用姿勢如下:

1
2
3
4
5
queryset = Entry.objects.all()
# 請求數據庫
print([e.headline for e in queryset])
# 使用對象 queryset 對象的緩存
print([e.pub_date for e in queryset])

 

無法使用緩存的情況
QuerySet 並不是所有情況都會設置緩存的, 當查詢 QuerySet 對象中部分值時, 會先去檢查下緩存, 如果緩存中沒有, 將會請求數據拉取數據, 但是這部分數據將不會被緩存。因此,在第一次使用 QuerySet 對象時, 如果是通過 python slice 或 數組下標(index) 去限制所取請求結果的子集時, 這部分結果將不會加入緩存。
其原因大致是使用了 slice 或 index 時數據查詢時的 SQL 語句會額外添加 LIMIT, OFFSET 條件, 而 queryset 不做部分數據的緩存。
例如:

1
2
3
4
5
6
queryset = Entry.objects.all()
# 請求數據庫拉取第5條數據,
# 通過數組下標(index)取數據, 其結果不緩存
print(queryset[ 5])
# 因為沒有換成, 再一次拉取第5條數據時, 會重新請求數據庫
print(queryset[ 5])

 

假如在使用 slice 或 index 取數據之前,已經對 QuerySet 對象求值(不是部分求值), 即可使用 Queryset 對象的緩存

1
2
3
4
5
6
7
queryset = Entry.objects.all()
# 因為使用了整個 queryset 對象
[entry for entry in Entry.objects.all()]
# 從緩存中取數據
print(queryset[ 5])
# 從緩存中取數據
print(queryset[ 5])

 

使用 Q 對象進行復雜查找

在 QuerySet 對象的 filter() 方法中, 要用 AND 或者 OR 組合查詢條件, 來執行更復雜的查詢時, 可以使用 Q 對象。
Q 對象使用 &, | 和 ~ 來分別表示 SQL 中的 AND, OR 和 NOT
例如: 查詢 entry 表中 headline 以 ‘what’ 或 ‘who’ 開頭的數據

1
2
3
from django.db.models import Q
entry = Entry.objects.filter(Q(headline__startswith= 'what') | Q(headline__startswith='who'))
# SQL: SELECT * FROM entry WHERE headline LIKE 'what%' OR headline LIKE 'who%'

 

如果在 filer() 方法中多個 Q 對象作為參數時, 則不同參數之間將會以 AND 形式組合, 例如:

1
2
Entry.objects.filter(Q(headline__startswith= 'what'), Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))
# SQL SELECT * FROM entry WHERE headline LIKE 'what%' AND (pub_date='2017-3-29' OR pub_date='2017-3-31')

 

Q 對象和普通的查詢條件可以混合使用, 但是在 filter 中普通的查詢條件,要放在 Q 對象之后傳入, 否則將會是無效查詢

1
2
3
4
# 有效查詢
Entry.objects.filter(Q(pub_date=date( 2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)), headline__startswith='what')
# 無效查詢, 因為普通查詢條件在 Q 對象之前
Entry.objects.filter(headline__startswith= 'what', Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))

 

對象刪除

1、通過 model 對象刪除
一個 model 對象擁有 delete() 方法, 當要刪除某一條數據時, 可以直接執行 delete 方法

1
2
entry = Entry.objects.get(pk= 1)
entry.delete()

 

2、通過 QuerySet 對象刪除
QuerySet 對象也有 delete() 方法, 其將會刪除所有包含的成員。
例如: 刪除 entry 表中所有在 2016 年發布的數據

1
2
3
Entry.objects.filter(pub_date__year= 2016).delete()
#刪除表中所有數據
Entry.bojects.all().delete()

 

當 Django 刪除一個數據時, 其默認或使用 SQL 的 CASCADE 刪除模式, 即: 在父表上 delete 刪除一條數據是,與之通過 ForeignKey 關聯的子表中的數據也對應刪除。

復制數據

在 Django 中復制一條數據只需要將對應的 model 對象的 pk(id) 清除即可

1
2
3
4
5
blog = Blog(name= 'My blog', tagline='Blogging is easy')
blog.save() # blog.pk = 1
# 復制一條
blog.pk = None
blog.save() # blog.pk = 2

 

但是這種拷貝不會 many-to-many 的對應關系,因此在復制數據后,需要更新其多對多關系

1
2
3
4
5
entry = Entry.objects.all()[ 0]
auther_old = entry.authors.all() #提取 authors
entry.pk = None
entry.save()
entry.authors.set(auther_old) # 更新對應關系

 

對於 one-to-one 對應關系的數據, 在拷貝后要先更新一個新的對應關系后,開可以執行 save() 操作。

通過 QuerySet 執行更新操作

QuerySet 對象使用 update() 方法更新數據, 與 model 對象使用 save 不同。
update() 方法調用后會立即執行數據庫 Update 操作
1、更新不是外鍵的字段
例: entry 表中在 2017 年發布數據的 headline 字段都更新為 ‘Everything is the same’

1
Entry.objects.filter(pub_date__year= 2017).update(headline='Everything is the same')

 

2、更新外建字段
例: 更新 entry 表中 blog 字段關聯的外鍵

1
2
b = Blog.objects.get(pk= 1)
Entry.objects.all().update(blog=b)

 

如果想要更新某一行數據, 可以使用 filter 來進行過濾

1
2
b = Blog.objects.get(pk= 1)
Entry.objects.filter(blog=b).update(headline= 'Everything is the same')

 

注意: 執行 update() 方法時, 會執行生成相應的 SQL 語句。不會去執行 model 對象里面的 save() 方法, 也不會出發 pre_save 和 post_save 這兩個鈎子函數。添加了 auto_now 屬性的字段也無法執行。如果需要執行這些方法的操作, 可以遍歷 QuerySet 對象中的每一個 model 實例去執行 save() 方法。

1
2
for item in my_queryset:
item.save()

 

3、使用 F 表達式執行更新操作
在根據字段現有值去執行更新操作的場景中, F 表達比較實用
例如: 對 entry 中 n_pingbacks 字段執行遞增操作

1
2
from django.db.models import F
Entry.objects.all().update(n_pingbacks=F( 'n_pingbacks') + 1)

 

但是通過 F 表達式無法執行, 有 join 操作的更新, 即通過雙下划線 __ 關聯的操作
例如: 將 entry 表中所有數據的 headline 字段更新為其對應 blog 表中的 name, 無法通過 F 表達式實現

1
2
# 下面語句執行后會拋出 FieldError 異常
Entry.objects.update(headline=F( 'blog__name'))

 

對象的對應關系

當在 model 定義是添加了 ForeignKey, OneToOneFiedl, ManyToMangField 的字段時, model 會自動生成相關 API 來獲取相關數據。

One-To-Many 關系

1、正向獲取 父表 to 子表
如果一個 model 包含有 ForeignKey 字段, 這個 model 的對象可以方便的獲取與之關聯的另一個 model 的對象。
例如: 獲取 Entry 對象對應的 Blog 對象

1
2
3
4
5
6
e = Entry.objects.get(id= 2)
e.blog # 返回通過外鍵關聯的 blog 對象
#如果要更新 e 對象的 blog 屬性
b = Blog.objects.get(id= 3)
e.blog = b
e.save() # 執行根系操作,

 

one-to-many 關系在第一次使用后將會被緩存

1
2
3
e = Entry.objects.get(id= 2)
print(e.blog) # 查詢數據, 並將數據緩存
print(e.blog) # 不查詢數據庫, 之間中緩存中讀取

 

使用 QuerySet 的 select_related() 方法時, 會將相應的 one-to-many 關系的對象都預先取出來並緩存, 在真正使用時就不會訪問數據庫

1
2
3
e = Entry.objects.select_related().get(id= 2)
print(e.blog) # 不查詢數據庫
print(e.bong) # 不查詢數據庫

 

2、反向獲取 子表 to 父表
如果 model A 通過 ForeignKey字段 field 與 model B 想關聯。 B 對象可以通過 model Manager 去訪問與之對應的所有的 A 對象。 默認的, 這個 model Manage 名為 foo_set, 其中 foo 是擁有外鍵那個 model 名的小寫, 即 a_set()
例: 通過 Blog 對象查詢 Entry 對象:

1
2
3
4
5
6
# 查詢與 Blog 對象 b 關聯的所有 entry 對象
b = Blog.objects.get(pk= 2)
b.entry_set.all()
 
# 查詢與 Blog 對象 b 關聯的 entry 對象中 headline 包含 'Lennon' 的
b.entry_set.filter(headline__contains= 'Lennon')

 

如果在定義 ForeignKey 字段時 通過 related_name 可以更改這個默認的 foo_set() Manage 方法。
例如: 將最頂部的 Entry Model 中的 blog 字段修改成如下: blog = ForeignKey(Blog, related_name=’entries’), 上面的代碼中的 entry_set 就可以都改成 entries。

1
2
3
4
5
6
# 查詢與 Blog 對象 b 關聯的所有 entry 對象
b = Blog.objects.get(pk= 2)
b.entries.all()
 
# 查詢與 Blog 對象 b 關聯的 entry 對象中 headline 包含 'Lennon' 的
b.entries.filter(headline__contains= 'Lennon')

 

Many-To-Many 關系

對於 many-to-many 關系的 API 使用方法與上面的 one-to-many 關系的一致。區別在於, 在命名 ManyToMany 字段時, 字段名不要與其對應的 model 名的小寫一致。例如 Entry 中, authors 字段名稱不與其對應 Model(Author) 的小寫名 author 相同。 這點與 blog 字段不同。

1
2
3
4
5
6
7
8
e = Entry.objects.get(id= 3)
e.authors.all() # 返回 e 對象對應的所有 authors
e.authors.count() # authors 的數量
e.authors.filter(name__contains= 'John') # 返回名字中包含 John 的作者
 
a = Author.objects.get(id= 5)
# 返回所有與 a 對象對應的 Entry 對象
a.entry_set.all()

 

One-To-One 關系

One-to-one 關系同 many-to-one 非常相似, API 用法與 many-to-one 的基本也基本一致

1
2
3
4
5
6
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
 
ed = EntryDetail.objects.get(pk= 3)
en.entry # 返回與之對應的 Entry 對象

 

與 many-to-one 不同的是其反向查找, 如下:

1
2
3
4
5
6
7
8
e = Entry.objects.get(pk= 3)
# 取得與 Entry 對象對應的 EntryDetail 對象,
# 只需調用 EntryDetail 的小寫 entrydetail 即可
e.entrydetail
 
#更新
e.entrydetail = ed2
e.save()

 

通過關聯對象查詢

當查詢過濾條件傳入 filter 函數是,既可以使用整個對象,可以使用相應的對象值。

1
2
3
4
# Blog 對象 b 的 id = 5
Entry.objects.filter(blog=b) # 通過整個對象進行查詢
Entry.objects.filter(blog=b.id) # 通過 b 的 id 屬性查詢
Entry.objects.filter(blog= 5) # 直接用 id 值查詢 hard code

 

做個標記: 下期整理 QuerySet 比較常用的 API

Model 層對象

一個model類對應數據庫中的一張表, 類中的屬性代表數據庫中的各個字段。 類實例化后的對象, 代表數據庫中的一條記錄。
本文將基於下面的 models 對象展開, 由 Blog, Author, Entry 三個 models 組成。 Blog, Author 是兩個獨立的 model(表), 沒有任何外鍵字段。 Entry 和 Blog 是多對一的關系, 通過外鍵關聯; Entry 和 Author 是多對多關系。
假設 Blog 類所在目錄為 mysite/blog/models.py, Author 類所在目錄為 mysite/author/models.py, Entry 類所在目錄為 mysite/entry/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
 
class Blog(models.Model):
name = models.CharField(max_length= 100)
tagline = models.TextField()
 
class Author(models.Model):
name = models.CharField(max_length= 200)
email = models.EmailField()
 
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()

創建 model 對象

Model 對象實例化之后,可以調用 save() 方法其寫入到數據庫。

1
2
3
4
5
6
from blog.models import Blog
 
# 實例化對象
b = Blog(name= 'Beatles Blog', tagline='All the latest Beatles news.')
# 將對象寫入數據庫
b.save()

 

當執行 save() 函數時, ORM 會執行 INSERT SQL 語句, 將數據寫入數據庫。在調用 save() 函數之前, 實例不會執行 INSERT 操作。

保存對象更新

假設: 上例中的 b 實例已經存入數據庫, 現在要更新 b 中的 tagline 字段。先更新 tagline 對應的值,然后執行 save() 操作。如下:

1
2
3
4
#更新 tagline 字段
b.tagline = 'All the latest Beatles new 03/28'
# 將更新寫入數據庫
b.save()

 

當執行 save() 操作時, ORM 會執行 UPDATE SQL 語句。

保存外鍵字段

保存外鍵(ForeignKey)字段同正常的字段類似, 也是通過 save() 函數來實現。
假設, 數據庫中已經存在對應的 Blog 和 Entry 對象, 現在要將這個兩個對象關聯起來, 可以進行如下操作:

1
2
3
4
5
6
7
8
9
10
from entry.models import Entry
 
# 獲取 id 為 1 的 entry 對象, 執行 SELECT 操作
entry = Entry.objects.get(pk= 1)
# 獲取 name 為 'Cheddar Talk' 的 blog 對象
cheese_blog = Blog.objects.get(name= 'Cheddar Talk')
# 更新 entry 的 blog 屬性
entry.blog = cheese_blog
# 寫入更新
entry.save()

 

保存多對多字段

更新一個多對多字段, 同外鍵字段不同, 其使用一個專門的 add() 函數添加對應的關聯, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from author.models import Author
 
# 創建 author 對象
# 采用 create() 方法創建對象,會將 create 和 save () 同時進行,
# 不需要單獨調用 save() 方法
joe = Author.objects.create(name= 'Joe')
# entry 為上例中實例的對象
entry.authors.add(joe)
 
# add() 方法可以添加對個參數
john = Author.objects.create(name= 'john')
paul = Author.objects.create(name= 'paul')
ringo = Author.objects.create(name= 'ringo')
entry.authors.add(john, paul, ringo)

 

對象檢索

對數據庫中對象的檢索, 是通過 model Manage 來構造一個 QuerySet 對象來實現。每個 model 類都有一個 Manage方法, Model 類通過 objects 來調用 Manage 方法。 model 對象中沒有 objects 屬性。QuerySet 對象是一個model 類對應的實例集合, 即數據庫對應表的子集。
QuerySet 可以是 空(zero), 單個對象(one), 多個對象(many).
QuerySet 通過 Filters 方法來實現查詢結果的過濾。 對於 SQL 來說, QuerySet 等同於 SELECT 聲明, Filter 等同於 LIMIT, WHERE 聲明。

檢索所有對象

查找 model 類對應表中的所有對象 (數據), 是通過 all() 方法來實現, 其返回一個 QuerySet 對象。

1
2
# Entry model,
all_entries = Entry.objects.all()

 

通過 filters 檢索特定對象

通常查詢數據庫是,只是檢索對應表中的一條或幾條數據, 其主要通過一下兩種方法來實現:

1、filter(**kwargs)

通過 filter 中的條件(kwargs) 進數據庫查詢特定的數據, 返回一個 QuerySet 對象。
2、exclude(**kwargs)
通過 exclude 中的條件, 排除特定的數據, 返回表中的剩余數據, 返回結果為 QuerySet 對象。

例: 查詢姓名為 paul 的作者:

1
2
# 返回的 QuerySet 對象中只包含 name='paul' 的 model 對象
Author.objects.filter(name='paul')

 

例: 查詢姓名不為 paul 的所有作者:

1
2
# 返回的 QuerySet 對象中,不包含 name='pual' 的 model 對象
Author.objects.exclude(name= 'paul')

 

3、filter 級聯

filter, exclude 等方法不僅僅可以單獨使用, 也可以級聯進行使用

例: 查找 entry 中 headline 由 What 開頭, 不是有今天發布的, 發布日期大於 2017/03/10 的數據, 共三個條件。

1
2
3
4
5
6
7
8
9
10
Entry.objects.filter(
#條件1, 由 What 開頭
headline__startswith= 'What'
).exclude(
#條件2, 不由今天(2017-03-28)發布
pub_data__gte=datetime.date.today()
).filter(
# 條件3, 發布日期大於 2017/03/10
pub_data__gte=datetime( 2017, 3, 10)
)

 

上面所列的三個條件最終會整合成一條 SQL 語句去執行:
SELECT * FROM entry(表名) WHERE headline LIKE ‘What%’ AND NOT pub_date = ‘2017-3-28’ AND pub_date > ‘2017-3-10’

4、每個 filter 返回的對象都是不相關的

每次查詢生成的 QuerySet 對象都是相互獨立的, 可以保存或重復使用

1
2
3
4
q1 = Entry.objects.filter(headline__startwith= 'What')
# QuerySet 對象中有 filter, exclude 方法
q2 = q1.exclude(pub_data__gte=datetime.date.today())
q3 = q1.filter(pub_data__gte=datetime.date.today())

 

上面的三個 QuerySet 對象 q1, q2, q3 中, 是相互獨立的,擁有各自獨立的內存空間。

5、QuerySet 懶加載 (lazy)

QuerySet are lazy - 暫且套用前端的懶加載名詞稱之為懶加載。其意思為,在 QuerySet 對象創建的時候,是不會進行數據庫查詢操作的。只有在使用這個對象的時候才會進行數據庫查詢操作。

1
2
3
4
q = Entry.objects.filter(headline__startwith= 'What')
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains= 'food')
print(q)

 

上例中, 雖然在 print(q) 有三次 QuerySet 對象的 filter 操作。但是,他們都不會實際進行數據庫查詢操作, 知道在使用這個 QuerySet 對象的時候。即,在 print(q) 的時候執行真正的數據庫查詢操作。

檢索單個 model 對象

上面介紹的 filter() 等方法返回的結果是 QuerySet 對象。如果明確知道有且只有一個對象可以查詢到時,可以使用 get() 方法進行查詢, 其返回結果為一個 model 對象。

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

 

返回的 one_entry 為 model 對象, 而不是 QuerySet 對象。
采用這種方法查詢是,如果查詢結果為空,將拋出 DoesNotExist 異常。在一般情況下,不建議使用可以使用如下方式代替:

1
2
3
one_entry = Entry.objects.filter(pk= 1)
if one_entry.exists():
one_entry = one_entry[ 0]

 

QuerySet 的其他方法

當進行數據庫查詢時,常用的也就是 all(), get(), filter() 和 exclude() 等方法。但是這些方法,無法完成一些復雜的查詢方法。下面將介紹一些 QuerySet 的復雜的查詢方法。

Limiting 查詢

利用 Python Array 中的分片, 可以限制返回查詢結果的數量。 其對應的 SQL 語法為 LIMIT 和 OFFSET。
返回結果為 QuerySet 對象

例如:
限制返回查詢結果集中的前5組數據 (LIMIT 5)

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

 

返回查詢結果集中5~9這5組數據 ( OFFSET 5 LIMIT 5)

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

 

在查詢結果集中前10組數據中取5組數據, 取數據的 step 等於 2

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

 

返回查詢結果集中的第一個數據, 即 SELECT * FROM table1 LIMIT 1

1
2
3
4
5
6
7
# 返回長度為 1 的 QuerySet 對象
Entry.objects.all()[ 0:1]
# 注意其返回的不是 QuerySet 對象, 而是 Entry 的 model 對象
# 如果查詢結果不存在, 將會拋出 DoesNotExist 異常
Entry.objects.all()[ 0]
# Entry.objects.all()[0] 等同於如下:
Entry.objects.all()[ 0:1].get()

 

按字段查詢

按字段查詢, 在 SQL 中對應的 WHERE 條件。這些查詢條件是以參數形式出入 QuerySet 方法 (filter, exclude, get) 中, 這個在前面例子中有涉及。
基本查詢條件配置方式: fieldlookuptype=value, field 為字段名稱, lookuptype 為查詢類型, value為類型值。 field 和 lookuptype 通過 雙下划線連接。
例如: 查詢在今天之前發表的 Entry

1
2
3
Entry.objects.filter(pub_date__lte=datetime.date.today())
# 其對應如下 SQL 語句, 假設今天為 2017-03-30
# SELECT * FROM entry WHERE pub_date <= '2017-03-30';

 

如果根據外鍵的查詢時, 可以根據對應的字段名稱加上 _id 后綴進行查詢, 其實在 model migration 之后, 數據庫中外鍵所在字段名稱就是 model 中的屬性名加上后綴 _id。
例如查詢 id 為 4 的 blog 對象所關聯的 Entry:

1
Entry.objects.filter(blog_id= 4)

 

如果傳入的查詢條件不對時, 其將會拋出 TypeError 異常。

常見查詢類型

1、exact 精確匹配
根據對應字段值查詢,大小寫敏感

1
2
Entry.objects.filter(headline__excat= 'cat bites dog')
# SQL: SELECT * from entry WHERE headline = 'cat bites dog'

 

如果不添加 __ 對應的查詢類型, 則默認是 exact 匹配。

1
2
3
# 以下兩條查找語句等價
Entry.objects.filter(id__excat= 13)
Entry.objects.filter(id= 13)

 

2、 iexact 匹配
根據對應字段值查詢,但大小寫不敏感

1
2
3
4
Entry.objects.filter(headline__iexact= 'cat Bites Dog')
# 估計類型下列查詢語句
# SQL: SELECT * FROM entry WHERE lower(headline) = lower('cat Bites Dog')
# 或者SQL: SELECT * FROM entry WHERE UPPER(headline) = UPPER('cat Bites Dog')

 

即: 表中 headline 字段值為 Cat Bites dog 或 Cat BITES dog 等都會命中返回

3、contains 查詢
查詢對應字段值包含某些字符的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__contains= 'Lennon')
# SQL: SELECT * FROM entry WHERE headline LIKE '%Lennon%'

 

4、icontains 查詢
同 3、 contains 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__icontains= 'Lennon')

 

5、startswith 查詢
查詢對應字段值中, 以特定某些字符開頭的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__startswith= 'cat')
# SQL: SELECT * FROM entry WHERE headline LIKE 'cat%'

 

6、istartswith 查詢
同 5、startswith 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__istartswith= 'Cat')

 

7、endswith 查詢
查詢對應字段值中, 以特定某些字符結尾的數據, 大小寫敏感

1
2
Entry.objects.filter(headline__startswith= 'dog')
# SQL: SELECT * FROM entry WHERE headline LIKE '%dog'

 

8、iendswith 查詢
同 7、endswith 查詢, 但大小寫不敏感

1
Entry.objects.filter(headline__istartswith= 'Cat')

 

跨表查詢

Django 提供了強大的跨表查詢方式(lookup span relationship), 完成 SQL 中的 JOINs 查詢。
使用與外表關聯的字段名(field name) 和 對應表中對應的字段名通過雙下划線聯接起來,即可完成兩張表的 JOIN 查詢
例如: 查詢名為 beatles blog 的 blog 對應的 entry

1
2
3
4
5
# blog 為 Entry mode 外鍵字段, name 為 Blog model 中的字段, __ 聯接
# 分解成兩步理解: 1、根據 blog 表中 name='beatles blog' 得到對應的數據,
# 再 1、中獲得的結果,到 entry 表中查詢出最終的結果
Entry.objects.filter(blog__name= 'beatles blog')
# SQL 估計為: SELECT a.id, a.blog, a.headline ... FROM entry a INNER JOIN blog b ON a.blog = b.id WHERE b.name = 'beatles blog'

 

上例中是根據 blog 查詢對應的 entry, 從 entry 反推 blog 也是可以實現的,
例如: 查詢 headline 中包含 Lennon 的 entry 對應的 blog
查詢方式, 使用小寫的 Model 名(即: entry) 關聯對應的字段名(headline) 再添加相應的查詢類型和其值: 如下:

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

 

更進一步, 跨三張表的查詢, 也很容易實現:
例如: 根據 auther 名查詢對應的 blog, 這個過程中關系到三張表, entry, author, blog。簡單分析:根據 author 表中的查詢結果, 找出 entry 表中的符合條件的數據, 再根據從 entry 表中獲得結果, 獲取 blog 表中對應的數據。
查詢方式, 小寫的 Model 名(entry) 關聯對應的字段名 authors(不是表 author) 關聯對應 author 表中的字段名(name) 再添加相應的查詢類型和其值:

1
2
#
Blog.objects.filter(entry__authors__name= 'Lennon')

 

在跨表查詢時, 在執行 filter 操作是,可以添加多個過濾參數,以達到更復雜的查詢邏輯
例如:

1
2
3
4
# 查詢 entry 中 headline 包含 Lennon 的 Blog 和 發表日期在 2013 年的 blog, 兩個查詢條件結果取並集
Blog.objects.filter(entry__headline__contains= 'Lennon', entry__pub_date__year=2013)
# 查詢 entry 中 headline 包含 Lennon 的 Blog 並且 發表日期是 2013 年的, 兩個查詢條件取交集
Blog.objects.filter(entry__headline__contains= 'Lennon').filter(entry__pub_date__year=2013)

 

根據同一表(model)內字段查詢

Django 提供了 F 表達式(expressions) 來實現, 同一個表內不同的兩個字段的對比。F() 表現一次查詢實例的引用。
例: 查詢 entry 表中 n_comments 值大於 n_pingbacks 的數據:

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

 

F() 支持邏輯運算: 加,減,乘,除, 取模, 冪等。

1
2
Entry.objects.filter(n_comments__gt=F( 'n_pingbacks') * 2)
Entry.objects.filter(rating__gt=F( 'n_pingbacks') + F('n_comments'))

 

F() 也支持雙下划線的表關聯操作,

1
2
3
4
5
#查詢: entry 表中 author name 等於 blog name 的數據
Entry.objects.filter(authors__name=F( 'blog__name'))
#查詢, entry 表中發表3天后還有修改的數據
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F( 'pub_date') + timedelta(day=3))

 

F() 可以通過方法 .bitand() 和 .bitor() 實現按位與運算和或運算。

pk 縮寫

在 Django 中采用 pk 縮寫來表示 ‘primary key’, 表中的 id (primary key) 字段,可以使用 pk 來表示
例如: 下面的查詢語句等價

1
2
3
Blog.objects.get(id__exact= 13)
Blog.objects.get(id= 13)
Blog.objects.get(pk= 13)

 

pk 除 exact 查詢類型外,也可以使用其他的查詢類型

1
2
3
4
# 查找 id 為 1,2,3 的 blog 數據
Blog.objects.filter(pk__in=[ 1,2,3])
# 查找 id 大於 13 的數據
Blog.objects.filter(pk_gt= 13)

 

pk 可以使用在關聯表的查詢中

1
2
3
#查找 entry 表中 blog id 為 13 的數據
Entry.objects.filter(blog__id= 13)
Entry.objects.filter(blog__pk= 13)

 

QuerySet 緩存

由於 QuerySet 懶加載的原因,在一個新生成的 QuerySet 對象中是沒有緩存的。 當其第一次使用的時候才會去請求數據庫拉取數據。 此時 Django 會將查詢到的結果緩存起來,當第二次使用該 QuerySet 對象時,就不需要重新請求數據庫。
當然要獲得緩存帶來的便利,要保持正確的使用姿勢才行, 例如:

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

 

上面兩句代碼中, QuerySet 對象將會查詢兩次數據庫。因為,每使用一次 Entry.objects.all() 都會生成一個新的 QuerySet 對象。 其本質上, 第一 print 語句執行時使用的 是一個 QuerySet 對象, 第二個 print 語句執行的是另一個 QuerySet 對象, 雖然說前后兩個 QuerySet 對象是擁有相同的值, 但他們不是共享一個內存空間,是相互獨立的。
正確的使用姿勢如下:

1
2
3
4
5
queryset = Entry.objects.all()
# 請求數據庫
print([e.headline for e in queryset])
# 使用對象 queryset 對象的緩存
print([e.pub_date for e in queryset])

 

無法使用緩存的情況
QuerySet 並不是所有情況都會設置緩存的, 當查詢 QuerySet 對象中部分值時, 會先去檢查下緩存, 如果緩存中沒有, 將會請求數據拉取數據, 但是這部分數據將不會被緩存。因此,在第一次使用 QuerySet 對象時, 如果是通過 python slice 或 數組下標(index) 去限制所取請求結果的子集時, 這部分結果將不會加入緩存。
其原因大致是使用了 slice 或 index 時數據查詢時的 SQL 語句會額外添加 LIMIT, OFFSET 條件, 而 queryset 不做部分數據的緩存。
例如:

1
2
3
4
5
6
queryset = Entry.objects.all()
# 請求數據庫拉取第5條數據,
# 通過數組下標(index)取數據, 其結果不緩存
print(queryset[ 5])
# 因為沒有換成, 再一次拉取第5條數據時, 會重新請求數據庫
print(queryset[ 5])

 

假如在使用 slice 或 index 取數據之前,已經對 QuerySet 對象求值(不是部分求值), 即可使用 Queryset 對象的緩存

1
2
3
4
5
6
7
queryset = Entry.objects.all()
# 因為使用了整個 queryset 對象
[entry for entry in Entry.objects.all()]
# 從緩存中取數據
print(queryset[ 5])
# 從緩存中取數據
print(queryset[ 5])

 

使用 Q 對象進行復雜查找

在 QuerySet 對象的 filter() 方法中, 要用 AND 或者 OR 組合查詢條件, 來執行更復雜的查詢時, 可以使用 Q 對象。
Q 對象使用 &, | 和 ~ 來分別表示 SQL 中的 AND, OR 和 NOT
例如: 查詢 entry 表中 headline 以 ‘what’ 或 ‘who’ 開頭的數據

1
2
3
from django.db.models import Q
entry = Entry.objects.filter(Q(headline__startswith= 'what') | Q(headline__startswith='who'))
# SQL: SELECT * FROM entry WHERE headline LIKE 'what%' OR headline LIKE 'who%'

 

如果在 filer() 方法中多個 Q 對象作為參數時, 則不同參數之間將會以 AND 形式組合, 例如:

1
2
Entry.objects.filter(Q(headline__startswith= 'what'), Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))
# SQL SELECT * FROM entry WHERE headline LIKE 'what%' AND (pub_date='2017-3-29' OR pub_date='2017-3-31')

 

Q 對象和普通的查詢條件可以混合使用, 但是在 filter 中普通的查詢條件,要放在 Q 對象之后傳入, 否則將會是無效查詢

1
2
3
4
# 有效查詢
Entry.objects.filter(Q(pub_date=date( 2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)), headline__startswith='what')
# 無效查詢, 因為普通查詢條件在 Q 對象之前
Entry.objects.filter(headline__startswith= 'what', Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))

 

對象刪除

1、通過 model 對象刪除
一個 model 對象擁有 delete() 方法, 當要刪除某一條數據時, 可以直接執行 delete 方法

1
2
entry = Entry.objects.get(pk= 1)
entry.delete()

 

2、通過 QuerySet 對象刪除
QuerySet 對象也有 delete() 方法, 其將會刪除所有包含的成員。
例如: 刪除 entry 表中所有在 2016 年發布的數據

1
2
3
Entry.objects.filter(pub_date__year= 2016).delete()
#刪除表中所有數據
Entry.bojects.all().delete()

 

當 Django 刪除一個數據時, 其默認或使用 SQL 的 CASCADE 刪除模式, 即: 在父表上 delete 刪除一條數據是,與之通過 ForeignKey 關聯的子表中的數據也對應刪除。

復制數據

在 Django 中復制一條數據只需要將對應的 model 對象的 pk(id) 清除即可

1
2
3
4
5
blog = Blog(name= 'My blog', tagline='Blogging is easy')
blog.save() # blog.pk = 1
# 復制一條
blog.pk = None
blog.save() # blog.pk = 2

 

但是這種拷貝不會 many-to-many 的對應關系,因此在復制數據后,需要更新其多對多關系

1
2
3
4
5
entry = Entry.objects.all()[ 0]
auther_old = entry.authors.all() #提取 authors
entry.pk = None
entry.save()
entry.authors.set(auther_old) # 更新對應關系

 

對於 one-to-one 對應關系的數據, 在拷貝后要先更新一個新的對應關系后,開可以執行 save() 操作。

通過 QuerySet 執行更新操作

QuerySet 對象使用 update() 方法更新數據, 與 model 對象使用 save 不同。
update() 方法調用后會立即執行數據庫 Update 操作
1、更新不是外鍵的字段
例: entry 表中在 2017 年發布數據的 headline 字段都更新為 ‘Everything is the same’

1
Entry.objects.filter(pub_date__year= 2017).update(headline='Everything is the same')

 

2、更新外建字段
例: 更新 entry 表中 blog 字段關聯的外鍵

1
2
b = Blog.objects.get(pk= 1)
Entry.objects.all().update(blog=b)

 

如果想要更新某一行數據, 可以使用 filter 來進行過濾

1
2
b = Blog.objects.get(pk= 1)
Entry.objects.filter(blog=b).update(headline= 'Everything is the same')

 

注意: 執行 update() 方法時, 會執行生成相應的 SQL 語句。不會去執行 model 對象里面的 save() 方法, 也不會出發 pre_save 和 post_save 這兩個鈎子函數。添加了 auto_now 屬性的字段也無法執行。如果需要執行這些方法的操作, 可以遍歷 QuerySet 對象中的每一個 model 實例去執行 save() 方法。

1
2
for item in my_queryset:
item.save()

 

3、使用 F 表達式執行更新操作
在根據字段現有值去執行更新操作的場景中, F 表達比較實用
例如: 對 entry 中 n_pingbacks 字段執行遞增操作

1
2
from django.db.models import F
Entry.objects.all().update(n_pingbacks=F( 'n_pingbacks') + 1)

 

但是通過 F 表達式無法執行, 有 join 操作的更新, 即通過雙下划線 __ 關聯的操作
例如: 將 entry 表中所有數據的 headline 字段更新為其對應 blog 表中的 name, 無法通過 F 表達式實現

1
2
# 下面語句執行后會拋出 FieldError 異常
Entry.objects.update(headline=F( 'blog__name'))

 

對象的對應關系

當在 model 定義是添加了 ForeignKey, OneToOneFiedl, ManyToMangField 的字段時, model 會自動生成相關 API 來獲取相關數據。

One-To-Many 關系

1、正向獲取 父表 to 子表
如果一個 model 包含有 ForeignKey 字段, 這個 model 的對象可以方便的獲取與之關聯的另一個 model 的對象。
例如: 獲取 Entry 對象對應的 Blog 對象

1
2
3
4
5
6
e = Entry.objects.get(id= 2)
e.blog # 返回通過外鍵關聯的 blog 對象
#如果要更新 e 對象的 blog 屬性
b = Blog.objects.get(id= 3)
e.blog = b
e.save() # 執行根系操作,

 

one-to-many 關系在第一次使用后將會被緩存

1
2
3
e = Entry.objects.get(id= 2)
print(e.blog) # 查詢數據, 並將數據緩存
print(e.blog) # 不查詢數據庫, 之間中緩存中讀取

 

使用 QuerySet 的 select_related() 方法時, 會將相應的 one-to-many 關系的對象都預先取出來並緩存, 在真正使用時就不會訪問數據庫

1
2
3
e = Entry.objects.select_related().get(id= 2)
print(e.blog) # 不查詢數據庫
print(e.bong) # 不查詢數據庫

 

2、反向獲取 子表 to 父表
如果 model A 通過 ForeignKey字段 field 與 model B 想關聯。 B 對象可以通過 model Manager 去訪問與之對應的所有的 A 對象。 默認的, 這個 model Manage 名為 foo_set, 其中 foo 是擁有外鍵那個 model 名的小寫, 即 a_set()
例: 通過 Blog 對象查詢 Entry 對象:

1
2
3
4
5
6
# 查詢與 Blog 對象 b 關聯的所有 entry 對象
b = Blog.objects.get(pk= 2)
b.entry_set.all()
 
# 查詢與 Blog 對象 b 關聯的 entry 對象中 headline 包含 'Lennon' 的
b.entry_set.filter(headline__contains= 'Lennon')

 

如果在定義 ForeignKey 字段時 通過 related_name 可以更改這個默認的 foo_set() Manage 方法。
例如: 將最頂部的 Entry Model 中的 blog 字段修改成如下: blog = ForeignKey(Blog, related_name=’entries’), 上面的代碼中的 entry_set 就可以都改成 entries。

1
2
3
4
5
6
# 查詢與 Blog 對象 b 關聯的所有 entry 對象
b = Blog.objects.get(pk= 2)
b.entries.all()
 
# 查詢與 Blog 對象 b 關聯的 entry 對象中 headline 包含 'Lennon' 的
b.entries.filter(headline__contains= 'Lennon')

 

Many-To-Many 關系

對於 many-to-many 關系的 API 使用方法與上面的 one-to-many 關系的一致。區別在於, 在命名 ManyToMany 字段時, 字段名不要與其對應的 model 名的小寫一致。例如 Entry 中, authors 字段名稱不與其對應 Model(Author) 的小寫名 author 相同。 這點與 blog 字段不同。

1
2
3
4
5
6
7
8
e = Entry.objects.get(id= 3)
e.authors.all() # 返回 e 對象對應的所有 authors
e.authors.count() # authors 的數量
e.authors.filter(name__contains= 'John') # 返回名字中包含 John 的作者
 
a = Author.objects.get(id= 5)
# 返回所有與 a 對象對應的 Entry 對象
a.entry_set.all()

 

One-To-One 關系

One-to-one 關系同 many-to-one 非常相似, API 用法與 many-to-one 的基本也基本一致

1
2
3
4
5
6
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
 
ed = EntryDetail.objects.get(pk= 3)
en.entry # 返回與之對應的 Entry 對象

 

與 many-to-one 不同的是其反向查找, 如下:

1
2
3
4
5
6
7
8
e = Entry.objects.get(pk= 3)
# 取得與 Entry 對象對應的 EntryDetail 對象,
# 只需調用 EntryDetail 的小寫 entrydetail 即可
e.entrydetail
 
#更新
e.entrydetail = ed2
e.save()

 

通過關聯對象查詢

當查詢過濾條件傳入 filter 函數是,既可以使用整個對象,可以使用相應的對象值。

1
2
3
4
# Blog 對象 b 的 id = 5
Entry.objects.filter(blog=b) # 通過整個對象進行查詢
Entry.objects.filter(blog=b.id) # 通過 b 的 id 屬性查詢
Entry.objects.filter(blog= 5) # 直接用 id 值查詢 hard code

 

做個標記: 下期整理 QuerySet 比較常用的 API


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM