Django4.0 聚合-聚合和其他QuerySet子句

2022-03-16 17:34 更新

filter() 和 exclude()

聚合也可以參與過濾。任何應(yīng)用于普通模型字段的 ?filter()? (或 ?exclude()?)會具有約束被認為是聚合的對象的效果。
當使用 ?annotate()? 子句,過濾器具有約束計算注解的對象的效果。比如,你可以使用查詢生成一個所有書籍的注解列表,這個列表的標題以 "Django" 開頭。

>>> from django.db.models import Avg, Count
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

當使用 ?aggregate()? 子句,過濾器將具有約束計算聚合的對象的效果。比如,你可以使用查詢生成所有標題以 "Django" 開頭的平均價格。

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

過濾注解

注解過的值也可以使用過濾器。注解的別名可以和任何其他模型字段一樣使用 ?filter()? 和 ?exclude()? 子句。

比如,要生成多名作者的書籍列表,可以發(fā)出這種查詢:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

這個查詢生成一個注解結(jié)果集,然后生成一個基于注解的過濾器。
如果你需要兩個帶有兩個獨立的過濾器的注解,你可以在任何聚合中使用 ?filter ?語句。比如,要生成一個帶有高評價書籍的作者列表:

>>> highly_rated = Count('book', filter=Q(book__rating__gte=7))
>>> Author.objects.annotate(num_books=Count('book'), highly_rated_books=highly_rated)

結(jié)果集中的每個 Author 都有 ?num_books ?和 ?highly_rated_books ?屬性。

annotate() 和 filter() 子句的順序

當開發(fā)一個涉及 ?annotate()? 和 ?filter()? 子句的復(fù)雜查詢時,要特別注意應(yīng)用于 ?QuerySet ?的子句的順序。
當一個 ?annotate()? 子句應(yīng)用于查詢,會根據(jù)查詢狀態(tài)來計算注解,直到請求的注解為止。這實際上意味著 ?filter()? 和 ?annotate()? 不是可交換的操作。
比如:

  • 出版者A有兩本評分4和5的書。
  • 出版者B有兩本評分1和4的書。
  • 出版者C有一本評分1的書。

下面就是 ?Count ?聚合的例子:

>>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 1)

兩個查詢返回出版者列表,這些出版者至少有一本評分3的書,因此排除了C。
在第一個查詢里,注解優(yōu)先于過濾器,因此過濾器沒有影響注解。?distinct=True? 用來避免一個 ?query ?bug。
第二個查詢每個發(fā)布者評分3以上的書籍數(shù)量。過濾器優(yōu)先于注解,因此過濾器約束計算注解時考慮的對象。
這里是另一個關(guān)于 Avg 聚合的例子:

>>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5)  # (1+4)/2

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 4.0)  # 4/1 (book with rating 1 excluded)

第一個查詢請求至少有一本評分3以上的書籍的出版者的書籍平均分。第二個查詢只請求評分3以上的作者書籍的平均評分。
很難憑直覺了解ORM如何將復(fù)雜的查詢集轉(zhuǎn)化為SQL查詢,因此當有疑問時,請使用 ?str(queryset.query)? 檢查SQL,并寫大量的測試。

order_by()

注解可以當做基本排序來使用。當你定義了一個 ?order_by()? 子句,你提供的聚合可以引用任何定義為查詢中 ?annotate()? 子句的一部分的別名。
比如,通過書籍的作者數(shù)量來對書籍的 ?QuerySet ?排序,你可以使用下面的查詢:

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

values()

通常,注解值會添加到每個對象上,即一個被注解的 ?QuerySet ?將會為初始 ?QuerySet ?的每個對象返回一個結(jié)果集。然而,當使用 ?values()? 子句來對結(jié)果集進行約束時,生成注解值的方法會稍有不同。不是在原始 ?QuerySet ?中對每個對象添加注解并返回,而是根據(jù)定義在 ?values()? 子句中的字段組合先對結(jié)果進行分組,再對每個單獨的分組進行注解,這個注解值是根據(jù)分組中所有的對象計算得到的。
下面是一個關(guān)于作者的查詢例子,查詢每個作者所著書的平均評分:

>>> Author.objects.annotate(average_rating=Avg('book__rating'))

這段代碼返回的是數(shù)據(jù)庫中的所有作者及其所著書的平均評分。
但是如果你使用 ?values()? 子句,結(jié)果會稍有不同:

>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

在這個例子中,作者會按名字分組,所以你只能得到不重名的作者分組的注解值。這意味著如果你有兩個作者同名,那么他們原本各自的查詢結(jié)果將被合并到同一個結(jié)果中;兩個作者的所有評分都將被計算為一個平均分。

annotate() 和 values() 的順序

和使用 ?filter()? 一樣,作用于某個查詢的 ?annotate()? 和 ?values()? 子句的順序非常重要。如果 ?values()? 子句在 ?annotate()? 之前,就會根據(jù) ?values()? 子句產(chǎn)生的分組來計算注解。
然而如果 ?annotate()? 子句在 ?values()? 之前,就會根據(jù)整個查詢集生成注解。這種情況下,?values()? 子句只能限制輸出的字段。
舉個例子,如果我們顛倒上個例子中 ?values()? 和 ?annotate()? 的順序:

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

這段代碼將為每個作者添加一個唯一注解,但只有作者姓名和 ?average_rating ?注解會返回在輸出結(jié)果中。
你應(yīng)該也會注意 ?average_rating ?已經(jīng)明確包含在返回的值列表中。這是必需的,因為 ?values()? 和 ?annotate()? 子句的順序。
如果 ?values()? 子句在 ?annotate()? 子句之前,任何注解將自動添加在結(jié)果集中。然而,如果 ?values()? 子句應(yīng)用在 ?annotate()? 子句之后,則需要顯式包含聚合列。

與 order_by() 交互

在選擇輸出數(shù)據(jù)時使用查詢集的 ?order_by()? 部分中提到的字段,即使在 ?values()? 調(diào)用中沒有另外指定它們也是如此。 這些額外的字段用于將“?like?”的結(jié)果組合在一起,它們可以使原本相同的結(jié)果行看起來是分開的。 尤其是在計算事物時,這一點會出現(xiàn)。

舉個例子,假設(shè)你有這樣的模型:

from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()

如果您想計算每個不同數(shù)據(jù)值在有序查詢集中出現(xiàn)的次數(shù),您可以試試這個:

items = Item.objects.order_by('name')
# Warning: not quite correct!
items.values('data').annotate(Count('id'))

它將按 ?Item ?對象的公共數(shù)據(jù)值對它們進行分組,然后計算每組中 ?id ?值的數(shù)量。 除非它不會完全工作。 按名稱排序也將在分組中發(fā)揮作用,因此此查詢將按不同的(數(shù)據(jù),名稱)對分組,這不是您想要的。 相反,您應(yīng)該構(gòu)造這個查詢集:

items.values('data').annotate(Count('id')).order_by()

清除任何查詢中的排序。你也可以通過 ?data ?排序,沒有任何有害影響,因為它已經(jīng)在查詢中發(fā)揮了作用。

這個行為與 ?distinct()? 的行為相同,一般規(guī)則是一樣的:通常情況下,你不希望額外的列在結(jié)果中發(fā)揮作用,因此要清除排序,或者至少確保它只限于您在 ?values()? 調(diào)用中選擇的那些字段。

聚合注解

你也可以在注解結(jié)果上生成聚合。當你定義 ?aggregate()? 子句時,你提供的聚合可以引用任何定義在查詢中 ?annotate()? 子句的別名。
比如,如果你想計算每本書的平均作者數(shù),首先使用作者數(shù)注解書籍集合,然后引用注解字段聚合作者數(shù):

>>> from django.db.models import Avg, Count
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號