Django 數據庫抽象 API 描述了如何創建、檢索、更新和刪除獨立的對象。但是,有時你會需要處理一些有關對象的集合的統計。本文描述如何使用 Django 查詢來處理統計。
本文我們將使用以下模型。這些模型用於在線書店圖書清單:
class Author(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() friends = models.ManyToManyField('self', blank=True)
class Publisher(models.Model): name = models.CharField(max_length=300) num_awards = models.IntegerField() class Book(models.Model): isbn = models.CharField(max_length=9) 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)
生成整個查詢集的統計
Django 提供兩種方法來產生統計。
第一種方法是產生整個 查詢集 的統計。假設我們要統計所有書的平均價格。 Djnago 中查詢所有書的語句為:
>>> Book.objects.all()
在這個語句后加上一個 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() 是一個 查詢集 的未端子句,調用后會返回一個由名稱-值配對組成的字典。名稱是指統計的名稱,值就是統計的值。名稱由字段名稱配雙下划線加上函數名自動組成。如果你想手動指定統計名稱,可以象下例在統計子句中定義:
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
如果你想要生成多個統計,那么只要在統計子句后加上另外的統計子句就可以了。例如,如果要計算所有書價中最高價和最低價,可以這樣寫:
>>> from django.db.models import Avg, Max, Min, Count, Sum
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
生成查詢集中每一個項目的統計
第二種方法是為 查詢集 中每個獨立的對象生成統計。例如,當你檢索一個書單時,可能想知道每本書有幾個作者。每本書與每個作者之間是一個多對多的關系,我們要為每本書總結這個關系。
要產生每個對象的統計可以使用 annotate() 子句。當定義一個 annotate() 子句后, 查詢集 中的每個對象就可以與特定值關聯,相當於每個對象有一個 “注釋”。
這種注釋的語法與 aggregate() 相同。 annotate() 的每個參數代表一個統計。例如,要計算每本書的作者人數:
生成查詢集中每一個項目的統計
第二種方法是為 查詢集 中每個獨立的對象生成統計。例如,當你檢索一個書單時,可能想知道每本書有幾個作者。每本書與每個作者之間是一個多對多的關系,我們要為每本書總結這個關系。
要產生每個對象的統計可以使用 annotate() 子句。當定義一個 annotate() 子句后, 查詢集 中的每個對象就可以與特定值關聯,相當於每個對象有一個 “注釋”。
這種注釋的語法與 aggregate() 相同。 annotate() 的每個參數代表一個統計。例如,要計算每本書的作者人數:
>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
聯合的深度是無限的。例如,要統計所有書的作者的最小年齡,你可以這樣:
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
統計和其他查詢子句
filter() 和 exclude()¶
在過濾器中也可以使用統計。任何用於一般模型的 filter() (或 exclude() )也可與統計聯用。
當與 annotate() 子句聯用時,過濾器作用於被統計的對象上。例如要統計書名以 "Django" 開頭的書:
>>> 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() 和 filter() 子句的順序
當使用同時包含 annotate() 和 filter() 子句的復雜查詢時,要特別小心兩種子句的順序。
當一個 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.0 )的出版商。但是,第一個查詢中的統計會提供出版商的所有書的數量;第二個查詢中的統計只返回好書的數量。第一個查詢中統計先於過濾器,所以過濾器對統計沒有作用。而第二個查詢過濾器先於統計,所以統計的對象是已經過濾過的。
order_by()
統計可以作為排序的基礎。當你定義一個 order_by 子句時,可以引用 annotate() 子句中的統計。
例如,要依據書的作者人數進行排序,可以這樣:
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
values()
通常,統計會針對 查詢集 中每一個對象返回一個結果。但是,當使用 values 子句來約束要統計的列時,返回的結果會有所不同。原先統計結果中,統計字段的值相同的項會分組合並統計。
例如,要統計每個作者各自所寫的書的平均評分:
>>> Author.objects.annotate(average_rating=Avg('book__rating'))
返回的結果會包含每一個作者及其所寫的書的平均計分。
但是,如果使用 values() 子句,返回的結果會有所不同:
Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
這個例子中會把作者按名字分組統計,返回的結果中不會有重復的作者名字。名字相同的作者在統計中會作為同一個作者來統計,同名作者所寫的書的評分會合並為一個作者的書來統計。
annotate() 和 values() 子句的順序
當使用 filter() 子句時, annotate() 和 values() 子句的順序是非常重要的。如果 values() 子句先於 annotate() 子句,會按照前文所述的方式統計。
但是,如果 annotate() 子句先於 values() 子句,那么統計會作用於整個查詢集,而 values() 子句只約束統計輸出的字段。
例如,如果我們把前一個例子中的 values() 和 annotate() 子句調換順序:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
這個例子會為每一個作者生成唯一的結果。但是在輸了的數據中只會包含作者名和 average_rating 的統計。
你可以注意到 average_rating 在例子中顯示地定義了。在 annotate() 和 values() 子句的順序處於這種情況是必須顯式定義。
如果 values() 子句先於 annotate() 子句,那么任何統計會自動添加到輸出結果中。但是 values() 子句在 annotate() 子句之后,那么必須顯式定義統計列。
缺省排序或 order_by() 子句的副作用
一個查詢集中 order_by() 子句中的字段(或一個模型中缺省排序字段)會對輸了數據產生影響,即使在 values() 中沒有這些字段的定義時也同樣會影響。這些特殊的字段會影響統計結果,這種情況在計數統計時尤為明顯。
假設有一個這樣的模型:
class Item(models.Model): name = models.CharField(max_length=10) data = models.IntegerField() class Meta: ordering = ["name"] Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
這個例子中會把作者按名字分組統計,返回的結果中不會有重復的作者名字。名字相同的作者在統計中會作為同一個作者來統計,同名作者所寫的書的評分會合並為一個作者的書來統計。
annotate() 和 values() 子句的順序
當使用 filter() 子句時, annotate() 和 values() 子句的順序是非常重要的。如果 values() 子句先於 annotate() 子句,會按照前文所述的方式統計。
但是,如果 annotate() 子句先於 values() 子句,那么統計會作用於整個查詢集,而 values() 子句只約束統計輸出的字段。
例如,如果我們把前一個例子中的 values() 和 annotate() 子句調換順序:
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')
這個例子會為每一個作者生成唯一的結果。但是在輸了的數據中只會包含作者名和 average_rating 的統計。
你可以注意到 average_rating 在例子中顯示地定義了。在 annotate() 和 values() 子句的順序處於這種情況是必須顯式定義。
如果 values() 子句先於 annotate() 子句,那么任何統計會自動添加到輸出結果中。但是 values() 子句在 annotate() 子句之后,那么必須顯式定義統計列。
缺省排序或 order_by() 子句的副作用
一個查詢集中 order_by() 子句中的字段(或一個模型中缺省排序字段)會對輸了數據產生影響,即使在 values() 中沒有這些字段的定義時也同樣會影響。這些特殊的字段會影響統計結果,這種情況在計數統計時尤為明顯。
假設有一個這樣的模型:
class Item(models.Model): name = models.CharField(max_length=10) data = models.IntegerField() class Meta: ordering = ["name"]