查詢操作
查找是數(shù)據(jù)庫操作中一個非常重要的技術。查詢一般就是使用filter、exclude以及get三個方法來實現(xiàn)。我們可以在調用這些方法的時候傳遞不同的參數(shù)來實現(xiàn)查詢需求。在ORM層面,這些查詢條件都是使用field+__+condition的方式來使用的。以下將那些常用的查詢條件來一一解釋。
查詢條件
exact:
使用精確的=進行查找。如果提供的是一個None,那么在SQL層面就是被解釋為NULL。示例代碼如下:
article=Article.objects.get(id__exact=14)
article=Article.objects.get(id__exact=None)
以上的兩個查找在翻譯為SQL語句為如下:
select...fromarticlewhereid=14;
select...fromarticlewhereidISNULL;
iexact:
使用like進行查找。示例代碼如下:
article = Article.objects.filter(title__iexact='hello world')
那么以上的查詢就等價于以下的SQL語句:
select...fromarticlewheretitlelike'hello world';
注意上面這個sql語句,因為在MySQL中,沒有一個叫做ilike的。所以exact和iexact的區(qū)別實際上就是LIKE和=的區(qū)別,在大部分collation=utf8_general_ci情況下都是一樣的(collation是用來對字符串比較的)。
contains:
大小寫敏感,判斷某個字段是否包含了某個數(shù)據(jù)。示例代碼如下:
articles=Article.objects.filter(title__contains='hello')
在翻譯成SQL語句為如下:
select...wheretitlelikebinary'%hello%';
要注意的是,在使用contains的時候,翻譯成的sql語句左右兩邊是有百分號的,意味著使用的是模糊查詢。而exact翻譯成sql語句左右兩邊是沒有百分號的,意味著使用的是精確的查詢。
icontains:
大小寫不敏感的匹配查詢。示例代碼如下:
articles=Article.objects.filter(title__icontains='hello')
在翻譯成SQL語句為如下:
select...wheretitlelike'%hello%';
in:
提取那些給定的field的值是否在給定的容器中。容器可以為list、tuple或者任何一個可以迭代的對象,包括QuerySet對象。示例代碼如下:
articles=Article.objects.filter(id__in=[1,2,3])
以上代碼在翻譯成SQL語句為如下:
select...whereidin(1,3,4)
當然也可以傳遞一個QuerySet對象進去。示例代碼如下:
inner_qs=Article.objects.filter(title__contains='hello')
categories=Category.objects.filter(article__in=inner_qs)
以上代碼的意思是獲取那些文章標題包含hello的所有分類。將翻譯成以下SQL語句,示例代碼如下:
select...fromcategorywherearticle.idin(selectidfromarticlewheretitlelike'%hello%');
gt:
某個field的值要大于給定的值。示例代碼如下:
articles=Article.objects.filter(id__gt=4)
以上代碼的意思是將所有id大于4的文章全部都找出來。將翻譯成以下SQL語句:
select...whereid >4;
gte:
類似于gt,是大于等于。
lt:
類似于gt是小于。
lte:
類似于lt,是小于等于。
startswith:
判斷某個字段的值是否是以某個值開始的。大小寫敏感。示例代碼如下:
articles=Article.objects.filter(title__startswith='hello')
以上代碼的意思是提取所有標題以hello字符串開頭的文章。將翻譯成以下SQL語句:
select...wheretitlelike'hello%'
istartswith:
類似于startswith,但是大小寫是不敏感的。
endswith:
判斷某個字段的值是否以某個值結束。大小寫敏感。示例代碼如下:
articles=Article.objects.filter(title__endswith='world')
以上代碼的意思是提取所有標題以world結尾的文章。將翻譯成以下SQL語句:
select...wheretitlelike'%world';
iendswith:
類似于endswith,只不過大小寫不敏感。
range:
判斷某個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ā)布時間在2018/1/1到2018/12/12之間的文章。將翻譯成以下的SQL語句:
select...fromarticlewherepub_timebetween'2018-01-01'and'2018-12-12'。
需要注意的是,以上提取數(shù)據(jù),不會包含最后一個值。也就是不會包含2018/12/12的文章。而且另外一個重點,因為我們在settings.py中指定了USE_TZ=True,并且設置了TIME_ZONE='Asia/Shanghai',因此我們在提取數(shù)據(jù)的時候要使用django.utils.timezone.make_aware先將datetime.datetime從navie時間轉換為aware時間。make_aware會將指定的時間轉換為TIME_ZONE中指定的時區(qū)的時間。
date:
針對某些date或者datetime類型的字段。可以指定date的范圍。并且這個時間過濾,還可以使用鏈式調用。示例代碼如下:
articles=Article.objects.filter(pub_date__date=date(2018,3,29))
以上代碼的意思是查找時間為2018/3/29這一天發(fā)表的所有文章。將翻譯成以下的sql語句:
select...WHEREDATE(CONVERT_TZ(`front_article`.`pub_date`,'UTC','Asia/Shanghai')) =2018-03-29
注意,因為默認情況下MySQL的表中是沒有存儲時區(qū)相關的信息的。因此我們需要下載一些時區(qū)表的文件,然后添加到Mysql的配置路徑中。如果你用的是windows操作系統(tǒng)。那么在http://dev.mysql.com/downloads/timezones.html下載timezone_2018d_posix.zip - POSIX standard。然后將下載下來的所有文件拷貝到C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql中,如果提示文件名重復,那么選擇覆蓋即可。如果用的是linux或者mac系統(tǒng),那么在命令行中執(zhí)行以下命令:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p,然后輸入密碼,從系統(tǒng)中加載時區(qū)文件更新到mysql中。
year:
根據(jù)年份進行查找。示例代碼如下:
articles=Article.objects.filter(pub_date__year=2018)
articles=Article.objects.filter(pub_date__year__gte=2017)
以上的代碼在翻譯成SQL語句為如下:
select...wherepub_datebetween'2018-01-01'and'2018-12-31';
select...wherepub_date >='2017-01-01';
month:
同year,根據(jù)月份進行查找。
day:
同year,根據(jù)日期進行查找。
week_day:
Django 1.11新增的查找方式。同year,根據(jù)星期幾進行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。
time:
根據(jù)時間進行查找。示例代碼如下:
articles=Article.objects.filter(pub_date__time=datetime.time(12,12,12));
以上的代碼是獲取每一天中12點12分12秒發(fā)表的所有文章。更多的關于時間的過濾,請參考Django官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range。
isnull:
根據(jù)值是否為空進行查找。示例代碼如下:
articles=Article.objects.filter(pub_date__isnull=False)
以上的代碼的意思是獲取所有發(fā)布日期不為空的文章。將來翻譯成SQL語句如下:
select...wherepub_dateisnotnull;
regex和iregex:
大小寫敏感和大小寫不敏感的正則表達式。示例代碼如下:
articles=Article.objects.filter(title__regex=r'^hello')
以上代碼的意思是提取所有標題以hello字符串開頭的文章。將翻譯成以下的SQL語句:
select...wheretitle regexp binary'^hello';
iregex是大小寫不敏感的。
根據(jù)關聯(lián)的表進行查詢:
假如現(xiàn)在有兩個ORM模型,一個是Article,一個是Category。代碼如下:
classCategory(models.Model):
"""文章分類表"""
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)
比如想要獲取文章標題中包含"hello"的所有的分類。那么可以通過以下代碼來實現(xiàn):
categories=Category.object.filter(article__title__contains("hello"))
聚合函數(shù):
如果你用原生SQL,則可以使用聚合函數(shù)來提取數(shù)據(jù)。比如提取某個商品銷售的數(shù)量,那么可以使用Count,如果想要知道商品銷售的平均價格,那么可以使用Avg。聚合函數(shù)是通過aggregate方法來實現(xiàn)的。在講解這些聚合函數(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):
"""圖書模型"""
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):
"""圖書訂單模型"""
book=models.ForeignKey("Book",on_delete=models.CASCADE)
price=models.FloatField()
?
classMeta:
db_table='book_order'
Avg:求平均值。比如想要獲取所有圖書的價格平均值。那么可以使用以下代碼實現(xiàn)。
fromdjango.db.modelsimportAvg
result=Book.objects.aggregate(Avg('price'))
print(result)
以上的打印結果是:
{"price__avg":23.0}
其中price__avg的結構是根據(jù)field__avg規(guī)則構成的。如果想要修改默認的名字,那么可以將Avg賦值給一個關鍵字參數(shù)。示例代碼如下:
fromdjango.db.modelsimportAvg
result=Book.objects.aggregate(my_avg=Avg('price'))
print(result)
那么以上的結果打印為:
{"my_avg":23}
Count:獲取指定的對象的個數(shù)。示例代碼如下:
fromdjango.db.modelsimportCount
result=Book.objects.aggregate(book_num=Count('id'))
以上的result將返回Book表中總共有多少本圖書。Count類中,還有另外一個參數(shù)叫做distinct,默認是等于False,如果是等于True,那么將去掉那些重復的值。比如要獲取作者表中所有的不重復的郵箱總共有多少個,那么可以通過以下代碼來實現(xiàn):
fromdjang.db.modelsimportCount
result=Author.objects.aggregate(count=Count('email',distinct=True))
Max和Min:獲取指定對象的最大值和最小值。比如想要獲取Author表中,最大的年齡和最小的年齡分別是多少。那么可以通過以下代碼來實現(xiàn):
fromdjango.db.modelsimportMax,Min
result=Author.objects.aggregate(Max('age'),Min('age'))
如果最大的年齡是88,最小的年齡是18。那么以上的result將為:
{"age__max":88,"age__min":18}
Sum:求指定對象的總和。比如要求圖書的銷售總額。那么可以使用以下代碼實現(xiàn):
fromdjang.db.modelsimportSum
result=Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")
以上的代碼annotate的意思是給Book表在查詢的時候添加一個字段叫做total,這個字段的數(shù)據(jù)來源是從BookStore模型的price的總和而來。values方法是只提取name和total兩個字段的值。
更多的聚合函數(shù)請參考官方文檔:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions
aggregate和annotate的區(qū)別:
aggregate:返回使用聚合函數(shù)后的字段和值。? (字典)
annotate:在原來模型字段的基礎之上添加一個使用了聚合函數(shù)的字段,并且在使用聚合函數(shù)的時候,會使用當前這個模型的主鍵進行分組(group by)。比如以上Sum的例子,如果使用的是annotate,那么將在每條圖書的數(shù)據(jù)上都添加一個字段叫做total,計算這本書的銷售總額。而如果使用的是aggregate,那么將求所有圖書的銷售總額。
F表達式和Q表達式:
F表達式:
F表達式是用來優(yōu)化ORM操作數(shù)據(jù)庫的。比如我們要將公司所有員工的薪水都增加1000元,如果按照正常的流程,應該是先從數(shù)據(jù)庫中提取所有的員工工資到Python內存中,然后使用Python代碼在員工工資的基礎之上增加1000元,最后再保存到數(shù)據(jù)庫中。這里面涉及的流程就是,首先從數(shù)據(jù)庫中提取數(shù)據(jù)到Python內存中,然后在Python內存中做完運算,之后再保存到數(shù)據(jù)庫中。示例代碼如下:
employees=Employee.objects.all()
foremployeeinemployees:
employee.salary+=1000
employee.save()
而我們的F表達式就可以優(yōu)化這個流程,他可以不需要先把數(shù)據(jù)從數(shù)據(jù)庫中提取出來,計算完成后再保存回去,他可以直接執(zhí)行SQL語句,就將員工的工資增加1000元。示例代碼如下:
fromdjang.db.modelsimportF
Employee.object.update(salary=F("salary")+1000)
F表達式并不會馬上從數(shù)據(jù)庫中獲取數(shù)據(jù),而是在生成SQL語句的時候,動態(tài)的獲取傳給F表達式的值。
比如如果想要獲取作者中,name和email相同的作者數(shù)據(jù)。如果不使用F表達式,那么需要使用以下代碼來完成:
authors=Author.objects.all()
forauthorinauthors:
ifauthor.name==author.email:
print(author)
如果使用F表達式,那么一行代碼就可以搞定。示例代碼如下:
fromdjango.db.modelsimportF
authors=Author.objects.filter(name=F("email"))
Q表達式:
如果想要實現(xiàn)所有價格高于100元,并且評分達到9.0以上評分的圖書。那么可以通過以下代碼來實現(xiàn):
books=Book.objects.filter(price__gte=100,rating__gte=9)
以上這個案例是一個并集查詢,可以簡單的通過傳遞多個條件進去來實現(xiàn)。但是如果想要實現(xiàn)一些復雜的查詢語句,比如要查詢所有價格低于10元,或者是評分低于9分的圖書。那就沒有辦法通過傳遞多個條件進去實現(xiàn)了。這時候就需要使用Q表達式來實現(xiàn)了。示例代碼如下:
fromdjango.db.modelsimportQ
books=Book.objects.filter(Q(price__lte=10)|Q(rating__lte=9))
以上是進行或運算,當然還可以進行其他的運算,比如有&和~(非)等。一些用Q表達式的例子如下:
fromdjango.db.modelsimportQ
# 獲取id等于3的圖書
books=Book.objects.filter(Q(id=3))
# 獲取id等于3,或者名字中包含文字"記"的圖書
books=Book.objects.filter(Q(id=3)|Q(name__contains("記")))
# 獲取價格大于100,并且書名中包含"記"的圖書
books=Book.objects.filter(Q(price__gte=100)&Q(name__contains("記")))
# 獲取書名包含“記”,但是id不等于3的圖書
books=Book.objects.filter(Q(name__contains='記')&~Q(id=3))
QuerySet API:
我們通常做查詢操作的時候,都是通過模型名字.objects的方式進行操作。其實模型名字.objects是一個django.db.models.manager.Manager對象,而Manager這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過Python動態(tài)添加的方式,從QuerySet類中拷貝過來的。所以我們如果想要學習ORM模型的查找操作,必須首先要學會QuerySet上的一些API的使用。
返回新的QuerySet的方法:
在使用QuerySet進行查找操作的時候,可以提供多種操作。比如過濾完后還要根據(jù)某個字段進行排序,那么這一系列的操作我們可以通過一個非常流暢的鏈式調用的方式進行。比如要從文章表中獲取標題為123,并且提取后要將結果根據(jù)發(fā)布的時間進行排序,那么可以使用以下方式來完成:
articles=Article.objects.filter(title='123').order_by('create_time')
可以看到order_by方法是直接在filter執(zhí)行后調用的。這說明filter返回的對象是一個擁有order_by方法的對象。而這個對象正是一個新的QuerySet對象。因此可以使用order_by方法。
那么以下將介紹在那些會返回新的QuerySet對象的方法。
filter:將滿足條件的數(shù)據(jù)提取出來,返回一個新的QuerySet。具體的filter可以提供什么條件查詢。請見查詢操作章節(jié)。
exclude:排除滿足條件的數(shù)據(jù),返回一個新的QuerySet。示例代碼如下:
Article.objects.exclude(title__contains='hello')
以上代碼的意思是提取那些標題不包含hello的圖書。
annotate:給QuerySet中的每個對象都添加一個使用查詢表達式(聚合函數(shù)、F表達式、Q表達式、Func表達式等)的新字段。示例代碼如下:
articles=Article.objects.annotate(author_name=F("author__name"))
以上代碼將在每個對象中都添加一個author__name的字段,用來顯示這個文章的作者的年齡。
order_by:指定將查詢的結果根據(jù)某個字段進行排序。如果要倒敘排序,那么可以在這個字段的前面加一個負號。示例代碼如下:
# 根據(jù)創(chuàng)建的時間正序排序
articles=Article.objects.order_by("create_time")
# 根據(jù)創(chuàng)建的時間倒序排序
articles=Article.objects.order_by("-create_time")
# 根據(jù)作者的名字進行排序
articles=Article.objects.order_by("author__name")
# 首先根據(jù)創(chuàng)建的時間進行排序,如果時間相同,則根據(jù)作者的名字進行排序
articles=Article.objects.order_by("create_time",'author__name')
一定要注意的一點是,多個order_by,會把前面排序的規(guī)則給打亂,而使用后面的排序方式。比如以下代碼:
articles=Article.objects.order_by("create_time").order_by("author__name")
他會根據(jù)作者的名字進行排序,而不是使用文章的創(chuàng)建時間。
values:用來指定在提取數(shù)據(jù)出來,需要提取哪些字段。默認情況下會把表中所有的字段全部都提取出來,可以使用values來進行指定,并且使用了values方法后,提取出的QuerySet中的數(shù)據(jù)類型不是模型,而是在values方法中指定的字段和值形成的字典:
articles=Article.objects.values("title",'content')
forarticleinarticles:
print(article)
以上打印出來的article是類似于{"title":"abc","content":"xxx"}的形式。如果在values中沒有傳遞任何參數(shù),那么將會返回這個惡模型中所有的屬性。
values_list:類似于values。只不過返回的QuerySet中,存儲的不是字典,而是元組。示例代碼如下:
articles=Article.objects.values_list("id","title")
print(articles)
那么在打印articles后,結果為<QuerySet [(1,'abc'),(2,'xxx'),...]>等。如果在values_list中只有一個字段。那么你可以傳遞flat=True來將結果扁平化。示例代碼如下:
articles1=Article.objects.values_list("title")
>><QuerySet[("abc",),("xxx",),...]>
articles2=Article.objects.values_list("title",flat=True)
>><QuerySet["abc",'xxx',...]>
all:獲取這個ORM模型的QuerySet對象。
(重點)select_related:在提取某個模型的數(shù)據(jù)的同時,也提前將相關聯(lián)的數(shù)據(jù)提取出來。比如提取文章數(shù)據(jù),可以使用select_related將author信息提取出來,以后再次使用article.author的時候就不需要再次去訪問數(shù)據(jù)庫了??梢詼p少數(shù)據(jù)庫查詢的次數(shù)。示例代碼如下:
article=Article.objects.get(pk=1)
>>article.author# 重新執(zhí)行一次查詢語句
article=Article.objects.select_related("author").get(pk=2)
>>article.author# 不需要重新執(zhí)行查詢語句了
selected_related只能用在一對多或者一對一中,不能用在多對多或者多對一中。比如可以提前獲取文章的作者,但是不能通過作者獲取這個作者的文章,或者是通過某篇文章獲取這個文章所有的標簽。
prefetch_related:這個方法和select_related非常的類似,就是在訪問多個表中的數(shù)據(jù)的時候,減少查詢的次數(shù)。這個方法是為了解決多對一和多對多的關系的查詢問題。比如要獲取標題中帶有hello字符串的文章以及他的所有標簽,示例代碼如下:
fromdjango.dbimportconnection
articles=Article.objects.prefetch_related("tag_set").filter(title__contains='hello')
print(articles.query)# 通過這條命令查看在底層的SQL語句
forarticleinarticles:
print("title:",article.title)
print(article.tag_set.all())
?
# 通過以下代碼可以看出以上代碼執(zhí)行的sql語句
forsqlinconnection.queries:
print(sql)
但是如果在使用article.tag_set的時候,如果又創(chuàng)建了一個新的QuerySet那么會把之前的SQL優(yōu)化給破壞掉。比如以下代碼:
tags=Tag.obejcts.prefetch_related("articles")
fortagintags:
articles=tag.articles.filter(title__contains='hello')#因為filter方法會重新生成一個QuerySet,因此會破壞掉之前的sql優(yōu)化
?
# 通過以下代碼,我們可以看到在使用了filter的,他的sql查詢會更多,而沒有使用filter的,只有兩次sql查詢
forsqlinconnection.queries:
print(sql)
那如果確實是想要在查詢的時候指定過濾條件該如何做呢,這時候我們可以使用django.db.models.Prefetch來實現(xiàn),Prefetch這個可以提前定義好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)
因為使用了Prefetch,即使在查詢文章的時候使用了filter,也只會發(fā)生兩次查詢操作。
defer:在一些表中,可能存在很多的字段,但是一些字段的數(shù)據(jù)量可能是比較龐大的,而此時你又不需要,比如我們在獲取文章列表的時候,文章的內容我們是不需要的,因此這時候我們就可以使用defer來過濾掉一些字段。這個字段跟values有點類似,只不過defer返回的不是字典,而是模型。示例代碼如下:
articles=list(Article.objects.defer("title"))
forsqlinconnection.queries:
print('='*30)
print(sql)
在看以上代碼的sql語句,你就可以看到,查找文章的字段,除了title,其他字段都查找出來了。當然,你也可以使用article.title來獲取這個文章的標題,但是會重新執(zhí)行一個查詢的語句。示例代碼如下:
articles=list(Article.objects.defer("title"))
forarticleinarticles:
# 因為在上面提取的時候過濾了title
# 這個地方重新獲取title,將重新向數(shù)據(jù)庫中進行一次查找操作
print(article.title)
forsqlinconnection.queries:
print('='*30)
print(sql)
defer雖然能過濾字段,但是有些字段是不能過濾的,比如id,即使你過濾了,也會提取出來。
only:跟defer類似,只不過defer是過濾掉指定的字段,而only是只提取指定的字段。
get:獲取滿足條件的數(shù)據(jù)。這個函數(shù)只能返回一條數(shù)據(jù),并且如果給的條件有多條數(shù)據(jù),那么這個方法會拋出MultipleObjectsReturned錯誤,如果給的條件沒有任何數(shù)據(jù),那么就會拋出DoesNotExit錯誤。所以這個方法在獲取數(shù)據(jù)的只能,只能有且只有一條。
create:創(chuàng)建一條數(shù)據(jù),并且保存到數(shù)據(jù)庫中。這個方法相當于先用指定的模型創(chuàng)建一個對象,然后再調用這個對象的save方法。示例代碼如下:
article=Article(title='abc')
article.save()
?
# 下面這行代碼相當于以上兩行代碼
article=Article.objects.create(title='abc')
get_or_create:根據(jù)某個條件進行查找,如果找到了那么就返回這條數(shù)據(jù),如果沒有查找到,那么就創(chuàng)建一個。示例代碼如下:
obj,created=Category.objects.get_or_create(title='默認分類')
如果有標題等于默認分類的分類,那么就會查找出來,如果沒有,則會創(chuàng)建并且存儲到數(shù)據(jù)庫中。這個方法的返回值是一個元組,元組的第一個參數(shù)obj是這個對象,第二個參數(shù)created代表是否創(chuàng)建的。
bulk_create:一次性創(chuàng)建多個數(shù)據(jù)。示例代碼如下:
Tag.objects.bulk_create([
Tag(name='111'),
Tag(name='222'),
])
count:獲取提取的數(shù)據(jù)的個數(shù)。如果想要知道總共有多少條數(shù)據(jù),那么建議使用count,而不是使用len(articles)這種。因為count在底層是使用select count(*)來實現(xiàn)的,這種方式比使用len函數(shù)更加的高效。
first和last:返回QuerySet中的第一條和最后一條數(shù)據(jù)。
aggregate:使用聚合函數(shù)。
exists:判斷某個條件的數(shù)據(jù)是否存在。如果要判斷某個條件的元素是否存在,那么建議使用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:去除掉那些重復的數(shù)據(jù)。這個方法如果底層數(shù)據(jù)庫用的是MySQL,那么不能傳遞任何的參數(shù)。比如想要提取所有銷售的價格超過80元的圖書,并且刪掉那些重復的,那么可以使用distinct來幫我們實現(xiàn),示例代碼如下:
books=Book.objects.filter(bookorder__price__gte=80).distinct()
需要注意的是,如果在distinct之前使用了order_by,那么因為order_by會提取order_by中指定的字段,因此再使用distinct就會根據(jù)多個字段來進行唯一化,所以就不會把那些重復的數(shù)據(jù)刪掉。示例代碼如下:
orders=BookOrder.objects.order_by("create_time").values("book_id").distinct()
那么以上代碼因為使用了order_by,即使使用了distinct,也會把重復的book_id提取出來。
update:執(zhí)行更新操作,在SQL底層走的也是update命令。比如要將所有category為空的article的article字段都更新為默認的分類。示例代碼如下:
Article.objects.filter(category__isnull=True).update(category_id=3)
注意這個方法走的是更新的邏輯。所以更新完成后保存到數(shù)據(jù)庫中不會執(zhí)行save方法,因此不會更新auto_now設置的字段。
delete:刪除所有滿足條件的數(shù)據(jù)。刪除數(shù)據(jù)的時候,要注意on_delete指定的處理方式。
切片操作:有時候我們查找數(shù)據(jù),有可能只需要其中的一部分。那么這時候可以使用切片操作來幫我們完成。QuerySet使用切片操作就跟列表使用切片操作是一樣的。示例代碼如下:
books=Book.objects.all()[1:3]
forbookinbooks:
print(book)
切片操作并不是把所有數(shù)據(jù)從數(shù)據(jù)庫中提取出來再做切片操作。而是在數(shù)據(jù)庫層面使用LIMIE和OFFSET來幫我們完成。所以如果只需要取其中一部分的數(shù)據(jù)的時候,建議大家使用切片操作。
什么時候Django會將QuerySet轉換為SQL去執(zhí)行:
生成一個QuerySet對象并不會馬上轉換為SQL語句去執(zhí)行。比如我們獲取Book表下所有的圖書:
books=Book.objects.all()
print(connection.queries)
我們可以看到在打印connection.quries的時候打印的是一個空的列表。說明上面的QuerySet并沒有真正的執(zhí)行。在以下情況下QuerySet會被轉換為SQL語句執(zhí)行:
迭代:在遍歷QuerySet對象的時候,會首先先執(zhí)行這個SQL語句,然后再把這個結果返回進行迭代。比如以下代碼就會轉換為SQL語句:
forbookinBook.objects.all():
print(book)
使用步長做切片操作:QuerySet可以類似于列表一樣做切片操作。做切片操作本身不會執(zhí)行SQL語句,但是如果如果在做切片操作的時候提供了步長,那么就會立馬執(zhí)行SQL語句。需要注意的是,做切片后不能再執(zhí)行filter方法,否則會報錯。
調用len函數(shù):調用len函數(shù)用來獲取QuerySet中總共有多少條數(shù)據(jù)也會執(zhí)行SQL語句。
調用list函數(shù):調用list函數(shù)用來將一個QuerySet對象轉換為list對象也會立馬執(zhí)行SQL語句。
判斷:如果對某個QuerySet進行判斷,也會立馬執(zhí)行SQL語句。
ORM模型遷移
遷移命令:
makemigrations:將模型生成遷移腳本。模型所在的app,必須放在settings.py中的INSTALLED_APPS中。這個命令有以下幾個常用選項:
app_label:后面可以跟一個或者多個app,那么就只會針對這幾個app生成遷移腳本。如果沒有任何的app_label,那么會檢查INSTALLED_APPS中所有的app下的模型,針對每一個app都生成響應的遷移腳本。
--name:給這個遷移腳本指定一個名字。
--empty:生成一個空的遷移腳本。如果你想寫自己的遷移腳本,可以使用這個命令來實現(xiàn)一個空的文件,然后自己再在文件中寫遷移腳本。
migrate:將新生成的遷移腳本。映射到數(shù)據(jù)庫中。創(chuàng)建新的表或者修改表的結構。以下一些常用的選項:
app_label:將某個app下的遷移腳本映射到數(shù)據(jù)庫中。如果沒有指定,那么會將所有在INSTALLED_APPS中的app下的模型都映射到數(shù)據(jù)庫中。
app_label migrationname:將某個app下指定名字的migration文件映射到數(shù)據(jù)庫中。
--fake:可以將指定的遷移腳本名字添加到數(shù)據(jù)庫中。但是并不會把遷移腳本轉換為SQL語句,修改數(shù)據(jù)庫中的表。
--fake-initial:將第一次生成的遷移文件版本號記錄在數(shù)據(jù)庫中。但并不會真正的執(zhí)行遷移腳本。
showmigrations:查看某個app下的遷移文件。如果后面沒有app,那么將查看INSTALLED_APPS中所有的遷移文件。
sqlmigrate:查看某個遷移文件在映射到數(shù)據(jù)庫中的時候,轉換的SQL語句。
migrations中的遷移版本和數(shù)據(jù)庫中的遷移版本對不上怎么辦?
找到哪里不一致,然后使用python manage.py --fake [版本名字],將這個版本標記為已經映射。
刪除指定app下migrations和數(shù)據(jù)庫表django_migrations中和這個app相關的版本號,然后將模型中的字段和數(shù)據(jù)庫中的字段保持一致,再使用命令python manage.py makemigrations重新生成一個初始化的遷移腳本,之后再使用命令python manage.py makemigrations --fake-initial來將這個初始化的遷移腳本標記為已經映射。以后再修改就沒有問題了。
更多關于遷移腳本的。請查看官方文檔:https://docs.djangoproject.com/en/2.0/topics/migrations/
根據(jù)已有的表自動生成模型:
在實際開發(fā)中,有些時候可能數(shù)據(jù)庫已經存在了。如果我們用Django來開發(fā)一個網站,讀取的是之前已經存在的數(shù)據(jù)庫中的數(shù)據(jù)。那么該如何將模型與數(shù)據(jù)庫中的表映射呢?根據(jù)舊的數(shù)據(jù)庫生成對應的ORM模型,需要以下幾個步驟:
Django給我們提供了一個inspectdb的命令,可以非常方便的將已經存在的表,自動的生成模型。想要使用inspectdb自動將表生成模型。首先需要在settings.py中配置好數(shù)據(jù)庫相關信息。不然就找不到數(shù)據(jù)庫。示例代碼如下:
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表:
那么通過python manage.py inspectdb,就會將表轉換為模型后的代碼,顯示在終端:
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...中使用。
如果只是想要轉換一個表為模型。那么可以指定表的名字。示例命令如下:
python manage.py inspectdb article_article > models.py
修正模型:新生成的ORM模型有些地方可能不太適合使用。比如模型的名字,表之間的關系等等。那么以下選項還需要重新配置一下:
模型名:自動生成的模型,是根據(jù)表的名字生成的,可能不是你想要的。這時候模型的名字你可以改成任何你想要的。
模型所屬app:根據(jù)自己的需要,將相應的模型放在對應的app中。放在同一個app中也是沒有任何問題的。只是不方便管理。
模型外鍵引用:將所有使用ForeignKey的地方,模型引用都改成字符串。這樣不會產生模型順序的問題。另外,如果引用的模型已經移動到其他的app中了,那么還要加上這個app的前綴。
讓Django管理模型:將Meta下的managed=False刪掉,如果保留這個,那么以后這個模型有任何的修改,使用migrate都不會映射到數(shù)據(jù)庫中。
當有多對多的時候,應該也要修正模型。將中間表注視了,然后使用ManyToManyField來實現(xiàn)多對多。并且,使用ManyToManyField生成的中間表的名字可能和數(shù)據(jù)庫中那個中間表的名字不一致,這時候肯定就不能正常連接了。那么可以通過db_table來指定中間表的名字。示例代碼如下:
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)在已經存在的表的名字叫做:article_tag
# 可以使用db_table,指定中間表的名字
tags=models.ManyToManyField("Tag",db_table='article_tag')
?
classMeta:
db_table='article'
表名:切記不要修改表的名字。不然映射到數(shù)據(jù)庫中,會發(fā)生找不到對應表的錯誤。
執(zhí)行命令python manage.py makemigrations生成初始化的遷移腳本。方便后面通過ORM來管理表。這時候還需要執(zhí)行命令python manage.py migrate --fake-initial,因為如果不使用--fake-initial,那么會將遷移腳本會映射到數(shù)據(jù)庫中。這時候遷移腳本會新創(chuàng)建表,而這個表之前是已經存在了的,所以肯定會報錯。此時我們只要將這個0001-initial的狀態(tài)修改為已經映射,而不真正執(zhí)行映射,下次再migrate的時候,就會忽略他。
將Django的核心表映射到數(shù)據(jù)庫中:Django中還有一些核心的表也是需要創(chuàng)建的。不然有些功能是用不了的。比如auth相關表。如果這個數(shù)據(jù)庫之前就是使用Django開發(fā)的,那么這些表就已經存在了??梢圆挥霉芰?。如果之前這個數(shù)據(jù)庫不是使用Django開發(fā)的,那么應該使用migrate命令將Django中的核心模型映射到數(shù)據(jù)庫中。