<!--
來源:http://python.usyiyi.cn/
-->
# 聚合 #
Django數據庫抽象API描述了使用Django查詢來增刪查改單個對象的方法。然而,你有時候會想要獲取從一組對象導出的值或者是聚合一組對象。這份指南描述了通過Django查詢來生成和返回聚合值的方法。
整篇指南我們都將引用以下模型。這些模型用來記錄多個網上書店的庫存。
```
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class Publisher(models.Model):
name = models.CharField(max_length=300)
num_awards = models.IntegerField()
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
rating = models.FloatField()
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
pubdate = models.DateField()
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
registered_users = models.PositiveIntegerField()
```
## 速查表 ##
急著用嗎?以下是在上述模型的基礎上,進行一般的聚合查詢的方法:
```
# Total number of books.
>>> Book.objects.count()
2452
# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73
# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}
# Cost per page
>>> Book.objects.all().aggregate(
... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653}
# All the following queries involve traversing the Book<->Publisher
# many-to-many relationship backward
# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73
# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323
```
## 在查詢集上生成聚合 ##
Django提供了兩種生成聚合的方法。第一種方法是從整個查詢集生成統計值。比如,你想要計算所有在售書的平均價錢。Django的查詢語法提供了一種方式描述所有圖書的集合。
```
>>> Book.objects.all()
```
我們需要在QuerySet.對象上計算出總價格。這可以通過在QuerySet后面附加aggregate() 子句來完成。
```
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
```
all()在這里是多余的,所以可以簡化為:
```
>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}
```
aggregate()子句的參數描述了我們想要計算的聚合值,在這個例子中,是Book 模型中price字段的平均值。查詢集參考中列出了聚合函數的列表。
aggregate()是QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它。
```
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
```
如果你希望生成不止一個聚合,你可以向aggregate()子句中添加另一個參數。所以,如果你也想知道所有圖書價格的最大值和最小值,可以這樣查詢:
```
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
```
## 為查詢集的每一項生成聚合 ##
生成匯總值的第二種方法,是為QuerySet中每一個對象都生成一個獨立的匯總值。比如,如果你在檢索一列圖書,你可能想知道有多少作者寫了每一本書。每本書和作者是多對多的關系。我們想要匯總QuerySet.中每本書里的這種關系。
逐個對象的匯總結果可以由annotate()子句生成。當annotate()子句被指定之后,QuerySet中的每個對象都會被注上特定的值。
這些注解的語法都和aggregate()子句所使用的相同。annotate()的每個參數都描述了將要被計算的聚合。比如,給圖書添加作者數量的注解:
```
# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1
```
和使用 aggregate()一樣,注解的名稱也根據聚合函式的名稱和聚合字段的名稱得到的。你可以在指定注解時,為默認名稱提供一個別名:
```
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
```
與 aggregate() 不同的是, annotate() 不是一個終止子句。annotate()子句的返回結果是一個查詢集 (QuerySet);這個 QuerySet可以用任何QuerySet方法進行修改,包括 filter(), order_by(), 甚至是再次應用annotate()。
> 有任何疑問的話,請檢查 SQL query!
>
> 要想弄清楚你的查詢到底發生了什么,可以考慮檢查你QuerySet的 query 屬性。
>
> 例如,在annotate() 中混入多個聚合將會得出錯誤的結果,因為多個表上做了交叉連接,導致了多余的行聚合。
## 連接和聚合 ##
至此,我們已經了解了作用于單種模型實例的聚合操作, 但是有時,你也想對所查詢對象的關聯對象進行聚合。
在聚合函式中指定聚合字段時,Django 允許你使用同樣的 雙下劃線 表示關聯關系,然后 Django 在就會處理要讀取的關聯表,并得到關聯對象的聚合。
例如,要得到每個書店的價格區別,可以使用如下注解:
```
>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
```
這段代碼告訴 Django 獲取書店模型,并連接(通過多對多關系)圖書模型,然后對每本書的價格進行聚合,得出最小值和最大值。
同樣的規則也用于 aggregate() 子句。如果你想知道所有書店中最便宜的書和最貴的書價格分別是多少:
```
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
```
關系鏈可以按你的要求一直延伸。 例如,想得到所有作者當中最小的年齡是多少,就可以這樣寫:
```
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
```
## 遵循反向關系 ##
和 跨關系查找的方法類似,作用在你所查詢的模型的關聯模型或者字段上的聚合和注解可以遍歷"反轉"關系。關聯模型的小寫名稱和雙下劃線也用在這里。
例如,我們可以查詢所有出版商,并注上它們一共出了多少本書(注意我們如何用 'book'指定Publisher -> Book 的外鍵反轉關系):
```
>>> from django.db.models import Count, Min, Sum, Avg
>>> Publisher.objects.annotate(Count('book'))
```
QuerySet結果中的每一個Publisher都會包含一個額外的屬性叫做book__count。
我們也可以按照每個出版商,查詢所有圖書中最舊的那本:
```
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
```
(返回的字典會包含一個鍵叫做 'oldest_pubdate'。如果沒有指定這樣的別名,它會更長一些,像 'book__pubdate__min'。)
這不僅僅可以應用掛在外鍵上面。還可以用到多對多關系上。例如,我們可以查詢每個作者,注上它寫的所有書(以及合著的書)一共有多少頁(注意我們如何使用 'book'來指定Author -> Book的多對多的反轉關系):
```
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
```
(每個返回的QuerySet中的Author 都有一個額外的屬性叫做total_pages。如果沒有指定這樣的別名,它會更長一些,像 book__pages__sum。)
或者查詢所有圖書的平均評分,這些圖書由我們存檔過的作者所寫:
```
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
```
(返回的字典會包含一個鍵叫做'average__rating'。如果沒有指定這樣的別名,它會更長一些,像'book__rating__avg'。)
聚合和其他查詢集子句
## filter() 和 exclude() ##
聚合也可以在過濾器中使用。 作用于普通模型字段的任何 filter()(或 exclude()) 都會對聚合涉及的對象進行限制。
使用annotate() 子句時,過濾器有限制注解對象的作用。例如,你想得到以 "Django" 為書名開頭的圖書作者的總數:
```
>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
```
使用aggregate()子句時,過濾器有限制聚合對象的作用。例如,你可以算出所有以 "Django" 為書名開頭的圖書平均價格:
```
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
```
## 對注解過濾 ##
注解值也可以被過濾。 像使用其他模型字段一樣,注解也可以在filter()和exclude() 子句中使用別名。
例如,要得到不止一個作者的圖書,可以用:
```
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
```
這個查詢首先生成一個注解結果,然后再生成一個作用于注解上的過濾器。
## annotate() 的順序 ##
編寫一個包含 annotate() 和 filter() 子句的復雜查詢時,要特別注意作用于 QuerySet的子句的順序。
當一個annotate() 子句作用于某個查詢時,要根據查詢的狀態才能得出注解值,而狀態由 annotate() 位置所決定。以這就導致filter() 和 annotate() 不能交換順序,下面兩個查詢就是不同的:
```
>>> Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
```
另一個查詢:
```
>>> Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
```
兩個查詢都返回了至少出版了一本好書(評分大于 3 分)的出版商。 但是第一個查詢的注解包含其該出版商發行的所有圖書的總數;而第二個查詢的注解只包含出版過好書的出版商的所發行的圖書總數。 在第一個查詢中,注解在過濾器之前,所以過濾器對注解沒有影響。 在第二個查詢中,過濾器在注解之前,所以,在計算注解值時,過濾器就限制了參與運算的對象的范圍。
## order_by() ##
注解可以用來做為排序項。 在你定義 order_by() 子句時,你提供的聚合可以引用定義的任何別名做為查詢中 annotate()子句的一部分。
例如,根據一本圖書作者數量的多少對查詢集 QuerySet進行排序:
```
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
```
## values() ##
通常,注解會添加到每個對象上 —— 一個被注解的QuerySet會為初始QuerySet的每個對象返回一個結果集。但是,如果使用了values()子句,它就會限制結果中列的范圍,對注解賦值的方法就會完全不同。不是在原始的 QuerySet返回結果中對每個對象中添加注解,而是根據定義在values() 子句中的字段組合對先結果進行唯一的分組,再根據每個分組算出注解值, 這個注解值是根據分組中所有的成員計算而得的:
例如,考慮一個關于作者的查詢,查詢出每個作者所寫的書的平均評分:
```
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
```
這段代碼返回的是數據庫中所有的作者以及他們所著圖書的平均評分。
但是如果你使用了values()子句,結果是完全不同的:
```
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
```
在這個例子中,作者會按名稱分組,所以你只能得到某個唯一的作者分組的注解值。 這意味著如果你有兩個作者同名,那么他們原本各自的查詢結果將被合并到同一個結果中;兩個作者的所有評分都將被計算為一個平均分。
## annotate() 的順序 ##
和使用 filter() 子句一樣,作用于某個查詢的annotate() 和 values() 子句的使用順序是非常重要的。如果values() 子句在 annotate() 之前,就會根據 values() 子句產生的分組來計算注解。
但是,如果 annotate() 子句在 values()子句之前,就會根據整個查詢集生成注解。在這種情況下,values() 子句只能限制輸出的字段范圍。
舉個例子,如果我們互換了上個例子中 values()和 annotate() 子句的順序:
```
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
```
這段代碼將給每個作者添加一個唯一的字段,但只有作者名稱和average_rating 注解會返回在輸出結果中。
你也應該注意到 average_rating 顯式地包含在返回的列表當中。之所以這么做的原因正是因為values() 和 annotate() 子句。
如果 values() 子句在 annotate() 子句之前,注解會被自動添加到結果集中;但是,如果 values() 子句作用于annotate() 子句之后,你需要顯式地包含聚合列。
## 與默認排序或order_by()交互 ##
在查詢集中的order_by() 部分(或是在模型中默認定義的排序項) 會在選擇輸出數據時被用到,即使這些字段沒有在values() 調用中被指定。這些額外的字段可以將相似的數據行分在一起,也可以讓相同的數據行相分離。在做計數時,就會表現地格外明顯:
通過例子中的方法,假設有一個這樣的模型:
```
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=10)
data = models.IntegerField()
class Meta:
ordering = ["name"]
```
關鍵的部分就是在模型默認排序項中設置的name字段。如果你想知道每個非重復的data值出現的次數,可以這樣寫:
```
# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))
```
...這部分代碼想通過使用它們公共的 data 值來分組 Item對象,然后在每個分組中得到 id 值的總數。但是上面那樣做是行不通的。這是因為默認排序項中的 name也是一個分組項,所以這個查詢會根據非重復的 (data, name) 進行分組,而這并不是你本來想要的結果。所以,你應該這樣改寫:
```
Item.objects.values("data").annotate(Count("id")).order_by()
```
...這樣就清空了查詢中的所有排序項。 你也可以在其中使用 data ,這樣并不會有副作用,這是因為查詢分組中只有這么一個角色了。
這個行為與查詢集文檔中提到的 distinct() 一樣,而且生成規則也一樣:一般情況下,你不想在結果中由額外的字段扮演這個角色,那就清空排序項,或是至少保證它僅能訪問 values()中的字段。
> 注意
>
> 你可能想知道為什么 Django 不刪除與你無關的列。主要原因就是要保證使用 distinct()和其他方法的一致性。Django 永遠不會 刪除你所指定的排序限制(我們不能改動那些方法的行為,因為這會違背 API stability 原則)。
## 聚合注解 ##
你也可以在注解的結果上生成聚合。 當你定義一個 aggregate() 子句時,你提供的聚合會引用定義的任何別名做為查詢中 annotate() 子句的一部分。
例如,如果你想計算每本書平均有幾個作者,你先用作者總數注解圖書集,然后再聚合作者總數,引入注解字段:
```
>>> from django.db.models import Count, Avg
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}
```
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架