目錄
- 1、創(chuàng)建對(duì)象
- 2、保存ForeignKey和ManyToManyField字段
- 3、檢索對(duì)象
- 跨越多值的關(guān)系查詢
- 使用F表達(dá)式引用模型的字段:
- 4、緩存和查詢集
- 5、使用Q對(duì)象進(jìn)行復(fù)雜查詢
- 6、比較對(duì)象
- 7、刪除對(duì)象
- 8、復(fù)制模型實(shí)例
- 9、批量更新對(duì)象
- 10、關(guān)系的對(duì)象
- 11、使用原生SQL語句
一旦創(chuàng)建好了數(shù)據(jù)模型,Django就會(huì)自動(dòng)為我們提供一個(gè)數(shù)據(jù)庫抽象API,允許創(chuàng)建、檢索、更新和刪除對(duì)象操作
下面的示例都是通過下面參考模型來對(duì)模型字段進(jìn)行操作說明:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(to=Blog,on_delete=models.CASCADE)
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):
return self.headline
1、創(chuàng)建對(duì)象
Django使用更直觀的系統(tǒng):模型類表示數(shù)據(jù)庫表,該類的實(shí)例表示數(shù)據(jù)庫表中的特定記錄也就是數(shù)據(jù)值
要?jiǎng)?chuàng)建對(duì)象,請(qǐng)使用模型類的關(guān)鍵字參數(shù)對(duì)其進(jìn)行實(shí)例化,然后調(diào)用save()以將其保存到數(shù)據(jù)庫中,假設(shè)模型存在于mysite/app01/models.py文件中
from app01.models import Blog
b = Blog(name='beatles blog',tagline='all the latest beatles news.')
b.save()
它會(huì)在后臺(tái)執(zhí)行SQL語句INSERT,如果不調(diào)用save()方法,Django不會(huì)立刻將該操作反應(yīng)到數(shù)據(jù)庫中,save()方法沒有返回值,它可以接受一些額外的參數(shù)
#修改對(duì)象的值,在后臺(tái)會(huì)執(zhí)行一條SQL語句的UPDATE
b.name = 'python is django'
b.save()
Blog.objects.all()
QuerySet [Blog: python is django>]>
如果想要一行代碼完成上面的操作,請(qǐng)使用create()方法,它可以省略save的步驟:
Blog.objects.create(name='Blog title',tagline='this is one blog title name')
Blog.objects.filter(name='Blog title') #查詢數(shù)據(jù)
QuerySet [Blog: name:Blog title;tagline:this is one blog title name>]>
2、保存ForeignKey和ManyToManyField字段
保存一個(gè)ForeignKey字段和保存普通字段沒什么區(qū)別,只需要注意值的類型要正確,下面的例子,有一個(gè)Entry的實(shí)例和一個(gè)Blog的實(shí)例,然后把cheese_blog作為值賦給了entry的blog屬性,最后調(diào)用save方法進(jìn)行保存。
from app01.models import Blog,Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name='Blog title')
entry.blog = cheese_blog
entry.save()
ManyToManyField字段的保存稍微有點(diǎn)區(qū)別,需要調(diào)用add()方法,而不是直接給屬性賦值,但它不需要調(diào)用save方法,如下示例:
from app01.models import Author
joe = Author.objects.create(name='Joe')
entry.authors.add(joe)
在對(duì)對(duì)多關(guān)系中可以一次性添加多條記錄,只需在調(diào)用add()時(shí)指定多個(gè)值即可:
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)
#如指定了錯(cuò)誤的類型對(duì)象,將引發(fā)異常
3、檢索對(duì)象
想要從數(shù)據(jù)庫中檢索對(duì)象,需要基于模型類,通過管理器(Manager)構(gòu)造一個(gè)查詢結(jié)果集(QuerySet),每個(gè)QuerySet代表一些數(shù)據(jù)庫對(duì)象的集合,它可以包含零個(gè)、一個(gè)或多個(gè)過濾器(filters),F(xiàn)ilters縮小查詢結(jié)果的范圍,在SQL語法中,一個(gè)QuerySet相當(dāng)于一個(gè)SELECT語句,而filter相當(dāng)于WHERE或者LIMIT一類的子句
通過模型的Manager獲得QuerySet,每個(gè)模型至少具有一個(gè)Manager,默認(rèn)情況下,它被稱作為objects,可以通過模型類直接調(diào)用它,但不能通過模型類的實(shí)例調(diào)用它,以此實(shí)現(xiàn)‘表級(jí)別'操作和‘記錄級(jí)別'操作的強(qiáng)制分離,如下所示:
Blog.objects
django.db.models.manager.Manager object at 0x000001E3583EFDD8>
b = Blog(name='FOO',tagline='Bar')
b.objects
Traceback (most recent call last):
......
AttributeError: Manager isn't accessible via Blog instances
Managers只能通過模型類訪問,不能通過模型實(shí)例訪問,它強(qiáng)制表級(jí)操作和記錄操作之間的分離
#檢索所有對(duì)象,可使用all()方法,可獲取某張表的所有記錄
alll_entries = Entry.objects.all()
#過濾對(duì)象,有兩種方法用來過濾QuerySet的結(jié)果:
filter(**kwargs) :返回一個(gè)根據(jù)指定參數(shù)查詢出來的QuerySet
exclude(**kwargs) :返回給定查找參數(shù)不匹配的QuerySet
#例如:獲取2006年的博客條目,使用filter()
Entery.objects.filter(pub_date__year=2006)
#使用默認(rèn)的manager類,它等同于上
Entery.objects.all().filter(pub_date__year=2006)
鏈?zhǔn)竭^濾:filter和exclude的結(jié)果依然是QuerySet,因此它可以繼續(xù)被filter和exclude調(diào)用,這樣就形成了鏈?zhǔn)竭^濾:
Entry.objects.filter(headline__startswith='what').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime.date(2005.1.30))
#它將獲取數(shù)據(jù)庫中所有條目中帶'what'的首字母的QuerySet,然后排除,添加過濾器然后過濾是2005年1月30日的條目
被過濾的QuerySets都是唯一的,每次過濾,都會(huì)獲得一個(gè)全新的QuerySet,它和之前的QuerySet沒有任何關(guān)系,可以完全獨(dú)立的被保存,使用和重用
QuerySets是懶惰的,一個(gè)創(chuàng)建QuerySet的動(dòng)作不會(huì)立刻導(dǎo)致任何數(shù)據(jù)庫的行為,你可以不斷的進(jìn)行filter動(dòng)作,Django不會(huì)運(yùn)行任何實(shí)際的數(shù)據(jù)庫查詢操作,直到QuerySet被提交(evaluated),簡而言之就是只有碰到某些特定的操作,Django才會(huì)將所有的操作體現(xiàn)到數(shù)據(jù)庫內(nèi),否則它只是保存在內(nèi)存和Django層面中,這大大提高數(shù)據(jù)庫查詢效率,減少操作次數(shù)的優(yōu)化設(shè)計(jì)
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)
#看起來執(zhí)行的3次數(shù)據(jù)庫訪問,實(shí)際上只是在print語句時(shí)才執(zhí)行數(shù)據(jù)庫操作,通常QuerySet的檢索不會(huì)立刻執(zhí)行實(shí)際的數(shù)據(jù)庫操作,知道出現(xiàn)類似print的請(qǐng)求,也就是evaluated
檢索單個(gè)對(duì)象:filter方法始終返回的是QuerySet,哪怕只有一個(gè)對(duì)象符合顧慮條件,返回的也是包含一個(gè)對(duì)象的QuerySets,這是一個(gè)集合類型對(duì)象,可以理解為python列表,可迭代、可索引
如果要檢索的對(duì)象是一個(gè)時(shí),可以使用Manager的get()方法來直接返回這個(gè)對(duì)象
one_entry = Entry.objects.get(pk=1)
在get方法中你也可以使用任何filter方法中的查詢參數(shù),用法也時(shí)一樣的
注意:使用get()和filter()方法后通過切片方式[0]時(shí),有著不同的地方,看似兩者都是獲取單一對(duì)象,但,如果在查詢時(shí)沒有匹配到對(duì)象,那么get()方法將拋出DoesNotExist異常,這個(gè)異常時(shí)模型類的一個(gè)屬性,在上面的例子中,如果不存在主鍵為1的Entry對(duì)象,那么Django將拋出Entry.DoesNotExist異常
同樣在使用get()方法查詢時(shí),如果結(jié)果超過1個(gè),則會(huì)拋出MultipleObjectsReturned異常,這個(gè)異常也是模型類的要給屬性;所以get方法要慎用!
其他QuerySet方法:大多數(shù)情況下,需要從數(shù)據(jù)庫中查找對(duì)象時(shí),使用all()、get()、filter()和exclude()就行,針對(duì)QuerySet的方法還有很多高級(jí)用法可以參考官網(wǎng)
QuerySet使用限制:使用類似于python對(duì)列表進(jìn)行切片的方法可以對(duì)QuerySet進(jìn)行范圍取值,它相當(dāng)于SQL語句中的LIMIT和OFFSET子句
Entry.objects.all()[:5] #返回前5個(gè)對(duì)象
Entry.objects.all()[5:10] #返回第6個(gè)到第10個(gè)對(duì)象
注意:QuerySet不支持負(fù)索引,如:Entry.objects.all()[-1]是不允許的
通常情況下,切片操作會(huì)返回一個(gè)新的QuerySet,并且不會(huì)被立刻執(zhí)行,但是有一個(gè)例外,就是在指定step步長的時(shí)候,查詢操作會(huì)立刻在數(shù)據(jù)庫內(nèi)執(zhí)行,如下:
Entry.objects.all()[:10:2]
若要獲取單一的對(duì)象而不是一個(gè)列表(SELECT foo FROM bar LIMIT 1),可以簡單地使用索引而不是切片,例如,下面的語句返回?cái)?shù)據(jù)庫中根據(jù)標(biāo)題排序后的第一條:
Entry.objects.order_by('headline')[0]
#相當(dāng)于:
Entry.objects.order_by('headline')[0:1].get()
#注意:如果沒有匹配到對(duì)象,第一種方法會(huì)拋出IndexError異常,而第二種
#方法會(huì)拋出DoesNotExist異常,所以在使用get和切片時(shí),需要注意查詢結(jié)果的元素個(gè)數(shù)
字段查詢:字段查詢是指SQL WHERE子句內(nèi)容的方式,也就是filter()、exclude()和get()等方法的關(guān)鍵字參數(shù),其基本格式是:field__lookuptype=value(注意是雙下劃線)
#pub_date為字段名鏈接雙下劃線再指定查找類型的關(guān)鍵字參數(shù)
Entry.objects.filter(pub_date__lte='2006-01-01')
#相當(dāng)于SQL語句:
SELECT * FROM blog_entry WHERE pub_date = '2006-01-01';
其中的字段必須是模型種定義的字段之一,但是有一個(gè)例外,那就是ForeignKey字段,你可以為其添加一個(gè)“_id“后綴(單下劃線),這種情況下鍵值是外鍵模型的主鍵原始值,如:
#blog為外鍵模型
Entry.objects.filter(blog_id=4)
#如果你傳遞了一個(gè)非法的鍵值,查詢函數(shù)會(huì)拋出TypeError異常
Django的數(shù)據(jù)庫API支持20多種查詢類型,下面介紹一些常用的:
#exact為模型類型,如果不提供查詢類型,那查詢類型就是這個(gè)默認(rèn)的exact,精確匹配
Entry.objects.get(headline__exact="Cat bites dog")
#SQL語句:
SELECT ... WHERE headline='Cat bites dog';
#下面兩個(gè)相當(dāng):
Blog.objects.get(id__exact=4)
Blog.objects.get(id=4)
#iexact:不區(qū)分大小寫的完全匹配
Blog.objects.get(name__iexact='beatles blog')
SELECT ... WHERE name ILIKE 'beatles blog'
#contains:區(qū)分大小寫
Entry.objects.get(headline__contains='Lennon')
SELECT ... WHERE headline LIKE '%Lennon%';
#icontains:不區(qū)分大小寫
Entry.objects.get(headline__icontains='Lennon')
SELECT ... WHERE headline ILIKE '%Lennon%';
#in:包含在列表、元組或集合的,也可接受字符串(可迭代)
Entry.objects.filter(id__in=[1,3,4])
#gt:大于
Entry.objects.filter(id__gt=3)
#gte:大于或等于
Entry.objects.filter(id__gte=3)
#lt:小于
Entry.objects.filter(id__lt=3)
#lte:小于或等于
Entry.objects.filter(id__lte=3)
#startswith:區(qū)分大小寫的開頭
Entry.objects.filter(headline__startswith='Lennon')
#istartswith:不區(qū)分大小寫的開頭
Entry.objects.filter(headline__istartswith='Lennon')
#endswith:區(qū)分大小寫的結(jié)尾;iendswith:不區(qū)分大小寫的結(jié)尾
Entry.objects.filter(headline__endswith='Lennon')
Entry.objects.filter(headline__iendswith='Lennon')
#range:范圍在內(nèi)
import datetime
start_date = datetime.date(2005,1,1)
end_date = datetime.date(2005,12,31)
Entry.objects.filter(pub_date__range=(start_date,end_date))
#date:對(duì)于datetime字段,將值轉(zhuǎn)換為日期,采用日期值查找
Entry.objects.filter(pub_date__date=datetime.date(2005,1,1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005,1,1))
#year:對(duì)于日期和時(shí)間字段,確切的年份匹配,需要整數(shù)年
Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)
#iso_year:精確的ISO 8601周編年份匹配,需要整數(shù)年
Entry.objects.filter(pub_date__iso_year=2005)
Entry.objects.filter(pub_date__iso_year__gte=2005)
#month:日期和日期時(shí)間字段,確切的月份匹配
Entry.objects.filter(pub_date__month=1)
Entry.objects.filter(pub_date__month__gte=5)
#day:確切的天匹配
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)
#week:日期和時(shí)間字段,根據(jù)ISO-8601返回周數(shù)
Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32,pub_date__week__lte=38)
#week_day:匹配星期幾,從1(星期日)到7(星期六)的整數(shù)值
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)
#quarter:匹配四季,取1到4之間的整數(shù)值
Entry.objects.filter(pub_date__quarter=2)
#time:對(duì)于datetime字段,將值轉(zhuǎn)換為時(shí)間,取一個(gè)datetime.time值
Entry.objects.filter(pub_date__time=datetime.time(14,30))
#hour:對(duì)日期時(shí)間和時(shí)間字段,精確的小時(shí)匹配,取值0-23
Entry.objects.filter(timestamp__hour=11)
#minute:對(duì)于日期時(shí)間和時(shí)間字段,精確分鐘匹配
Entry.objects.filter(timestamp__minute=29)
#second:對(duì)于日期時(shí)間和時(shí)間字段,確切的妙匹配
Entry.objects.filter(timestamp__second=31)
#isnull:采用任一True或False,其對(duì)應(yīng)于SQL查詢IS NULLIS NOT NULL
Entry.objects.filter(pub_date__isnull=True)
SELECT ... WHERE pub_date IS NULL
#regex:區(qū)分大小寫的正則表達(dá)式匹配,語法為python re模塊語法
Entry.objects.filter(headline__regex=r'^(an?|The)+')
#iregex:不區(qū)分大小寫的正則表達(dá)式匹配
Entry.objects.filter(headline__iregex=r'^(an?|the)+')
跨越關(guān)系的查詢:Django提供了強(qiáng)大并且直觀的方式解決跨越關(guān)聯(lián)的查詢,它在后臺(tái)自動(dòng)執(zhí)行包含JOIN的SQL語句,要跨越某個(gè)關(guān)聯(lián),只需使用關(guān)聯(lián)的模型字段名稱,并使用雙下劃線分隔,直至你想要的字段(可以鏈?zhǔn)娇缭?,無限跨越)。
#先指定字段名然后雙下劃線分隔指定外鍵字段名
Entry.objects.filter(blog__name='Blog title')
如果要引用一個(gè)反向關(guān)聯(lián)只需要使用模型的小寫名即可:
#獲取所有的blog對(duì)象,但前提是所關(guān)聯(lián)的Entry模型的headline字段包含'django models'
Blog.objects.filter(entry__headline__icontains='django models')
如果多級(jí)關(guān)聯(lián)種進(jìn)行過濾而且其中某個(gè)模型沒有滿足過濾條件的值,Django將把它當(dāng)作一個(gè)空(null)但是合法的對(duì)象,不會(huì)拋出任何異?;蝈e(cuò)誤:
#查詢Blog的所有對(duì)象,但關(guān)聯(lián)entry模型種authors字段關(guān)聯(lián)名必須時(shí)py.qi
Blog.objects.filter(entry__authors__name='py.qi')
如果Entry中沒有關(guān)聯(lián)任何的author,那么它將當(dāng)作其沒有name,而不會(huì)因?yàn)闆]有author引發(fā)一個(gè)錯(cuò)誤,通常這是比較符合邏輯的處理方式,唯一可能讓你困惑的時(shí)當(dāng)使用isnull的時(shí)候:
Blog.objects.filter(entry__authors__name__isnull=True)
這將返回Blog對(duì)象,它關(guān)聯(lián)的entry對(duì)象的author字段的name字段為空,以及Entry對(duì)象的author字段為空,如果你不需要后者,可以這樣寫:
Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__isnull=True)
跨越多值的關(guān)系查詢
最基本的filter和exclude的關(guān)鍵字參數(shù)只有一個(gè),這種情況很好理解,但是當(dāng)關(guān)聯(lián)字段有多個(gè),且是跨越外鍵或多對(duì)多的情況下,那么就比較復(fù)雜了
Blog.objects.filter(entry__headline__contains='django models',entry__pub_date__year=2019)
這是一個(gè)跨外鍵、兩個(gè)過濾參數(shù)的查詢,此時(shí)我們理解兩個(gè)參數(shù)之間屬于‘a(chǎn)nd'的關(guān)系,也就是說,過濾出來的Blog對(duì)象對(duì)用的entry對(duì)象必須滿足上面兩個(gè)條件,但是,我們看下面的用法:
Blog.objects.filter(entry__headline__contains='django models').filter(entry__pub_date__year=2019)
把兩個(gè)參數(shù)拆分,放在兩個(gè)filer調(diào)用里面,安裝我們前面說過的鏈?zhǔn)竭^濾,這里的結(jié)果和上面的例子應(yīng)該一樣,可實(shí)際上,它不一樣,Djang在這種情況下,將兩個(gè)filter之間的關(guān)系設(shè)計(jì)為'or',多對(duì)對(duì)關(guān)系下的多值查詢和外鍵foreignkey的情況一樣。
但是,exclude的策略設(shè)計(jì)又和filter不一樣:
Blog.objects.exclude(entry__headline__contains='django models',entry__pub_date__year=2019)
它會(huì)排除headline中包含‘django models'的entry或在2019年發(fā)布的Entry,中間是一個(gè)'or'的關(guān)系
那要排除同時(shí)滿足上面兩個(gè)條件的對(duì)象,應(yīng)該這么做:
Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='django models',pub_date__year=2019,),)
使用F表達(dá)式引用模型的字段:
到目前為止的例子中,我們都是將模型字段于常量進(jìn)行比較,但是,如果你想將模型的一個(gè)字段與同一個(gè)模型的另外一個(gè)字段進(jìn)行比較該怎么辦呢?
使用Django提供的F表達(dá)式;例如:為了查找commnets數(shù)目多于pingbacks數(shù)目的Entry,可以構(gòu)造一個(gè)F()對(duì)象來應(yīng)用pingback數(shù)目,并在查詢中使用該F()對(duì)象:
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django支持對(duì)F()對(duì)象進(jìn)行加、減、乘、除、取模以及慕運(yùn)算等算術(shù)操作,兩個(gè)操作數(shù)可以是常數(shù)和其他F()對(duì)象,如:查找comments數(shù)目比pingbacks兩倍還要多的Entry,可以這樣寫:
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
查詢r(jià)ating比pinggback和comments數(shù)目綜合還要小的Entry:
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
還可以在F()中使用雙下劃線來進(jìn)行跨表查詢,如:查詢author的名字與Blog名字相同的Entry:
Entry.objects.filter(authors__name=F('blog__name'))
對(duì)于date和datetime字段,還可以加或減去一個(gè)timedelta對(duì)象,下面的例子將返回發(fā)布時(shí)間超過3天后別修改的所有Entry:
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()對(duì)象還支持:.bitand()、.bitor()、.bitrightshift()和.bitleftshift()4種位操作:
F('somefield').bitand(16)
主鍵的快捷查詢方式:pk
pk就是primary key的縮寫,通常情況下,一個(gè)模型的主鍵位'id',所以下面三個(gè)語句的效果一樣:
Blog.objects.get(id__exact=1)
Blog.objects.get(id=1)
Blog.objects.get(pk=1)
可以聯(lián)合其他類型的參數(shù):
Blog.objects.filter(pk__in=[1,3,4])
Blog.objects.filter(pk__gt=10)
可以跨表操作:
Entry.objects.filter(blog__id__exact=3)
Entry.objects.filter(blog__id=3)
Entry.objects.filter(blog__pk=3)
在LIKE語句中轉(zhuǎn)義百分符號(hào)和下劃線:在原生SQL語句中%符號(hào)有特殊的作用,Django幫你自動(dòng)轉(zhuǎn)義了百分符號(hào)和下劃線,你可以和普通字符一樣使用它們,如下:
Entry.objects.filter(headline__contains='%')
#SQL語句
SELECT ... WHERE headline LIKE '%\%%';
4、緩存和查詢集
每個(gè)QuerySet都包含一個(gè)緩存,用于減少對(duì)數(shù)據(jù)庫的實(shí)際操作,有助于你提高查詢效率
對(duì)于新創(chuàng)建的QuerySet它的緩存是空的,當(dāng)QuerySet第一次被提交后,數(shù)據(jù)庫執(zhí)行實(shí)際的查詢操作,Django會(huì)把查詢的結(jié)果保存在QuerySet的緩存內(nèi),隨后對(duì)于該QuerySet的提交將重用這個(gè)緩存的數(shù)據(jù)。
要想高效的利用查詢結(jié)果,降低數(shù)據(jù)庫負(fù)載,你必須善于利用緩存,看下面的例子,這會(huì)造成2次實(shí)際的數(shù)據(jù)庫操作,加倍數(shù)據(jù)庫的負(fù)載,同時(shí)由于時(shí)間差的問題,可能在兩次操作之間數(shù)據(jù)庫被刪除或修改或添加,導(dǎo)致臟數(shù)據(jù)的問題:
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])
為了避免上面的問題,好的使用方式如下,這只產(chǎn)生一次實(shí)際的查詢操作,并保持了數(shù)據(jù)的一致性:
queryset = Entry.objects.all()
print([p.headline for p in queryset]) #提交查詢
print([p.pub_date for p in queryset]) #重用查詢緩存
何時(shí)不會(huì)被緩存呢,有一些操作不會(huì)緩存QuerySet,例如切片和索引,這就導(dǎo)致這些操作沒有緩存可用,每次都會(huì)執(zhí)行實(shí)際的數(shù)據(jù)庫查詢操作,如下:
queryset = Entry.objects.all()
print(queryset[5]) #查詢數(shù)據(jù)庫
print(queryset[2]) #再次查詢數(shù)據(jù)庫
但是,如果已經(jīng)遍歷過整個(gè)QuerySet,那么就相當(dāng)于緩存過,后續(xù)的操作則會(huì)使用緩存,如:
queryset = Entry.objects.all()
[entry for entry in queryset] #查詢數(shù)據(jù)庫
print(queryset[3]) #使用緩存
print(queryset[1]) #使用緩存
下面的操作都將遍歷QuerySet并建立緩存:
[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)
注意:簡單的打印QuerySet并不會(huì)建立緩存,因?yàn)開_repr__()調(diào)用只返回全部查詢集的一個(gè)切片
5、使用Q對(duì)象進(jìn)行復(fù)雜查詢
普通的filter函數(shù)里的條件都是'and'邏輯,如果你想實(shí)現(xiàn)'or'邏輯怎么辦呢?用Q查詢
Q來自django.db.models.Q ,用于封裝關(guān)鍵字參數(shù)的集合,可用作為關(guān)鍵字參數(shù)用于filter、exclude和get等函數(shù);例如:
from django.db.models import Q
Q(question__startswith='what')
可用使用“”或者“|”或"~"來組合Q對(duì)象,分別表示與或非邏輯,它將返回一個(gè)新的Q對(duì)象
Q(question__startswith='who')|Q(question__startswith='what')
#這相當(dāng)于:
WHERE question LIKE 'who%' OR question LIKE 'what%'
更多的例子:
Q(question__startswith='who') | ~Q(pub_date__year=2005)
也可以這樣使用,默認(rèn)情況下,以逗號(hào)分隔都表示AND關(guān)系:
from app01.models import Entry
from django.db.models import Q
from datetime import date
Entry.objects.get(Q(headline__startswith='a'),Q(pub_date=date(2005,1,1)) | Q(pub_date=date(2019,1,1)))
#它相當(dāng)于SQL的
SELECT * FROM Entry WHERE headline LIKE 'a%' AND (pub_date = '2005,1,1' OR pub_date = '2019,1,1)
當(dāng)關(guān)鍵字參數(shù)和Q對(duì)象組合使用時(shí),Q對(duì)象必須放在前面,否則會(huì)報(bào)錯(cuò)
Entry.objects.filter(Q(pub_date=date(2005,4,2)) | Q(pub_date=date(2019,4,2)),headline__startswith='a')
#如果將headline__startswith='a'放在前面將會(huì)報(bào)錯(cuò)
6、比較對(duì)象
要比較兩個(gè)模型實(shí)例,只需要使用python提供的雙等號(hào)比較符就可以了,在后臺(tái),其實(shí)比較的是兩個(gè)實(shí)例的主鍵的值,下面兩種方法是同等的:
some_entry == other_entry
some_entry.id == other_entry.id
如果模型的主鍵不叫'ID'也沒關(guān)系,后臺(tái)總是會(huì)使用正確的主鍵名字進(jìn)行比較,如下:主鍵名為'name‘時(shí),下面的方法一樣
some_obj == other_obj
some_obj.name == other_obj.name
7、刪除對(duì)象
刪除對(duì)象使用的是對(duì)象的delete()方法,該方法將返回被刪除對(duì)象的總數(shù)量和一個(gè)字典,字典包含了每種被刪除對(duì)象的類型和該類型的數(shù)量,如下:
e.delete()
(1, {'weblog.Entry': 1})
也可以批量刪除,每個(gè)QuerySet都有一個(gè)delete()方法,它能刪除該QuerySet的所有成員,如:
Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
需要注意的是,有可能不是每一個(gè)對(duì)象的delete方法都被執(zhí)行,如果你改寫了delete方法,為了確保對(duì)象被刪除,你必須手動(dòng)迭代QuerySet進(jìn)行逐一刪除操作。
當(dāng)django刪除一個(gè)對(duì)象時(shí),它默認(rèn)使用SQL的ON DELETE CASCADE約束,也就是說,任何有外鍵指向要?jiǎng)h除對(duì)象的對(duì)象將一起被刪除,如:
b = Blog.objects.get(pk=1)
# 下面的動(dòng)作將刪除該條Blog和所有的它關(guān)聯(lián)的Entry對(duì)象
b.delete()
這種級(jí)聯(lián)的行為可以通過ForeignKey的on_delete參數(shù)自定義
注意:delete()是唯一沒有在管理器上暴露出來的方法,這是刻意設(shè)計(jì)的一個(gè)安全機(jī)制,用來防止你意外的請(qǐng)求類似Entry.objects.delete()的動(dòng)作,而不慎刪除的所有條目,如果你確定想刪除所有的對(duì)象,你必須明確的請(qǐng)求一個(gè)完全的查詢集,像下面這樣:
Entry.objects.all().delete()
8、復(fù)制模型實(shí)例
雖然沒有內(nèi)置的方法用于復(fù)制模型的實(shí)例,但還是很容易創(chuàng)建一個(gè)新的實(shí)例并將原實(shí)例的所有字段都拷貝過來,最簡單的方法是將原實(shí)例的pk設(shè)置為None,這會(huì)創(chuàng)建一個(gè)新的實(shí)例copy,如下:
blog = Blog(name='My blog',tagline='Blogging is easy')
blog.save() #blog.pk == 1
blog.pk = None
blog.save() #blog.pk == 2
但是在使用繼承的時(shí)候,情況會(huì)變得復(fù)雜,如果有下面一個(gè)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
基于繼承的工作機(jī)制,必須同時(shí)將pk和id設(shè)為None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
對(duì)于外鍵和多對(duì)多關(guān)系,更需要進(jìn)一步處理,列如,Entry有一個(gè)ManyToManyField到Author,復(fù)制條目后,你必須為新的條目設(shè)置多對(duì)多關(guān)系,像下面這樣:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)
對(duì)于OneToOneField,還要復(fù)制相關(guān)對(duì)象并將其分配給新對(duì)象的字段,以避免違反一對(duì)一唯一約束,如:假設(shè)entry已經(jīng)如上所述重復(fù):
detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()
9、批量更新對(duì)象
使用update()方法可以批量為QuerySet中的所有對(duì)象進(jìn)行更新操作
# 更新所有2007年發(fā)布的entry的headline
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
只可以對(duì)普通字段和ForignKey字段使用這個(gè)方法,若要更新一個(gè)普通字段,只需提供一個(gè)新的常數(shù)值,若要更新ForeignKey字段,需設(shè)置新值為你想指向的新模型實(shí)例:
b = Blog.objects.get(pk=1)
# 修改所有的Entry,讓他們都屬于b
Entry.objects.all().update(blog=b)
update方法會(huì)被立刻執(zhí)行,并返回操作匹配到的行的數(shù)目(有可能不等于要更新的行的數(shù)量,因?yàn)橛行┬锌赡芤呀?jīng)有這個(gè)新值了);唯一的約束是:只能訪問一張數(shù)據(jù)庫表,你可以根據(jù)關(guān)系字段進(jì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()方法會(huì)直接轉(zhuǎn)換成一個(gè)SQL語句,并立刻批量執(zhí)行,它不會(huì)運(yùn)行模型的save()方法,或者產(chǎn)生pre_save或post_save信號(hào)(調(diào)用save()方法產(chǎn)生)或者服從auto_now字段選項(xiàng);如果你想保存QuerySet中的每個(gè)條目并確保每個(gè)實(shí)例的save()方法被調(diào)用,你不需要使用任何特殊的函數(shù)來處理,只需要迭代它們并調(diào)用save()方法:
for item in my_queryset:
item.save()
update方法可以配合F表達(dá)式,這對(duì)于批量更新同一模型中某個(gè)字段特別有用,例如增加Blog中每個(gè)Entry的pingback個(gè)數(shù):
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然后與filter和exclude子句中的F()對(duì)象不同,在update中你不可以使用F()對(duì)象進(jìn)行跨表操作,你只可以引用正在更新的模型的字段,如果你嘗試使用F()對(duì)象引入另外一張表的字段,將拋出FieldError異常:
# THIS WILL RAISE A FieldError
Entry.objects.update(headline=F('blog__name'))
10、關(guān)系的對(duì)象
利用上面一開始的模型,一個(gè)Entry對(duì)象e可以通過blog屬性e.blog獲取關(guān)聯(lián)的Blog對(duì)象,反過來,Blog對(duì)象b可以通過entry_set屬性b.entry_set.all()訪問與它關(guān)聯(lián)的所有Entry對(duì)象
1)一對(duì)多外鍵
正向查詢:
直接通過圓點(diǎn)加屬性,訪問外鍵對(duì)象:
e = Entry.objects.get(id=2)
e.blog
要注意的是,對(duì)外鍵的修改,必須調(diào)用save方法進(jìn)行保存
e = Entry.objects.get(id=2)
other_blog = Blog.objects.get(name='django')
e.blog = other_blog
e.save()
如果一個(gè)外鍵字段設(shè)置有Null=True屬性,那么可以通過給該字段賦值為None的方法移除外鍵值:
e = Entry.objects.get(id=2)
e.blog = None
e.save()
在第一次對(duì)一個(gè)外鍵關(guān)系進(jìn)行正向訪問的時(shí)候,關(guān)系對(duì)象會(huì)被緩存,隨后對(duì)同樣外鍵關(guān)系對(duì)象的訪問會(huì)使用這個(gè)緩存,如:
e = Entry.objects.get(id=2)
print(e.blog) # 訪問數(shù)據(jù)庫,獲取實(shí)際數(shù)據(jù)
print(e.blog) # 不會(huì)訪問數(shù)據(jù)庫,直接使用緩存的版本
請(qǐng)注意QuerySet的select_related()方法會(huì)遞歸的預(yù)填充所有的一對(duì)多關(guān)系到緩存中:
e = Entry.objects.select_related().get(id=2)
print(e.blog) #不會(huì)訪問數(shù)據(jù)庫,直接使用緩存
print(e.blog) #不會(huì)訪問數(shù)據(jù)庫,直接使用緩存
反向查詢:
如果一個(gè)模型有ForeignKey,那么該ForeignKey所指向的外鍵模型的實(shí)例可以通過一個(gè)管理器進(jìn)行反向查詢,返回源冒險(xiǎn)島所有實(shí)例;默認(rèn)情況下,這個(gè)管理器的名字為FOO_set,其中FOO是源模型的小寫名稱,該管理器返回的查詢集可以用前面提到的方式進(jìn)行過濾和操作。
b = Blog.objects.get(id=7)
b.entry_set.all() #Returns all Entry objects related to Blog
b.entry_set.filter(headline__contains='Every') #b.entries is a Manager that returns QuerySets
b.entry_set.count()
使用自定義的反向管理器:默認(rèn)情況下,用于反向關(guān)聯(lián)的RelatedManager是該模型默認(rèn)管理器子類,如果你想為一個(gè)查詢指定一個(gè)不同的管理器,你可以使用下面的語法:
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # 默認(rèn)管理器
entries = EntryManager() # 自定義管理器
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
指定的自定義反向管理器也可以調(diào)用它的自定義方法:
b.entry_set(manager='entries').is_published()
處理關(guān)聯(lián)對(duì)象的其他方法:
除了在前面定義的QuerySet方法之外,F(xiàn)oreignKey管理器還有其他方法用于處理關(guān)聯(lián)的對(duì)象集合,下面是每個(gè)方法的概括
add(obj1, obj2, ...):添加指定的模型對(duì)象到關(guān)聯(lián)的對(duì)象集中。
create(**kwargs):創(chuàng)建一個(gè)新的對(duì)象,將它保存并放在關(guān)聯(lián)的對(duì)象集中。返回新創(chuàng)建的對(duì)象。
remove(obj1, obj2, ...):從關(guān)聯(lián)的對(duì)象集中刪除指定的模型對(duì)象。
clear():清空關(guān)聯(lián)的對(duì)象集。
set(objs):重置關(guān)聯(lián)的對(duì)象集。
若要一次性給關(guān)聯(lián)的對(duì)象集賦值,使用set()方法,并給它賦值一個(gè)可迭代的對(duì)象集合或者一個(gè)主鍵值的列表,如:
b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])
在上面的例子中,e1和e2可以是完整的Entry實(shí)例,也可以是整數(shù)的主鍵值
如果clear()方法可用,那么在將可迭代對(duì)象中的成員添加到集合中之前,將從entry_set中刪除所有已經(jīng)存在的對(duì)象
如果clear()方法不可用,那么將直接添加可迭代對(duì)象中的成員而不會(huì)刪除所有已存在的對(duì)象
以上的每個(gè)反向操作都將立刻在數(shù)據(jù)庫內(nèi)執(zhí)行,所有的增加、創(chuàng)建和刪除操作也將立刻自動(dòng)地保存到數(shù)據(jù)庫內(nèi)。
2)多對(duì)多
多對(duì)多關(guān)系的兩端都會(huì)自動(dòng)獲取訪問另一端的API,這些API的工作方式與前面提到的‘反向'一對(duì)多關(guān)系的用法一樣,唯一的區(qū)別在于屬性的名稱,定義ManyToMangField的模型使用該字段的屬性名稱,而‘反向'模型使用源模型的小寫名稱加上‘_set'和一對(duì)多關(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.
與外鍵字段中一樣,在多對(duì)多的字段中也可以指定related_name名;注意:在一個(gè)模型中,如果存在多個(gè)外鍵或多對(duì)多的關(guān)系指向同一個(gè)外部模型,必須給他們分別加上不同的related_name,用于反向查詢
3)一對(duì)一
一對(duì)一非常類似多對(duì)一關(guān)系,可以簡單的通過模型的屬性訪問關(guān)聯(lián)的模型
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
不同之處在于反向查詢的時(shí)候,一對(duì)一關(guān)系中的關(guān)聯(lián)模型同樣具有一個(gè)管理器對(duì)象,但是該管理器表示一個(gè)單一的對(duì)象而不是對(duì)象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # 返回關(guān)聯(lián)的EntryDetail對(duì)象
如果沒有對(duì)象賦值給這個(gè)關(guān)系,Django將拋出一個(gè)DoesNotExist異常,可以給反向關(guān)聯(lián)進(jìn)行賦值,方法和正向的關(guān)聯(lián)一樣:
4)反向關(guān)聯(lián)是如何實(shí)現(xiàn)的:
一些ORM框架需要你在關(guān)系的兩端都進(jìn)行定義,Django的開發(fā)者認(rèn)為這違反了DRY(Don't Repeat Yourself)原則,所以在django中你只需在一端進(jìn)行定義;那么這是怎么實(shí)現(xiàn)的呢?因?yàn)樵陉P(guān)聯(lián)的模型類沒有被加載之前,一個(gè)模型類根本不知道有哪些類與它關(guān)聯(lián)
答案在app registry;在django啟動(dòng)的時(shí)候,它會(huì)導(dǎo)入所有INSTALLED_APPS中的應(yīng)用和每個(gè)應(yīng)用中的模型模塊,沒創(chuàng)建一個(gè)新的模型時(shí),django會(huì)自動(dòng)添加反向的關(guān)系到所有關(guān)聯(lián)的模型,如果關(guān)聯(lián)的模型還沒有導(dǎo)入,django將保存關(guān)聯(lián)的記錄并在關(guān)聯(lián)的模型導(dǎo)入時(shí)添加這些關(guān)系
由于這個(gè)原因,將模型所在的應(yīng)用都定義在INSTALLED_APPS的應(yīng)用列表就顯得特定重要,否則,反向關(guān)聯(lián)將不能正確工作。
5)通過關(guān)聯(lián)對(duì)象進(jìn)行查詢
涉及關(guān)聯(lián)對(duì)象的查詢與正常值的字段查詢遵循同樣的規(guī)則,當(dāng)你指定查詢需要匹配的值時(shí),你可以使用一個(gè)對(duì)象實(shí)例或者對(duì)象的主鍵值。
例如,如果你有一個(gè)id=5的Blog對(duì)象b,下面的三個(gè)查詢將是完全一樣的:
Entry.objects.filter(blog=b) # 使用對(duì)象實(shí)例
Entry.objects.filter(blog=b.id) # 使用實(shí)例的id
Entry.objects.filter(blog=5) # 直接使用id
11、使用原生SQL語句
如果你發(fā)現(xiàn)需要編寫的django查詢語句太復(fù)雜,你可以回歸到手工編寫SQL語句,django對(duì)于編寫原生的SQL查詢有許多選項(xiàng)。
最后需要注意的是Django的數(shù)據(jù)庫層只是一個(gè)數(shù)據(jù)庫接口,你可以利用其它的工具,編程語言或數(shù)據(jù)庫框架來訪問數(shù)據(jù)庫,django沒有強(qiáng)制指定你非要使用它的某個(gè)功能或模塊。
到此這篇關(guān)于django模型查詢操作的文章就介紹到這了,更多相關(guān)django模型查詢操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- python利用xpath爬取網(wǎng)上數(shù)據(jù)并存儲(chǔ)到django模型中
- Django數(shù)據(jù)模型中on_delete使用詳解
- Django Admin后臺(tái)模型列表頁面如何添加自定義操作按鈕
- Django模型驗(yàn)證器介紹與源碼分析
- Django3中的自定義用戶模型實(shí)例詳解
- Django CBV模型源碼運(yùn)行流程詳解
- Python Django模型詳解