前言
我們通常做查詢操作的時候,都是通過模型名字.objects
的方式進行操作。其實模型名字.objects是一個django.db.models.manager.Manager
對象,而Manager
這個類是一個“空殼”的類,他本身是沒有任何的屬性和方法的。他的方法全部都是通過Python動態添加的方式,從QuerySet
類中拷貝過來的。示例圖如下:
所以我們如果想要學習ORM模型的查找操作,必須首先要學會QuerySet上的一些API的使用
QuerySet 21個常用的API
filter
filter:將滿足條件的數據提取出來,返回一個新的QuerySet。具體詳情可查看這篇:https://www.cnblogs.com/jiakecong/p/14780601.html
exclude
exclude:排除滿足條件的數據,返回一個新的QuerySet。示例代碼如下:
Article.objects.exclude(title__contains='hello')
以上代碼的意思是提取那些標題不包含hello
的圖書。
annotate
annotate:給QuerySet
中的每個對象都添加一個使用查詢表達式(聚合函數、F表達式、Q表達式、Func表達式等)的新字段。示例代碼如下:
articles = Article.objects.annotate(author_age=F("author__age"))
以上代碼將在每個對象中都添加一個author__age
的字段,用來顯示這個文章的作者的年齡。
order_by
order_by:指定將查詢的結果根據某個字段進行排序。如果要倒敘排序,那么可以在這個字段的前面加一個負號。示例代碼如下:
# 根據創建的時間正序排序
articles = Article.objects.order_by("create_time")
# 根據創建的時間倒序排序
articles = Article.objects.order_by("-create_time")
# 根據作者的名字進行排序
articles = Article.objects.order_by("author__name")
# 首先根據創建的時間進行排序,如果時間相同,則根據作者的名字進行排序
articles = Article.objects.order_by("create_time",'author__name')
一定要注意的一點是,多個order_by
,會把前面排序的規則給打亂,而使用后面的排序方式。比如以下代碼:
articles = Article.objects.order_by("create_time").order_by("author__name")
他會根據作者的名字進行排序,而不是使用文章的創建時間。
values
values:用來指定在提取數據出來,需要提取哪些字段。默認情況下會把表中所有的字段全部都提取出來,可以使用values
來進行指定,並且使用了values
方法后,提取出的QuerySet
中的數據類型不是模型,而是在values
方法中指定的字段和值形成的字典:
articles = Article.objects.values("title",'content')
for article in articles:
print(article)
以上打印出來的article
是類似於{"title":"abc","content":"xxx"}
的形式。如果在values
中沒有傳遞任何參數,那么將會返回一個字典,字典中包含這個模型中所有的屬性。
如果我們想要提取的是這個模型上關聯對象的屬性,那么也是可以的,示例代碼如下:
articles = Article.objects.values('title', 'content', 'author__name')
以上將會提取author的name字段,如果我們不想要這個名字,想自定義名字,可以使用關鍵字參數,示例代碼如下:
articles = Articles.objects.values('title', 'content', authorName=F('author__name'))
注意:自定義的名字不能跟模型上本身擁有的字段一樣,比如author__name
名字改成author
那么會報錯,因為Article
模型上本身擁有一個字段叫做author
,會產生沖突
values_list
values_list:類似於values
。只不過返回的QuerySet
中,存儲的不是字典,而是元組。示例代碼如下:
articles = Article.objects.values_list("id","title")
print(articles)
那么在打印articles
后,結果為<QuerySet [(1,'abc'),(2,'xxx'),...]>
等。
如果在values_list中只有一個字段。那么你可以傳遞flat=True
,這樣返回的結果就不在是一個元組,而是整個字段的值,示例代碼如下:
articles2 = Article.objects.values_list("title",flat=True)
那么以上返回的結果是
abc
xxx
all
all:獲取這個ORM模型的QuerySet
對象。
select_related
select_related:在提取某個模型的數據的同時,也提前將相關聯的數據提取出來。比如提取文章數據,可以使用select_related
將author
信息提取出來,以后再次使用article.author
的時候就不需要再次去訪問數據庫了。可以減少數據庫查詢的次數。示例代碼如下:
article = Article.objects.get(pk=1)
>> article.author # 重新執行一次查詢語句
article = Article.objects.select_related("author").get(pk=2)
>> article.author # 不需要重新執行查詢語句了
注意:selected_related
只能用在一對多或者一對一中,不能用在多對多或者多對一中。比如可以提前獲取文章的作者,但是不能通過作者獲取這個作者的文章,或者是通過某篇文章獲取這個文章所有的標簽。
prefetch_related
prefetch_related:這個方法和select_related
非常的類似,就是在訪問多個表中的數據的時候,減少查詢的次數。這個方法是為了解決多對一和多對多的關系的查詢問題。比如要獲取標題中帶有hello字符串的文章以及他的所有標簽,示例代碼如下:
from django.db import connection
articles = Article.objects.prefetch_related("tag_set").filter(title__contains='hello')
print(articles.query) # 通過這條命令查看在底層的SQL語句
for article in articles:
print("title:",article.title)
print(article.tag_set.all())
# 通過以下代碼可以看出以上代碼執行的sql語句
for sql in connection.queries:
print(sql)
但是如果在使用article.tag_set
的時候,如果又創建了一個新的QuerySet
那么會把之前的SQL優化給破壞掉。比如以下代碼:
tags = Tag.obejcts.prefetch_related("articles")
for tag in tags:
articles = tag.articles.filter(title__contains='hello') # 因為filter方法會重新生成一個QuerySet,因此會破壞掉之前的sql優化
# 通過以下代碼,我們可以看到在使用了filter的,他的sql查詢會更多,而沒有使用filter的,只有兩次sql查詢
for sql in connection.queries:
print(sql)
那如果確實是想要在查詢的時候指定過濾條件該如何做呢,這時候我們可以使用django.db.models.Prefetch
來實現,Prefetch
這個可以提前定義好queryset
。示例代碼如下:
tags = Tag.objects.prefetch_related(Prefetch("articles",queryset=Article.objects.filter(title__contains='hello'))).all()
for tag in tags:
articles = tag.articles.all()
for article in articles:
print(article)
for sql in connection.queries:
print('='*30)
print(sql)
因為使用了Prefetch
,即使在查詢文章的時候使用了filter
,也只會發生兩次查詢操作
defer
defer:在一些表中,可能存在很多的字段,但是一些字段的數據量可能是比較龐大的,而此時你又不需要,比如我們在獲取文章列表的時候,文章的內容我們是不需要的,因此這時候我們就可以使用defer
來過濾掉一些字段。這個字段跟values
有點類似,只不過defer返回的不是字典,而是模型。示例代碼如下:
articles = Article.objects.defer("title")
for article in articles:
print('article.id')
defer
雖然能過濾字段,但是有些字段是不能過濾的,比如id,即使你過濾了,也會提取出來。
only
only:跟defer
類似,只不過defer
是過濾掉指定的字段,而only
是只提取指定的字段。
get
get:獲取滿足條件的數據。這個函數只能返回一條數據,並且如果給的條件有多條數據,那么這個方法會拋出MultipleObjectsReturned
錯誤,如果給的條件沒有任何數據,那么就會拋出DoesNotExit
錯誤。所以這個方法在獲取數據,只能有且只有一條。
create
create:創建一條數據,並且保存到數據庫中。這個方法相當於先用指定的模型創建一個對象,然后再調用這個對象的save方法。示例代碼如下:
article = Article(title='abc')
article.save()
# 下面這行代碼相當於以上兩行代碼
article = Article.objects.create(title='abc')
get_or_create
get_or_create:根據某個條件進行查找,如果找到了那么就返回這條數據,如果沒有查找到,那么就創建一個。示例代碼如下:
obj,created= Category.objects.get_or_create(title='默認分類')
如果有標題等於默認分類的分類,那么就會查找出來,如果沒有,則會創建並且存儲到數據庫中。這個方法的返回值是一個元組,元組的第一個參數obj
是這個對象,第二個參數created
代表是否創建的。
bulk_create
bulk_create:一次性創建多個數據。示例代碼如下:
Tag.objects.bulk_create([
Tag(name='111'),
Tag(name='222'),
])
count
獲取提取的數據的個數。如果想要知道總共有多少條數據,那么建議使用count
,而不是使用len(articles)這種。因為count在底層是使用select count(*)來實現的,這種方式比使用len函數更加的高效。
first和last
first和last:返回QuerySet
中的第一條和最后一條數據
aggregate
aggregate:使用聚合函數。具體詳情可參考這篇:https://www.cnblogs.com/jiakecong/p/14784109.html
exists
exists:判斷某個條件的數據是否存在。如果要判斷某個條件的元素是否存在,那么建議使用exists
,這比使用count
或者直接判斷QuerySet
更有效得多。示例代碼如下:
result = Book.objects.filter(name="三國演義").exists()
print(result)
distinct
distinct:去除掉那些重復的數據。這個方法如果底層數據庫用的是MySQL
,那么不能傳遞任何的參數。比如想要提取所有銷售的價格超過80元的圖書,並且刪掉那些重復的,那么可以使用distinct
來幫我們實現,示例代碼如下:
books = Book.objects.filter(bookorder__price__gte=80).distinct()
需要注意的是,如果在distinct
之前使用了order_by
,那么因為order_by
會提取order_by
中指定的字段,因此再使用distinct
就會根據多個字段來進行唯一化,所以就不會把那些重復的數據刪掉。示例代碼如下:
orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()
那么以上代碼因為使用了order_by
,即使使用了distinct
,也會把重復的book_id
提取出來。
update
update:執行更新操作,在SQL底層走的也是update
命令。比如要將所有category
為空的article
的article
字段都更新為默認的分類。示例代碼如下:
Article.objects.filter(category__isnull=True).update(category_id=3)
delete
delete:刪除所有滿足條件的數據。刪除數據的時候,要注意on_delete
指定的處理方式。
切片
切片操作:有時候我們查找數據,有可能只需要其中的一部分。那么這時候可以使用切片操作來幫我們完成。QuerySet
使用切片操作就跟列表使用切片操作是一樣的。示例代碼如下:
books = Book.objects.all()[1:3]
for book in books:
print(book)
切片操作並不是把所有數據從數據庫中提取出來再做切片操作。而是在數據庫層面使用LIMIE
和OFFSET
來幫我們完成。所以如果只需要取其中一部分的數據的時候,建議大家使用切片操作。
Django將QuerySet轉換為SQL語句去執行的五種情況
- 迭代:在遍歷
QuerySet
對象的時候,會首先先執行這個SQL語句,然后再把這個結果返回進行迭代。比如以下代碼就會轉換為SQL語句:
for book in Book.objects.all():
print(book)
- 使用步長做切片操作:
QuerySet
可以類似於列表一樣做切片操作。做切片操作本身不會執行SQL語句,但是如果如果在做切片操作的時候提供了步長,那么就會立馬執行SQL
語句。需要注意的是,做切片后不能再執行filter
方法,否則會報錯。 - 調用len函數:調用len函數用來獲取
QuerySet
中總共有多少條數據也會執行SQL語句。 - 調用list函數:調用list函數用來將一個
QuerySet
對象轉換為list對象也會立馬執行SQL語句。 - 判斷:如果對某個
QuerySet
進行判斷,也會立馬執行SQL語句。