在當今根據需求而不斷調整而成的應用程序中,一般不只須要能依常規的字段,如字母順序或建立日期,來對項目進行排序,還須要按其餘某種動態數據對項目進行排序。Djngo聚合就能知足這些要求。python
如下面的Model爲例git
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()
# books總數量. >>> Book.objects.count() 2452 # Total number of books with publisher=BaloneyPress >>> Book.objects.filter(publisher__name='BaloneyPress').count() 73 # books的平均price. >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} # books的最大price. >>> from django.db.models import Max >>> Book.objects.all().aggregate(Max('price')) {'price__max': Decimal('81.20')} # All the following queries involve traversing the Book<->Publisher # many-to-many relationship backward # 爲每一個publisher添加個num_books屬性,即每一個pulisher出版的book的數量. >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books=Count('book')) >>> pubs [<Publisher BaloneyPress>, <Publisher SalamiPress>, ...] >>> pubs[0].num_books 73 # 根據num_book屬性排序. >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5] >>> pubs[0].num_books 1323
Django有兩種方法來生成聚合。第一種方法是爲整個QuerySet生成聚合值,例如爲所有的books生成price的平均值:django
>>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35}
能夠簡略爲:ide
>>> Book.objects.aggregate(Avg('price')) {'price__avg': 34.35}
函數aggregate()的參數是一系列聚合函數aggregate functions:函數
Avgui
返回平均值對象
Count
class Count(field, distinct=False)blog
返回計數。當參數distinct=True時,返回unique的對象數目。排序
Maxip
返回最大值
Min
返回最小值.
StdDev
class StdDev(field, sample=False)
返回標準誤差
有一個參數sample
默認狀況下sample=False,返回整體標準誤差,若是sample=True,返回樣本標準誤差。
Sum
返回總值
Variance
class Variance(field, sample=False)
返回方差
有一個參數sample,默認返回整體方差,sample設爲True時返回樣本方差。
aggregate()方法被調用時,返回一個鍵值對字典,能夠指定key的名字:
>>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35}
若是你想生成多個聚合,你只須要添加另外一個參數。因此,若是咱們還想知道全部書的最高和最低的價格:
>>> 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')}
這是生成聚合值的第二種方法。好比你要檢索每本書有多少個做者。book和author是manytomany的關係,咱們能夠爲每本書總結出這種關係。
每一個對象的總結能夠用方法annotate()生成:
# 創建一個annotate QuerySet >>> from django.db.models import Count >>> q = Book.objects.annotate(Count('authors')) # 第一個對象 >>> q[0] <Book: The Definitive Guide to Django> >>> q[0].authors__count 2 # 第二個對象 >>> q[1] <Book: Practical Django Projects> >>> q[1].authors__count 1
也能夠指定生成屬性的名字:
>>> q = Book.objects.annotate(num_authors=Count('authors')) >>> q[0].num_authors 2 >>> q[1].num_authors 1
和aggregate()不一樣,annotate()的輸出是一個QuerySet。
目前爲止,咱們聚合查詢的field都屬於咱們要查詢的Model,咱們也能夠用其它Model的field來進行聚合查詢,例如:
>>> from django.db.models import Max, Min >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
這樣就能夠查詢每一個Store裏面books的價格範圍
聯合鏈的深度能夠爲所欲爲:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
經過book反向查詢publisher:
>>> 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'))
查詢每一個做者寫的書的總頁數:
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
查詢全部做者寫的書的平均rating:
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
filter() and exclude()
聚合能夠和filter和exclude一塊兒使用:
>>> from django.db.models import Count, Avg >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors')) >>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
能夠根據聚合值進行篩選:
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
編寫一個包含annotate()和filter()從句的複雜查詢時,要特別注意做用於QuerySet的從句的順序順序的不一樣,產生的意義也不一樣:
>>> 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分)的出版商的列表。可是第一個查詢的註解包含其該出版商發行的全部圖書的總數;而第二個查詢的註解只包含出版過好書的出版商的所發行的好書(評分大於3分)總數。在第一個查詢中,註解在過濾器以前,因此過濾器對註解沒有影響。在第二個查詢中,過濾器在註解以前,因此,在計算註解值時,過濾器就限制了參與運算的對象的範圍
order_by()
能夠根據聚合值進行排序:
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
一般,註解annotate是添加到每個對象上的,一個執行了註解操做的查詢集 QuerySet 所返回的結果中,每一個對象都添加了一個註解值。可是,若是使用了values()從句,它就會限制結果中列的範圍,對註解賦值的方法就會徹底不一樣。就不是在原始的 QuerySet 返回結果中對每一個對象中添加註解,而是根據定義在 values() 從句中的字段組合對先結果進行惟一的分組,再根據每一個分組算出註解值,這個註解值是根據分組中全部的成員計算而得的:
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
這樣的寫法下,QuerySet會根據name進行組合,返回的是每一個unique name的聚合值。若是有兩個做者有相同的名字,這兩個做者會被當作一個計算,他們的books會合在一塊兒。
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
位置互換後,會爲每一個author都生成一個average_rating,並且只會輸出每一個author的name和average_rating。
默認排序下使用聚合:
from django.db import models class Item(models.Model): name = models.CharField(max_length=10) data = models.IntegerField() class Meta: ordering = ["name"]
若是你想知道每一個非重複的data值出現的次數,你可能這樣寫:
# Warning: 不正確的寫法 Item.objects.values("data").annotate(Count("id"))
這部分代碼想經過使用它們公共的data值來分組Item對象,而後在每一個分組中獲得id值的總數。可是上面那樣作是行不通的。這是由於默認排序項中的name也是一個分組項,因此這個查詢會根據非重複的(data,name)進行分組,而這並非你原本想要的結果。因此,你須要這樣寫來去除默認排序的影響:
Item.objects.values("data").annotate(Count("id")).order_by()
也能夠根據annotation結果生成聚合值,例如計算每本書平均有幾個做者:
>>> from django.db.models import Count, Avg >>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors')) {'num_authors__avg': 1.66}