django

查詢(xún)操作

查找是數(shù)據(jù)庫(kù)操作中一個(gè)非常重要的技術(shù)。查詢(xún)一般就是使用filter、exclude以及get三個(gè)方法來(lái)實(shí)現(xiàn)。我們可以在調(diào)用這些方法的時(shí)候傳遞不同的參數(shù)來(lái)實(shí)現(xiàn)查詢(xún)需求。在ORM層面,這些查詢(xún)條件都是使用field+__+condition的方式來(lái)使用的。以下將那些常用的查詢(xún)條件來(lái)一一解釋。

查詢(xún)條件

exact:

使用精確的=進(jìn)行查找。如果提供的是一個(gè)None,那么在SQL層面就是被解釋為NULL。示例代碼如下:

article=Article.objects.get(id__exact=14)

article=Article.objects.get(id__exact=None)

以上的兩個(gè)查找在翻譯為SQL語(yǔ)句為如下:

select...fromarticlewhereid=14;

select...fromarticlewhereidISNULL;

iexact:

使用like進(jìn)行查找。示例代碼如下:

article = Article.objects.filter(title__iexact='hello world')

那么以上的查詢(xún)就等價(jià)于以下的SQL語(yǔ)句:

select...fromarticlewheretitlelike'hello world';

注意上面這個(gè)sql語(yǔ)句,因?yàn)樵贛ySQL中,沒(méi)有一個(gè)叫做ilike的。所以exact和iexact的區(qū)別實(shí)際上就是LIKE和=的區(qū)別,在大部分collation=utf8_general_ci情況下都是一樣的(collation是用來(lái)對(duì)字符串比較的)。

contains:

大小寫(xiě)敏感,判斷某個(gè)字段是否包含了某個(gè)數(shù)據(jù)。示例代碼如下:

articles=Article.objects.filter(title__contains='hello')

在翻譯成SQL語(yǔ)句為如下:

select...wheretitlelikebinary'%hello%';

要注意的是,在使用contains的時(shí)候,翻譯成的sql語(yǔ)句左右兩邊是有百分號(hào)的,意味著使用的是模糊查詢(xún)。而exact翻譯成sql語(yǔ)句左右兩邊是沒(méi)有百分號(hào)的,意味著使用的是精確的查詢(xún)。

icontains:

大小寫(xiě)不敏感的匹配查詢(xún)。示例代碼如下:

articles=Article.objects.filter(title__icontains='hello')

在翻譯成SQL語(yǔ)句為如下:

select...wheretitlelike'%hello%';

in:

提取那些給定的field的值是否在給定的容器中。容器可以為list、tuple或者任何一個(gè)可以迭代的對(duì)象,包括QuerySet對(duì)象。示例代碼如下:

articles=Article.objects.filter(id__in=[1,2,3])

以上代碼在翻譯成SQL語(yǔ)句為如下:

select...whereidin(1,3,4)

當(dāng)然也可以傳遞一個(gè)QuerySet對(duì)象進(jìn)去。示例代碼如下:

inner_qs=Article.objects.filter(title__contains='hello')

categories=Category.objects.filter(article__in=inner_qs)

以上代碼的意思是獲取那些文章標(biāo)題包含hello的所有分類(lèi)。將翻譯成以下SQL語(yǔ)句,示例代碼如下:

select...fromcategorywherearticle.idin(selectidfromarticlewheretitlelike'%hello%');

gt:

某個(gè)field的值要大于給定的值。示例代碼如下:

articles=Article.objects.filter(id__gt=4)

以上代碼的意思是將所有id大于4的文章全部都找出來(lái)。將翻譯成以下SQL語(yǔ)句:

select...whereid >4;

gte:

類(lèi)似于gt,是大于等于。

lt:

類(lèi)似于gt是小于。

lte:

類(lèi)似于lt,是小于等于。

startswith:

判斷某個(gè)字段的值是否是以某個(gè)值開(kāi)始的。大小寫(xiě)敏感。示例代碼如下:

articles=Article.objects.filter(title__startswith='hello')

以上代碼的意思是提取所有標(biāo)題以hello字符串開(kāi)頭的文章。將翻譯成以下SQL語(yǔ)句:

select...wheretitlelike'hello%'

istartswith:

類(lèi)似于startswith,但是大小寫(xiě)是不敏感的。

endswith:

判斷某個(gè)字段的值是否以某個(gè)值結(jié)束。大小寫(xiě)敏感。示例代碼如下:

articles=Article.objects.filter(title__endswith='world')

以上代碼的意思是提取所有標(biāo)題以world結(jié)尾的文章。將翻譯成以下SQL語(yǔ)句:

select...wheretitlelike'%world';

iendswith:

類(lèi)似于endswith,只不過(guò)大小寫(xiě)不敏感。

range:

判斷某個(gè)field的值是否在給定的區(qū)間中。示例代碼如下:

fromdjango.utils.timezoneimportmake_aware

fromdatetimeimportdatetime

start_date=make_aware(datetime(year=2018,month=1,day=1))

end_date=make_aware(datetime(year=2018,month=3,day=29,hour=16))

articles=Article.objects.filter(pub_date__range=(start_date,end_date))

以上代碼的意思是提取所有發(fā)布時(shí)間在2018/1/1到2018/12/12之間的文章。將翻譯成以下的SQL語(yǔ)句:

select...fromarticlewherepub_timebetween'2018-01-01'and'2018-12-12'。

需要注意的是,以上提取數(shù)據(jù),不會(huì)包含最后一個(gè)值。也就是不會(huì)包含2018/12/12的文章。而且另外一個(gè)重點(diǎn),因?yàn)槲覀冊(cè)趕ettings.py中指定了USE_TZ=True,并且設(shè)置了TIME_ZONE='Asia/Shanghai',因此我們?cè)谔崛?shù)據(jù)的時(shí)候要使用django.utils.timezone.make_aware先將datetime.datetime從navie時(shí)間轉(zhuǎn)換為aware時(shí)間。make_aware會(huì)將指定的時(shí)間轉(zhuǎn)換為T(mén)IME_ZONE中指定的時(shí)區(qū)的時(shí)間。

date:

針對(duì)某些date或者datetime類(lèi)型的字段??梢灾付╠ate的范圍。并且這個(gè)時(shí)間過(guò)濾,還可以使用鏈?zhǔn)秸{(diào)用。示例代碼如下:

articles=Article.objects.filter(pub_date__date=date(2018,3,29))

以上代碼的意思是查找時(shí)間為2018/3/29這一天發(fā)表的所有文章。將翻譯成以下的sql語(yǔ)句:

select...WHEREDATE(CONVERT_TZ(`front_article`.`pub_date`,'UTC','Asia/Shanghai')) =2018-03-29

注意,因?yàn)槟J(rèn)情況下MySQL的表中是沒(méi)有存儲(chǔ)時(shí)區(qū)相關(guān)的信息的。因此我們需要下載一些時(shí)區(qū)表的文件,然后添加到Mysql的配置路徑中。如果你用的是windows操作系統(tǒng)。那么在http://dev.mysql.com/downloads/timezones.html下載timezone_2018d_posix.zip - POSIX standard。然后將下載下來(lái)的所有文件拷貝到C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql中,如果提示文件名重復(fù),那么選擇覆蓋即可。如果用的是linux或者mac系統(tǒng),那么在命令行中執(zhí)行以下命令:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p,然后輸入密碼,從系統(tǒng)中加載時(shí)區(qū)文件更新到mysql中。

year:

根據(jù)年份進(jìn)行查找。示例代碼如下:

articles=Article.objects.filter(pub_date__year=2018)

articles=Article.objects.filter(pub_date__year__gte=2017)

以上的代碼在翻譯成SQL語(yǔ)句為如下:

select...wherepub_datebetween'2018-01-01'and'2018-12-31';

select...wherepub_date >='2017-01-01';

month:

同year,根據(jù)月份進(jìn)行查找。

day:

同year,根據(jù)日期進(jìn)行查找。

week_day:

Django 1.11新增的查找方式。同year,根據(jù)星期幾進(jìn)行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。

time:

根據(jù)時(shí)間進(jìn)行查找。示例代碼如下:

articles=Article.objects.filter(pub_date__time=datetime.time(12,12,12));

以上的代碼是獲取每一天中12點(diǎn)12分12秒發(fā)表的所有文章。更多的關(guān)于時(shí)間的過(guò)濾,請(qǐng)參考Django官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range。

isnull:

根據(jù)值是否為空進(jìn)行查找。示例代碼如下:

articles=Article.objects.filter(pub_date__isnull=False)

以上的代碼的意思是獲取所有發(fā)布日期不為空的文章。將來(lái)翻譯成SQL語(yǔ)句如下:

select...wherepub_dateisnotnull;

regex和iregex:

大小寫(xiě)敏感和大小寫(xiě)不敏感的正則表達(dá)式。示例代碼如下:

articles=Article.objects.filter(title__regex=r'^hello')

以上代碼的意思是提取所有標(biāo)題以hello字符串開(kāi)頭的文章。將翻譯成以下的SQL語(yǔ)句:

select...wheretitle regexp binary'^hello';

iregex是大小寫(xiě)不敏感的。

根據(jù)關(guān)聯(lián)的表進(jìn)行查詢(xún):

假如現(xiàn)在有兩個(gè)ORM模型,一個(gè)是Article,一個(gè)是Category。代碼如下:

classCategory(models.Model):

"""文章分類(lèi)表"""

name=models.CharField(max_length=100)

?

classArticle(models.Model):

"""文章表"""

title=models.CharField(max_length=100,null=True)

category=models.ForeignKey("Category",on_delete=models.CASCADE)

比如想要獲取文章標(biāo)題中包含"hello"的所有的分類(lèi)。那么可以通過(guò)以下代碼來(lái)實(shí)現(xiàn):

categories=Category.object.filter(article__title__contains("hello"))

聚合函數(shù):

如果你用原生SQL,則可以使用聚合函數(shù)來(lái)提取數(shù)據(jù)。比如提取某個(gè)商品銷(xiāo)售的數(shù)量,那么可以使用Count,如果想要知道商品銷(xiāo)售的平均價(jià)格,那么可以使用Avg。聚合函數(shù)是通過(guò)aggregate方法來(lái)實(shí)現(xiàn)的。在講解這些聚合函數(shù)的用法的時(shí)候,都是基于以下的模型對(duì)象來(lái)實(shí)現(xiàn)的。

fromdjango.dbimportmodels

?

classAuthor(models.Model):

"""作者模型"""

name=models.CharField(max_length=100)

age=models.IntegerField()

email=models.EmailField()

?

classMeta:

db_table='author'

?

?

classPublisher(models.Model):

"""出版社模型"""

name=models.CharField(max_length=300)

?

classMeta:

db_table='publisher'

?

?

classBook(models.Model):

"""圖書(shū)模型"""

name=models.CharField(max_length=300)

pages=models.IntegerField()

price=models.FloatField()

rating=models.FloatField()

author=models.ForeignKey(Author,on_delete=models.CASCADE)

publisher=models.ForeignKey(Publisher,on_delete=models.CASCADE)

?

classMeta:

db_table='book'

?

?

classBookOrder(models.Model):

"""圖書(shū)訂單模型"""

book=models.ForeignKey("Book",on_delete=models.CASCADE)

price=models.FloatField()

?

classMeta:

db_table='book_order'

Avg:求平均值。比如想要獲取所有圖書(shū)的價(jià)格平均值。那么可以使用以下代碼實(shí)現(xiàn)。

fromdjango.db.modelsimportAvg

result=Book.objects.aggregate(Avg('price'))

print(result)

以上的打印結(jié)果是:

{"price__avg":23.0}

其中price__avg的結(jié)構(gòu)是根據(jù)field__avg規(guī)則構(gòu)成的。如果想要修改默認(rèn)的名字,那么可以將Avg賦值給一個(gè)關(guān)鍵字參數(shù)。示例代碼如下:

fromdjango.db.modelsimportAvg

result=Book.objects.aggregate(my_avg=Avg('price'))

print(result)

那么以上的結(jié)果打印為:

{"my_avg":23}

Count:獲取指定的對(duì)象的個(gè)數(shù)。示例代碼如下:

fromdjango.db.modelsimportCount

result=Book.objects.aggregate(book_num=Count('id'))

以上的result將返回Book表中總共有多少本圖書(shū)。Count類(lèi)中,還有另外一個(gè)參數(shù)叫做distinct,默認(rèn)是等于False,如果是等于True,那么將去掉那些重復(fù)的值。比如要獲取作者表中所有的不重復(fù)的郵箱總共有多少個(gè),那么可以通過(guò)以下代碼來(lái)實(shí)現(xiàn):

fromdjang.db.modelsimportCount

result=Author.objects.aggregate(count=Count('email',distinct=True))

Max和Min:獲取指定對(duì)象的最大值和最小值。比如想要獲取Author表中,最大的年齡和最小的年齡分別是多少。那么可以通過(guò)以下代碼來(lái)實(shí)現(xiàn):

fromdjango.db.modelsimportMax,Min

result=Author.objects.aggregate(Max('age'),Min('age'))

如果最大的年齡是88,最小的年齡是18。那么以上的result將為:

{"age__max":88,"age__min":18}

Sum:求指定對(duì)象的總和。比如要求圖書(shū)的銷(xiāo)售總額。那么可以使用以下代碼實(shí)現(xiàn):

fromdjang.db.modelsimportSum

result=Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")

以上的代碼annotate的意思是給Book表在查詢(xún)的時(shí)候添加一個(gè)字段叫做total,這個(gè)字段的數(shù)據(jù)來(lái)源是從BookStore模型的price的總和而來(lái)。values方法是只提取name和total兩個(gè)字段的值。

更多的聚合函數(shù)請(qǐng)參考官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions

aggregate和annotate的區(qū)別:

aggregate:返回使用聚合函數(shù)后的字段和值。? (字典)

annotate:在原來(lái)模型字段的基礎(chǔ)之上添加一個(gè)使用了聚合函數(shù)的字段,并且在使用聚合函數(shù)的時(shí)候,會(huì)使用當(dāng)前這個(gè)模型的主鍵進(jìn)行分組(group by)。比如以上Sum的例子,如果使用的是annotate,那么將在每條圖書(shū)的數(shù)據(jù)上都添加一個(gè)字段叫做total,計(jì)算這本書(shū)的銷(xiāo)售總額。而如果使用的是aggregate,那么將求所有圖書(shū)的銷(xiāo)售總額。

F表達(dá)式和Q表達(dá)式:

F表達(dá)式:

F表達(dá)式是用來(lái)優(yōu)化ORM操作數(shù)據(jù)庫(kù)的。比如我們要將公司所有員工的薪水都增加1000元,如果按照正常的流程,應(yīng)該是先從數(shù)據(jù)庫(kù)中提取所有的員工工資到Python內(nèi)存中,然后使用Python代碼在員工工資的基礎(chǔ)之上增加1000元,最后再保存到數(shù)據(jù)庫(kù)中。這里面涉及的流程就是,首先從數(shù)據(jù)庫(kù)中提取數(shù)據(jù)到Python內(nèi)存中,然后在Python內(nèi)存中做完運(yùn)算,之后再保存到數(shù)據(jù)庫(kù)中。示例代碼如下:

employees=Employee.objects.all()

foremployeeinemployees:

employee.salary+=1000

employee.save()

而我們的F表達(dá)式就可以?xún)?yōu)化這個(gè)流程,他可以不需要先把數(shù)據(jù)從數(shù)據(jù)庫(kù)中提取出來(lái),計(jì)算完成后再保存回去,他可以直接執(zhí)行SQL語(yǔ)句,就將員工的工資增加1000元。示例代碼如下:

fromdjang.db.modelsimportF

Employee.object.update(salary=F("salary")+1000)

F表達(dá)式并不會(huì)馬上從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),而是在生成SQL語(yǔ)句的時(shí)候,動(dòng)態(tài)的獲取傳給F表達(dá)式的值。

比如如果想要獲取作者中,name和email相同的作者數(shù)據(jù)。如果不使用F表達(dá)式,那么需要使用以下代碼來(lái)完成:

authors=Author.objects.all()

forauthorinauthors:

ifauthor.name==author.email:

print(author)

如果使用F表達(dá)式,那么一行代碼就可以搞定。示例代碼如下:

fromdjango.db.modelsimportF

authors=Author.objects.filter(name=F("email"))

Q表達(dá)式:

如果想要實(shí)現(xiàn)所有價(jià)格高于100元,并且評(píng)分達(dá)到9.0以上評(píng)分的圖書(shū)。那么可以通過(guò)以下代碼來(lái)實(shí)現(xiàn):

books=Book.objects.filter(price__gte=100,rating__gte=9)

以上這個(gè)案例是一個(gè)并集查詢(xún),可以簡(jiǎn)單的通過(guò)傳遞多個(gè)條件進(jìn)去來(lái)實(shí)現(xiàn)。但是如果想要實(shí)現(xiàn)一些復(fù)雜的查詢(xún)語(yǔ)句,比如要查詢(xún)所有價(jià)格低于10元,或者是評(píng)分低于9分的圖書(shū)。那就沒(méi)有辦法通過(guò)傳遞多個(gè)條件進(jìn)去實(shí)現(xiàn)了。這時(shí)候就需要使用Q表達(dá)式來(lái)實(shí)現(xiàn)了。示例代碼如下:

fromdjango.db.modelsimportQ

books=Book.objects.filter(Q(price__lte=10)|Q(rating__lte=9))

以上是進(jìn)行或運(yùn)算,當(dāng)然還可以進(jìn)行其他的運(yùn)算,比如有&和~(非)等。一些用Q表達(dá)式的例子如下:

fromdjango.db.modelsimportQ

# 獲取id等于3的圖書(shū)

books=Book.objects.filter(Q(id=3))

# 獲取id等于3,或者名字中包含文字"記"的圖書(shū)

books=Book.objects.filter(Q(id=3)|Q(name__contains("記")))

# 獲取價(jià)格大于100,并且書(shū)名中包含"記"的圖書(shū)

books=Book.objects.filter(Q(price__gte=100)&Q(name__contains("記")))

# 獲取書(shū)名包含“記”,但是id不等于3的圖書(shū)

books=Book.objects.filter(Q(name__contains='記')&~Q(id=3))

QuerySet API:

我們通常做查詢(xún)操作的時(shí)候,都是通過(guò)模型名字.objects的方式進(jìn)行操作。其實(shí)模型名字.objects是一個(gè)django.db.models.manager.Manager對(duì)象,而Manager這個(gè)類(lèi)是一個(gè)“空殼”的類(lèi),他本身是沒(méi)有任何的屬性和方法的。他的方法全部都是通過(guò)Python動(dòng)態(tài)添加的方式,從QuerySet類(lèi)中拷貝過(guò)來(lái)的。所以我們?nèi)绻胍獙W(xué)習(xí)ORM模型的查找操作,必須首先要學(xué)會(huì)QuerySet上的一些API的使用。

返回新的QuerySet的方法:

在使用QuerySet進(jìn)行查找操作的時(shí)候,可以提供多種操作。比如過(guò)濾完后還要根據(jù)某個(gè)字段進(jìn)行排序,那么這一系列的操作我們可以通過(guò)一個(gè)非常流暢的鏈?zhǔn)秸{(diào)用的方式進(jìn)行。比如要從文章表中獲取標(biāo)題為123,并且提取后要將結(jié)果根據(jù)發(fā)布的時(shí)間進(jìn)行排序,那么可以使用以下方式來(lái)完成:

articles=Article.objects.filter(title='123').order_by('create_time')

可以看到order_by方法是直接在filter執(zhí)行后調(diào)用的。這說(shuō)明filter返回的對(duì)象是一個(gè)擁有order_by方法的對(duì)象。而這個(gè)對(duì)象正是一個(gè)新的QuerySet對(duì)象。因此可以使用order_by方法。

那么以下將介紹在那些會(huì)返回新的QuerySet對(duì)象的方法。

filter:將滿(mǎn)足條件的數(shù)據(jù)提取出來(lái),返回一個(gè)新的QuerySet。具體的filter可以提供什么條件查詢(xún)。請(qǐng)見(jiàn)查詢(xún)操作章節(jié)。

exclude:排除滿(mǎn)足條件的數(shù)據(jù),返回一個(gè)新的QuerySet。示例代碼如下:

Article.objects.exclude(title__contains='hello')

以上代碼的意思是提取那些標(biāo)題不包含hello的圖書(shū)。

annotate:給QuerySet中的每個(gè)對(duì)象都添加一個(gè)使用查詢(xún)表達(dá)式(聚合函數(shù)、F表達(dá)式、Q表達(dá)式、Func表達(dá)式等)的新字段。示例代碼如下:

articles=Article.objects.annotate(author_name=F("author__name"))

以上代碼將在每個(gè)對(duì)象中都添加一個(gè)author__name的字段,用來(lái)顯示這個(gè)文章的作者的年齡。

order_by:指定將查詢(xún)的結(jié)果根據(jù)某個(gè)字段進(jìn)行排序。如果要倒敘排序,那么可以在這個(gè)字段的前面加一個(gè)負(fù)號(hào)。示例代碼如下:

# 根據(jù)創(chuàng)建的時(shí)間正序排序

articles=Article.objects.order_by("create_time")

# 根據(jù)創(chuàng)建的時(shí)間倒序排序

articles=Article.objects.order_by("-create_time")

# 根據(jù)作者的名字進(jìn)行排序

articles=Article.objects.order_by("author__name")

# 首先根據(jù)創(chuàng)建的時(shí)間進(jìn)行排序,如果時(shí)間相同,則根據(jù)作者的名字進(jìn)行排序

articles=Article.objects.order_by("create_time",'author__name')

一定要注意的一點(diǎn)是,多個(gè)order_by,會(huì)把前面排序的規(guī)則給打亂,而使用后面的排序方式。比如以下代碼:

articles=Article.objects.order_by("create_time").order_by("author__name")

他會(huì)根據(jù)作者的名字進(jìn)行排序,而不是使用文章的創(chuàng)建時(shí)間。

values:用來(lái)指定在提取數(shù)據(jù)出來(lái),需要提取哪些字段。默認(rèn)情況下會(huì)把表中所有的字段全部都提取出來(lái),可以使用values來(lái)進(jìn)行指定,并且使用了values方法后,提取出的QuerySet中的數(shù)據(jù)類(lèi)型不是模型,而是在values方法中指定的字段和值形成的字典:

articles=Article.objects.values("title",'content')

forarticleinarticles:

print(article)

以上打印出來(lái)的article是類(lèi)似于{"title":"abc","content":"xxx"}的形式。如果在values中沒(méi)有傳遞任何參數(shù),那么將會(huì)返回這個(gè)惡模型中所有的屬性。

values_list:類(lèi)似于values。只不過(guò)返回的QuerySet中,存儲(chǔ)的不是字典,而是元組。示例代碼如下:

articles=Article.objects.values_list("id","title")

print(articles)

那么在打印articles后,結(jié)果為<QuerySet [(1,'abc'),(2,'xxx'),...]>等。如果在values_list中只有一個(gè)字段。那么你可以傳遞flat=True來(lái)將結(jié)果扁平化。示例代碼如下:

articles1=Article.objects.values_list("title")

>><QuerySet[("abc",),("xxx",),...]>

articles2=Article.objects.values_list("title",flat=True)

>><QuerySet["abc",'xxx',...]>

all:獲取這個(gè)ORM模型的QuerySet對(duì)象。

(重點(diǎn))select_related:在提取某個(gè)模型的數(shù)據(jù)的同時(shí),也提前將相關(guān)聯(lián)的數(shù)據(jù)提取出來(lái)。比如提取文章數(shù)據(jù),可以使用select_related將author信息提取出來(lái),以后再次使用article.author的時(shí)候就不需要再次去訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)了??梢詼p少數(shù)據(jù)庫(kù)查詢(xún)的次數(shù)。示例代碼如下:

article=Article.objects.get(pk=1)

>>article.author# 重新執(zhí)行一次查詢(xún)語(yǔ)句

article=Article.objects.select_related("author").get(pk=2)

>>article.author# 不需要重新執(zhí)行查詢(xún)語(yǔ)句了

selected_related只能用在一對(duì)多或者一對(duì)一中,不能用在多對(duì)多或者多對(duì)一中。比如可以提前獲取文章的作者,但是不能通過(guò)作者獲取這個(gè)作者的文章,或者是通過(guò)某篇文章獲取這個(gè)文章所有的標(biāo)簽。

prefetch_related:這個(gè)方法和select_related非常的類(lèi)似,就是在訪(fǎng)問(wèn)多個(gè)表中的數(shù)據(jù)的時(shí)候,減少查詢(xún)的次數(shù)。這個(gè)方法是為了解決多對(duì)一和多對(duì)多的關(guān)系的查詢(xún)問(wèn)題。比如要獲取標(biāo)題中帶有hello字符串的文章以及他的所有標(biāo)簽,示例代碼如下:

fromdjango.dbimportconnection

articles=Article.objects.prefetch_related("tag_set").filter(title__contains='hello')

print(articles.query)# 通過(guò)這條命令查看在底層的SQL語(yǔ)句

forarticleinarticles:

print("title:",article.title)

print(article.tag_set.all())

?

# 通過(guò)以下代碼可以看出以上代碼執(zhí)行的sql語(yǔ)句

forsqlinconnection.queries:

print(sql)

但是如果在使用article.tag_set的時(shí)候,如果又創(chuàng)建了一個(gè)新的QuerySet那么會(huì)把之前的SQL優(yōu)化給破壞掉。比如以下代碼:

tags=Tag.obejcts.prefetch_related("articles")

fortagintags:

articles=tag.articles.filter(title__contains='hello')#因?yàn)閒ilter方法會(huì)重新生成一個(gè)QuerySet,因此會(huì)破壞掉之前的sql優(yōu)化

?

# 通過(guò)以下代碼,我們可以看到在使用了filter的,他的sql查詢(xún)會(huì)更多,而沒(méi)有使用filter的,只有兩次sql查詢(xún)

forsqlinconnection.queries:

print(sql)

那如果確實(shí)是想要在查詢(xún)的時(shí)候指定過(guò)濾條件該如何做呢,這時(shí)候我們可以使用django.db.models.Prefetch來(lái)實(shí)現(xiàn),Prefetch這個(gè)可以提前定義好queryset。示例代碼如下:

tags=Tag.objects.prefetch_related(Prefetch("articles",queryset=Article.objects.filter(title__contains='hello'))).all()

fortagintags:

articles=tag.articles.all()

forarticleinarticles:

print(article)

?

forsqlinconnection.queries:

print('='*30)

print(sql)

因?yàn)槭褂昧薖refetch,即使在查詢(xún)文章的時(shí)候使用了filter,也只會(huì)發(fā)生兩次查詢(xún)操作。

defer:在一些表中,可能存在很多的字段,但是一些字段的數(shù)據(jù)量可能是比較龐大的,而此時(shí)你又不需要,比如我們?cè)讷@取文章列表的時(shí)候,文章的內(nèi)容我們是不需要的,因此這時(shí)候我們就可以使用defer來(lái)過(guò)濾掉一些字段。這個(gè)字段跟values有點(diǎn)類(lèi)似,只不過(guò)defer返回的不是字典,而是模型。示例代碼如下:

articles=list(Article.objects.defer("title"))

forsqlinconnection.queries:

print('='*30)

print(sql)

在看以上代碼的sql語(yǔ)句,你就可以看到,查找文章的字段,除了title,其他字段都查找出來(lái)了。當(dāng)然,你也可以使用article.title來(lái)獲取這個(gè)文章的標(biāo)題,但是會(huì)重新執(zhí)行一個(gè)查詢(xún)的語(yǔ)句。示例代碼如下:

articles=list(Article.objects.defer("title"))

forarticleinarticles:

# 因?yàn)樵谏厦嫣崛〉臅r(shí)候過(guò)濾了title

# 這個(gè)地方重新獲取title,將重新向數(shù)據(jù)庫(kù)中進(jìn)行一次查找操作

print(article.title)

forsqlinconnection.queries:

print('='*30)

print(sql)

defer雖然能過(guò)濾字段,但是有些字段是不能過(guò)濾的,比如id,即使你過(guò)濾了,也會(huì)提取出來(lái)。

only:跟defer類(lèi)似,只不過(guò)defer是過(guò)濾掉指定的字段,而only是只提取指定的字段。

get:獲取滿(mǎn)足條件的數(shù)據(jù)。這個(gè)函數(shù)只能返回一條數(shù)據(jù),并且如果給的條件有多條數(shù)據(jù),那么這個(gè)方法會(huì)拋出MultipleObjectsReturned錯(cuò)誤,如果給的條件沒(méi)有任何數(shù)據(jù),那么就會(huì)拋出DoesNotExit錯(cuò)誤。所以這個(gè)方法在獲取數(shù)據(jù)的只能,只能有且只有一條。

create:創(chuàng)建一條數(shù)據(jù),并且保存到數(shù)據(jù)庫(kù)中。這個(gè)方法相當(dāng)于先用指定的模型創(chuàng)建一個(gè)對(duì)象,然后再調(diào)用這個(gè)對(duì)象的save方法。示例代碼如下:

article=Article(title='abc')

article.save()

?

# 下面這行代碼相當(dāng)于以上兩行代碼

article=Article.objects.create(title='abc')

get_or_create:根據(jù)某個(gè)條件進(jìn)行查找,如果找到了那么就返回這條數(shù)據(jù),如果沒(méi)有查找到,那么就創(chuàng)建一個(gè)。示例代碼如下:

obj,created=Category.objects.get_or_create(title='默認(rèn)分類(lèi)')

如果有標(biāo)題等于默認(rèn)分類(lèi)的分類(lèi),那么就會(huì)查找出來(lái),如果沒(méi)有,則會(huì)創(chuàng)建并且存儲(chǔ)到數(shù)據(jù)庫(kù)中。這個(gè)方法的返回值是一個(gè)元組,元組的第一個(gè)參數(shù)obj是這個(gè)對(duì)象,第二個(gè)參數(shù)created代表是否創(chuàng)建的。

bulk_create:一次性創(chuàng)建多個(gè)數(shù)據(jù)。示例代碼如下:

Tag.objects.bulk_create([

Tag(name='111'),

Tag(name='222'),

])

count:獲取提取的數(shù)據(jù)的個(gè)數(shù)。如果想要知道總共有多少條數(shù)據(jù),那么建議使用count,而不是使用len(articles)這種。因?yàn)閏ount在底層是使用select count(*)來(lái)實(shí)現(xiàn)的,這種方式比使用len函數(shù)更加的高效。

first和last:返回QuerySet中的第一條和最后一條數(shù)據(jù)。

aggregate:使用聚合函數(shù)。

exists:判斷某個(gè)條件的數(shù)據(jù)是否存在。如果要判斷某個(gè)條件的元素是否存在,那么建議使用exists,這比使用count或者直接判斷QuerySet更有效得多。示例代碼如下:

ifArticle.objects.filter(title__contains='hello').exists():

print(True)

比使用count更高效:

ifArticle.objects.filter(title__contains='hello').count()>0:

print(True)

也比直接判斷QuerySet更高效:

ifArticle.objects.filter(title__contains='hello'):

print(True)

distinct:去除掉那些重復(fù)的數(shù)據(jù)。這個(gè)方法如果底層數(shù)據(jù)庫(kù)用的是MySQL,那么不能傳遞任何的參數(shù)。比如想要提取所有銷(xiāo)售的價(jià)格超過(guò)80元的圖書(shū),并且刪掉那些重復(fù)的,那么可以使用distinct來(lái)幫我們實(shí)現(xiàn),示例代碼如下:

books=Book.objects.filter(bookorder__price__gte=80).distinct()

需要注意的是,如果在distinct之前使用了order_by,那么因?yàn)閛rder_by會(huì)提取order_by中指定的字段,因此再使用distinct就會(huì)根據(jù)多個(gè)字段來(lái)進(jìn)行唯一化,所以就不會(huì)把那些重復(fù)的數(shù)據(jù)刪掉。示例代碼如下:

orders=BookOrder.objects.order_by("create_time").values("book_id").distinct()

那么以上代碼因?yàn)槭褂昧薿rder_by,即使使用了distinct,也會(huì)把重復(fù)的book_id提取出來(lái)。

update:執(zhí)行更新操作,在SQL底層走的也是update命令。比如要將所有category為空的article的article字段都更新為默認(rèn)的分類(lèi)。示例代碼如下:

Article.objects.filter(category__isnull=True).update(category_id=3)

注意這個(gè)方法走的是更新的邏輯。所以更新完成后保存到數(shù)據(jù)庫(kù)中不會(huì)執(zhí)行save方法,因此不會(huì)更新auto_now設(shè)置的字段。

delete:刪除所有滿(mǎn)足條件的數(shù)據(jù)。刪除數(shù)據(jù)的時(shí)候,要注意on_delete指定的處理方式。

切片操作:有時(shí)候我們查找數(shù)據(jù),有可能只需要其中的一部分。那么這時(shí)候可以使用切片操作來(lái)幫我們完成。QuerySet使用切片操作就跟列表使用切片操作是一樣的。示例代碼如下:

books=Book.objects.all()[1:3]

forbookinbooks:

print(book)

切片操作并不是把所有數(shù)據(jù)從數(shù)據(jù)庫(kù)中提取出來(lái)再做切片操作。而是在數(shù)據(jù)庫(kù)層面使用LIMIE和OFFSET來(lái)幫我們完成。所以如果只需要取其中一部分的數(shù)據(jù)的時(shí)候,建議大家使用切片操作。

什么時(shí)候Django會(huì)將QuerySet轉(zhuǎn)換為SQL去執(zhí)行:

生成一個(gè)QuerySet對(duì)象并不會(huì)馬上轉(zhuǎn)換為SQL語(yǔ)句去執(zhí)行。比如我們獲取Book表下所有的圖書(shū):

books=Book.objects.all()

print(connection.queries)

我們可以看到在打印connection.quries的時(shí)候打印的是一個(gè)空的列表。說(shuō)明上面的QuerySet并沒(méi)有真正的執(zhí)行。在以下情況下QuerySet會(huì)被轉(zhuǎn)換為SQL語(yǔ)句執(zhí)行:

迭代:在遍歷QuerySet對(duì)象的時(shí)候,會(huì)首先先執(zhí)行這個(gè)SQL語(yǔ)句,然后再把這個(gè)結(jié)果返回進(jìn)行迭代。比如以下代碼就會(huì)轉(zhuǎn)換為SQL語(yǔ)句:

forbookinBook.objects.all():

print(book)

使用步長(zhǎng)做切片操作:QuerySet可以類(lèi)似于列表一樣做切片操作。做切片操作本身不會(huì)執(zhí)行SQL語(yǔ)句,但是如果如果在做切片操作的時(shí)候提供了步長(zhǎng),那么就會(huì)立馬執(zhí)行SQL語(yǔ)句。需要注意的是,做切片后不能再執(zhí)行filter方法,否則會(huì)報(bào)錯(cuò)。

調(diào)用len函數(shù):調(diào)用len函數(shù)用來(lái)獲取QuerySet中總共有多少條數(shù)據(jù)也會(huì)執(zhí)行SQL語(yǔ)句。

調(diào)用list函數(shù):調(diào)用list函數(shù)用來(lái)將一個(gè)QuerySet對(duì)象轉(zhuǎn)換為list對(duì)象也會(huì)立馬執(zhí)行SQL語(yǔ)句。

判斷:如果對(duì)某個(gè)QuerySet進(jìn)行判斷,也會(huì)立馬執(zhí)行SQL語(yǔ)句。

ORM模型遷移

遷移命令:

makemigrations:將模型生成遷移腳本。模型所在的app,必須放在settings.py中的INSTALLED_APPS中。這個(gè)命令有以下幾個(gè)常用選項(xiàng):

app_label:后面可以跟一個(gè)或者多個(gè)app,那么就只會(huì)針對(duì)這幾個(gè)app生成遷移腳本。如果沒(méi)有任何的app_label,那么會(huì)檢查INSTALLED_APPS中所有的app下的模型,針對(duì)每一個(gè)app都生成響應(yīng)的遷移腳本。

--name:給這個(gè)遷移腳本指定一個(gè)名字。

--empty:生成一個(gè)空的遷移腳本。如果你想寫(xiě)自己的遷移腳本,可以使用這個(gè)命令來(lái)實(shí)現(xiàn)一個(gè)空的文件,然后自己再在文件中寫(xiě)遷移腳本。

migrate:將新生成的遷移腳本。映射到數(shù)據(jù)庫(kù)中。創(chuàng)建新的表或者修改表的結(jié)構(gòu)。以下一些常用的選項(xiàng):

app_label:將某個(gè)app下的遷移腳本映射到數(shù)據(jù)庫(kù)中。如果沒(méi)有指定,那么會(huì)將所有在INSTALLED_APPS中的app下的模型都映射到數(shù)據(jù)庫(kù)中。

app_label migrationname:將某個(gè)app下指定名字的migration文件映射到數(shù)據(jù)庫(kù)中。

--fake:可以將指定的遷移腳本名字添加到數(shù)據(jù)庫(kù)中。但是并不會(huì)把遷移腳本轉(zhuǎn)換為SQL語(yǔ)句,修改數(shù)據(jù)庫(kù)中的表。

--fake-initial:將第一次生成的遷移文件版本號(hào)記錄在數(shù)據(jù)庫(kù)中。但并不會(huì)真正的執(zhí)行遷移腳本。

showmigrations:查看某個(gè)app下的遷移文件。如果后面沒(méi)有app,那么將查看INSTALLED_APPS中所有的遷移文件。

sqlmigrate:查看某個(gè)遷移文件在映射到數(shù)據(jù)庫(kù)中的時(shí)候,轉(zhuǎn)換的SQL語(yǔ)句。

migrations中的遷移版本和數(shù)據(jù)庫(kù)中的遷移版本對(duì)不上怎么辦?

找到哪里不一致,然后使用python manage.py --fake [版本名字],將這個(gè)版本標(biāo)記為已經(jīng)映射。

刪除指定app下migrations和數(shù)據(jù)庫(kù)表django_migrations中和這個(gè)app相關(guān)的版本號(hào),然后將模型中的字段和數(shù)據(jù)庫(kù)中的字段保持一致,再使用命令python manage.py makemigrations重新生成一個(gè)初始化的遷移腳本,之后再使用命令python manage.py makemigrations --fake-initial來(lái)將這個(gè)初始化的遷移腳本標(biāo)記為已經(jīng)映射。以后再修改就沒(méi)有問(wèn)題了。

更多關(guān)于遷移腳本的。請(qǐng)查看官方文檔:https://docs.djangoproject.com/en/2.0/topics/migrations/

根據(jù)已有的表自動(dòng)生成模型:

在實(shí)際開(kāi)發(fā)中,有些時(shí)候可能數(shù)據(jù)庫(kù)已經(jīng)存在了。如果我們用Django來(lái)開(kāi)發(fā)一個(gè)網(wǎng)站,讀取的是之前已經(jīng)存在的數(shù)據(jù)庫(kù)中的數(shù)據(jù)。那么該如何將模型與數(shù)據(jù)庫(kù)中的表映射呢?根據(jù)舊的數(shù)據(jù)庫(kù)生成對(duì)應(yīng)的ORM模型,需要以下幾個(gè)步驟:

Django給我們提供了一個(gè)inspectdb的命令,可以非常方便的將已經(jīng)存在的表,自動(dòng)的生成模型。想要使用inspectdb自動(dòng)將表生成模型。首先需要在settings.py中配置好數(shù)據(jù)庫(kù)相關(guān)信息。不然就找不到數(shù)據(jù)庫(kù)。示例代碼如下:

DATABASES= {

'default': {

'ENGINE':'django.db.backends.mysql',

'NAME':"migrations_demo",

'HOST':'127.0.0.1',

'PORT':'3306',

'USER':'root',

'PASSWORD':'root'

? ? }

}

比如有以下表:

article表:

tag表:

article_tag表:

front_user表:

那么通過(guò)python manage.py inspectdb,就會(huì)將表轉(zhuǎn)換為模型后的代碼,顯示在終端:

fromdjango.dbimportmodels

?

classArticleArticle(models.Model):

title=models.CharField(max_length=100)

content=models.TextField(blank=True,null=True)

create_time=models.DateTimeField(blank=True,null=True)

author=models.ForeignKey('FrontUserFrontuser',models.DO_NOTHING,blank=True,null=True)

?

classMeta:

managed=False

db_table='article_article'

?

classArticleArticleTags(models.Model):

article=models.ForeignKey(ArticleArticle,models.DO_NOTHING)

tag=models.ForeignKey('ArticleTag',models.DO_NOTHING)

?

classMeta:

managed=False

db_table='article_article_tags'

unique_together= (('article','tag'),)

?

classArticleTag(models.Model):

name=models.CharField(max_length=100)

?

classMeta:

managed=False

db_table='article_tag'

?

classFrontUserFrontuser(models.Model):

username=models.CharField(max_length=100)

telephone=models.CharField(max_length=11)

?

classMeta:

managed=False

db_table='front_user_frontuser'

以上代碼只是顯示在終端。如果想要保存到文件中。那么可以使用>重定向輸出到指定的文件。比如讓他輸出到models.py文件中。示例命令如下:

python manage.py inspectdb > models.py

以上的命令,只能在終端執(zhí)行,不能在pycharm->Tools->Run manage.py Task...中使用。

如果只是想要轉(zhuǎn)換一個(gè)表為模型。那么可以指定表的名字。示例命令如下:

python manage.py inspectdb article_article > models.py

修正模型:新生成的ORM模型有些地方可能不太適合使用。比如模型的名字,表之間的關(guān)系等等。那么以下選項(xiàng)還需要重新配置一下:

模型名:自動(dòng)生成的模型,是根據(jù)表的名字生成的,可能不是你想要的。這時(shí)候模型的名字你可以改成任何你想要的。

模型所屬app:根據(jù)自己的需要,將相應(yīng)的模型放在對(duì)應(yīng)的app中。放在同一個(gè)app中也是沒(méi)有任何問(wèn)題的。只是不方便管理。

模型外鍵引用:將所有使用ForeignKey的地方,模型引用都改成字符串。這樣不會(huì)產(chǎn)生模型順序的問(wèn)題。另外,如果引用的模型已經(jīng)移動(dòng)到其他的app中了,那么還要加上這個(gè)app的前綴。

讓Django管理模型:將Meta下的managed=False刪掉,如果保留這個(gè),那么以后這個(gè)模型有任何的修改,使用migrate都不會(huì)映射到數(shù)據(jù)庫(kù)中。

當(dāng)有多對(duì)多的時(shí)候,應(yīng)該也要修正模型。將中間表注視了,然后使用ManyToManyField來(lái)實(shí)現(xiàn)多對(duì)多。并且,使用ManyToManyField生成的中間表的名字可能和數(shù)據(jù)庫(kù)中那個(gè)中間表的名字不一致,這時(shí)候肯定就不能正常連接了。那么可以通過(guò)db_table來(lái)指定中間表的名字。示例代碼如下:

classArticle(models.Model):

title=models.CharField(max_length=100,blank=True,null=True)

content=models.TextField(blank=True,null=True)

author=models.ForeignKey('front.User',models.SET_NULL,blank=True,null=True)

# 使用ManyToManyField模型到表,生成的中間表的規(guī)則是:article_tags

# 但現(xiàn)在已經(jīng)存在的表的名字叫做:article_tag

# 可以使用db_table,指定中間表的名字

tags=models.ManyToManyField("Tag",db_table='article_tag')

?

classMeta:

db_table='article'

表名:切記不要修改表的名字。不然映射到數(shù)據(jù)庫(kù)中,會(huì)發(fā)生找不到對(duì)應(yīng)表的錯(cuò)誤。

執(zhí)行命令python manage.py makemigrations生成初始化的遷移腳本。方便后面通過(guò)ORM來(lái)管理表。這時(shí)候還需要執(zhí)行命令python manage.py migrate --fake-initial,因?yàn)槿绻皇褂?-fake-initial,那么會(huì)將遷移腳本會(huì)映射到數(shù)據(jù)庫(kù)中。這時(shí)候遷移腳本會(huì)新創(chuàng)建表,而這個(gè)表之前是已經(jīng)存在了的,所以肯定會(huì)報(bào)錯(cuò)。此時(shí)我們只要將這個(gè)0001-initial的狀態(tài)修改為已經(jīng)映射,而不真正執(zhí)行映射,下次再migrate的時(shí)候,就會(huì)忽略他。

將Django的核心表映射到數(shù)據(jù)庫(kù)中:Django中還有一些核心的表也是需要?jiǎng)?chuàng)建的。不然有些功能是用不了的。比如auth相關(guān)表。如果這個(gè)數(shù)據(jù)庫(kù)之前就是使用Django開(kāi)發(fā)的,那么這些表就已經(jīng)存在了??梢圆挥霉芰?。如果之前這個(gè)數(shù)據(jù)庫(kù)不是使用Django開(kāi)發(fā)的,那么應(yīng)該使用migrate命令將Django中的核心模型映射到數(shù)據(jù)庫(kù)中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文:https://my.oschina.net/liuyuantao/blog/751438 查詢(xún)集API 參...
    陽(yáng)光小鎮(zhèn)少爺閱讀 3,967評(píng)論 0 8
  • Django 1.8.2 文檔Home | Table of contents | Index | Modules...
    軒轅小愛(ài)閱讀 2,425評(píng)論 0 2
  • Django 準(zhǔn)備 “虛擬環(huán)境為什么需要虛擬環(huán)境:到目前位置,我們所有的第三方包安裝都是直接通過(guò) pip inst...
    33jubi閱讀 1,395評(píng)論 0 5
  • 模塊間聯(lián)系越多,其耦合性越強(qiáng),同時(shí)表明其獨(dú)立性越差( 降低耦合性,可以提高其獨(dú)立性)。軟件設(shè)計(jì)中通常用耦合度和內(nèi)聚...
    riverstation閱讀 2,235評(píng)論 0 8
  • 查詢(xún)條件: exact:在底層會(huì)被翻譯成=。 iexact:在底層會(huì)被翻譯成LIKE。LIKE和=:大部分情況下都...
    xinmin閱讀 679評(píng)論 0 0

友情鏈接更多精彩內(nèi)容