21.QuerySetAPI

 

QuerySet API:

咱們一般作查詢操做的時候,都是經過模型名字.objects的方式進行操做。其實模型名字.objects是一個django.db.models.manager.Manager對象,而Manager這個類是一個「空殼」的類,他自己是沒有任何的屬性和方法的。他的方法所有都是經過Python動態添加的方式,從QuerySet類中拷貝過來的。javascript

模型.objects:

這個對象是django.db.models.manager.Manager的對象,這個類是一個空殼類,他上面的全部方法都是從QuerySet這個類上面拷貝過來的。所以咱們只要學會了QuerySet,這個objects也就知道該如何使用了。
Manager源碼解析:html

class_name = "BaseManagerFromQuerySet"

class_dict = {
    '_queryset_class': QuerySet
}

class_dict.update(cls._get_queryset_methods(QuerySet))

# type動態的時候建立類
# 第一個參數是用來指定建立的類的名字。建立的類名是:BaseManagerFromQuerySet
# 第二個參數是用來指定這個類的父類。
# 第三個參數是用來指定這個類的一些屬性和方法
return type(class_name,(cls,),class_dict)

_get_queryset_methods:這個方法就是將QuerySet中的一些方法拷貝出來

filter

將知足條件的數據提取出來,返回一個新的QuerySet。具體的filter能夠提供什麼條件查詢java

exclude

排除知足條件的數據,返回一個新的QuerySet。示例代碼以下:python

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


以上代碼的意思是提取那些標題不包含hello的圖書。ios

annotate

給QuerySet中的每一個對象都添加一個使用查詢表達式(聚合函數、F表達式、Q表達式、Func表達式等)的新字段。示例代碼以下:web

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


以上代碼將在每一個對象中都添加一個author__name的字段,用來顯示這個文章的做者的年齡。sql

aggregate

使用聚合函數。數據庫

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,會把前面排序的規則給打亂,而使用後面的排序方式。好比如下代碼:django

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

他會根據做者的名字進行排序,而不是使用文章的建立時間。
固然,也能夠在模型定義的在Meta類中定義ordering來指定默認的排序方式。示例代碼以下:api

class Meta:
        db_table = 'book_order'
        ordering = ['create_time','-price']

還能夠根據annotate定義的字段進行排序。好比要實現圖書的銷量進行排序,那麼示例代碼以下:

books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
    for book in books:
        print('%s/%s'%(book.name,book.order_nums))

values

values的返回值一樣也是一個QuerySet對象,可是這個QuetySet中裝的就不是模型了,而是一個一個的dict字典.

若是咱們想要提取的是這個模型上關聯的對象的屬性,那麼也是能夠的,查找順序跟filter的用法是同樣的.示例代碼以下:

books = Book.objects.values("id","name","author__name")


以上將會提取author模型下的name字段,若是咱們不想要這個名字,想要更改一個名字,那麼可使用關鍵字參數.示例代碼以下:

books = Book.objects.values("id","name",author_name=F("author__name"))


自定義的名字,不能和模型上自己擁有的字段同樣,好比以上author_name若是取名叫作author就會報錯,由於Book上自己就擁有一個字段叫作author.

values中,也可使用聚合函數來造成一個新的字段.好比我想要獲取每本圖書的銷量,那麼示例代碼以下:

books = Book.objects.values("id","name",order_nums=Count("bookorder"))

若是調用values方法的時候,沒有傳遞任何的參數,那麼會獲取這個模型上的全部字段以及對應的值造成的字典.示例代碼以下:

books = Book.objects.values()

values_list

d跟values是同樣的做用.只不過這個方法返回的QuerySet中,裝的不是字典,而是元組,示例代碼以下:

books = Book.objects.values_list("id","name")


那麼以上代碼的返回結果是:

(1,"西遊記")

若是給values_list只指定一個字段,那麼咱們能夠指定flat=True,這樣返回回來的結果就不在是一個元組,而是這個字段的值,示例代碼以下:

books = Book.objects.values_list("name",flat=True)


那麼以上返回的結果是:

`三國演義`


必定要注意的是,flat只能用在只有一個字段的狀況下,不然就會報錯.

all方法

查詢模型下的全部數據,返回一個QuerySet對象,這個QuerySet對象沒有通過任何的修改(好比過濾等)

在查找某個表的數據的時候,能夠一次性把相關聯的其餘表的數據都提取出來,這樣能夠在之後訪問相關聯的表的數據的時候,不用再次查找數據庫,能夠節省一些開銷.示例代碼以下:

books = Book.objects.select_related("author","publisher")
for book in books:
    print(book.author.name)
    # 由於在提取book的時候,使用了select_related,那麼之後在訪問book.author的時候,不會再次向數據庫從新發起查詢


注意:這個方法只能用在外鍵關聯的對象上,對於那種多對多,或者多對一的狀況不能使用它在實現,而應該使用prefetch_related來實現.

這個方法相似與select_related方法,也是用來查詢語句的時候,提早將查找的數據提取出來.不過這個方法是用來解決多對多,非外鍵的的狀況.這個方法會產生兩個查詢語句.因此,若是查詢外鍵關聯的模型就用select_related,若是查詢的是多對多或者非外鍵關聯的狀況,就用prefetch_related.示例代碼以下:

books = Book.objects.prefetch_related("bookorder_set")


須要注意的是:在使用prefetch_related查找出來的boororder_set,建議不要再對它進行任何操做,好比filter,否則又會產生N多查詢語句,影響查詢的性能.好比如下的代碼是不對的:

books = Book.objects.prefetch_related("bookorder_set")
for book in books:
    print(book.name)
    # 這個地方若是對bookorder_set進行了操做,那麼就又會產生新的sql語句,前面的prefetch_related就至關於白作了
    oredrs = book.bookorder_set.fliter(price__gte=90)
    for order in orders:
        print(order.id)


那麼若是確實是想要對預先查找的集合進行操做,那麼咱們可使用django.db.models.Prefetch來完成,示例代碼以下:

# 先使用Prefetch把查找的條件寫好,在放到prefetch_related中
from django.db.models import Prefetch
prefetch = Prefetch("bookorder_set",queryset=Bookorder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch)
for book in books:
    print(book.name)
    orders = book.bookorder_set.all()
    for order in orders:
        print(order.id)

defer和only

這兩個方法都會返回一個QuerySet,而且這個QuerySet中裝的都是模型,而不是字段
1. defer: 這個方法用來告訴ORM,在查詢某個模型的時候,過濾到某些字段.
2. only: 這個方法用來告訴ONR,在查詢某個模型的時候,只提取某些字段.
注意: 使用defer了的字段,之後在使用這個字段,會從新發起一次請求,所以要謹慎操做,only也同理.示例代碼以下:

articles = list(Article.objects.defer("title"))
for article in articles:
    # 由於在上面提取的時候過濾了title
    # 這個地方從新獲取title,將從新向數據庫中進行一次查找操做
    print(article.title)
for sql in connection.queries:
    print('='*30)
    print(sql)

get

獲取知足條件的數據,返回的是具體的模型。這個函數只能返回一條數據,而且若是給的條件有多條數據,那麼這個方法會拋出MultipleObjectsReturned錯誤,若是給的條件沒有任何數據,那麼就會拋出DoesNotExit錯誤。因此這個方法在獲取數據的只能,只能有且只有一條。

create

建立一條數據,而且保存到數據庫中。這個方法至關於先用指定的模型建立一個對象,而後再調用這個對象的save方法。示例代碼以下:

article = Article(title='abc')
article.save()

# 下面這行代碼至關於以上兩行代碼
article = Article.objects.create(title='abc')

bulk_create

一次性建立多個數據,無論多少條數據,一條SQL語句解決

get_or_create

若是給定的條件有數據,那麼就會把這個數據直接提取出來.若是給定的條件沒有數據,那麼就會先建立數據,而後再把數據返回回來.

count

count:獲取提取的數據的個數。若是想要知道總共有多少條數據,那麼建議使用count,而不是使用len(articles)這種。由於count在底層是使用select count(*)來實現的,這種方式比使用len函數更加的高效。

first和last

返回QuerySet中的第一條和最後一條數據,返回值是數據的模型.

exisit

exists:判斷某個條件的數據是否存在。若是要判斷某個條件的元素是否存在,那麼建議使用exists,這比使用count或者直接判斷QuerySet更有效得多。示例代碼以下:

if Article.objects.filter(title__contains='hello').exists():
    print(True)
比使用count更高效:
if Article.objects.filter(title__contains='hello').count() > 0:
    print(True)
也比直接判斷QuerySet更高效:
if Article.objects.filter(title__contains='hello'):
    print(True)

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)


注意這個方法走的是更新的邏輯。因此更新完成後保存到數據庫中不會執行save方法,所以不會更新auto_now設置的字段。

delete

delete:刪除全部知足條件的數據。刪除數據的時候,要注意on_delete指定的處理方式。

切片操做

切片操做:有時候咱們查找數據,有可能只須要其中的一部分。那麼這時候可使用切片操做來幫咱們完成。QuerySet使用切片操做就跟列表使用切片操做是同樣的。示例代碼以下:

books = Book.objects.all()[1:3]
for book in books:
    print(book)


切片操做並非把全部數據從數據庫中提取出來再作切片操做。而是在數據庫層面使用LIMIE和OFFSET來幫咱們完成。因此若是隻須要取其中一部分的數據的時候,建議你們使用切片操做。

何時Django會將QuerySet轉換爲SQL去執行:

生成一個QuerySet對象並不會立刻轉換爲SQL語句去執行。
好比咱們獲取Book表下全部的圖書:

books = Book.objects.all()
print(connection.queries)


咱們能夠看到在打印connection.quries的時候打印的是一個空的列表。說明上面的QuerySet並無真正的執行。
在如下狀況下QuerySet會被轉換爲SQL語句執行:

  1. 迭代:在遍歷QuerySet對象的時候,會首先先執行這個SQL語句,而後再把這個結果返回進行迭代。好比如下代碼就會轉換爲SQL語句:
    for book in Book.objects.all():
         print(book)
  2. 使用步長切片操做:QuerySet能夠相似於列表同樣作切片操做。作切片操做自己不會執行SQL語句,可是若是若是在作切片操做的時候提供了步長,那麼就會立馬執行SQL語句。須要注意的是,作切片後不能再執行filter方法,不然會報錯。

  3. 調用len函數:調用len函數用來獲取QuerySet中總共有多少條數據也會執行SQL語句。

  4. 調用list函數:調用list函數用來將一個QuerySet對象轉換爲list對象也會立馬執行SQL語句。

  5. 判斷:若是對某個QuerySet進行判斷,也會立馬執行SQL語句。

相關文章
相關標籤/搜索