主頁 > 知識(shí)庫 > django模型查詢操作的實(shí)現(xiàn)

django模型查詢操作的實(shí)現(xiàn)

熱門標(biāo)簽:百應(yīng)電話機(jī)器人總部 地圖標(biāo)注與注銷 無錫智能外呼系統(tǒng)好用嗎 成都呼叫中心外呼系統(tǒng)哪家強(qiáng) 西青語音電銷機(jī)器人哪家好 南昌地圖標(biāo)注 電梯新時(shí)達(dá)系統(tǒng)外呼顯示e 宿州電話機(jī)器人哪家好 旅游廁所地圖標(biāo)注怎么弄

一旦創(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)一樣:

e.entrydetail = ed

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模型詳解

標(biāo)簽:濰坊 辛集 渭南 許昌 雅安 西安 七臺(tái)河 贛州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《django模型查詢操作的實(shí)現(xiàn)》,本文關(guān)鍵詞  django,模型,查詢,操作,的,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《django模型查詢操作的實(shí)現(xiàn)》相關(guān)的同類信息!
  • 本頁收集關(guān)于django模型查詢操作的實(shí)現(xiàn)的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章