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

2022-03-16 17:34 更新

filter() 和 exclude()

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

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

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

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

過濾注解

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

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

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

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

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

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

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

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

  • 出版者A有兩本評(píng)分4和5的書。
  • 出版者B有兩本評(píng)分1和4的書。
  • 出版者C有一本評(píng)分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)

兩個(gè)查詢返回出版者列表,這些出版者至少有一本評(píng)分3的書,因此排除了C。
在第一個(gè)查詢里,注解優(yōu)先于過濾器,因此過濾器沒有影響注解。?distinct=True? 用來避免一個(gè) ?query ?bug。
第二個(gè)查詢每個(gè)發(fā)布者評(píng)分3以上的書籍?dāng)?shù)量。過濾器優(yōu)先于注解,因此過濾器約束計(jì)算注解時(shí)考慮的對(duì)象。
這里是另一個(gè)關(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)

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

order_by()

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

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

values()

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

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

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

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

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

annotate() 和 values() 的順序

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

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

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

與 order_by() 交互

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

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

from django.db import models

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

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

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

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

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

清除任何查詢中的排序。你也可以通過 ?data ?排序,沒有任何有害影響,因?yàn)樗呀?jīng)在查詢中發(fā)揮了作用。

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

聚合注解

你也可以在注解結(jié)果上生成聚合。當(dāng)你定義 ?aggregate()? 子句時(shí),你提供的聚合可以引用任何定義在查詢中 ?annotate()? 子句的別名。
比如,如果你想計(jì)算每本書的平均作者數(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)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)