aggregate和annotate方法的使用場景
Django的aggregate和annotate方法屬於高級查詢方法,主要用於組合查詢,是Django高手們必需要熟練掌握的。當我們需要對查詢集(queryset)的某些字段進行計算或進行先分組再計算或排序, 我們就需要使用aggregate和annotate方法了。
假如我們有如下一個模型,其中Student與Hobby(愛好)是多對多的關系。我們想要知道所有學生的平均年齡,我們常規做法一般是利用for循環從數據庫中把符合查詢條件的student對象一個一個取出,把他們年齡相加,然后再除以總人數。當人數非常多而我們又只需要平均年齡這條信息時,把所有符合查詢條件的學生對象都載入內存后再進行計算是非常浪費資源的,效率也非常低。一個更好的方法是在數據庫層面提取查詢數據時就直接返回我們所需要的信息。因為這個查詢涉及到對整個queryset的age字段進行統計計算,此時django的聚合函數方法aggregate就可以幫我們大大提升查詢效率了[見后文]。
class Student(models.Model): name = models.CharField(max_length=20) age = models.IntegerField() hobbies = models.ManyToManyField(Hobby) class Hobby(models.Model): name = models.CharField(max_length=20)
另一個例子是統計最受學生歡迎的5個愛好,常規做法是先將所有hobby對象提取出來,載入內存。然后利用for循環統計每組愛好對應的學生人數,再構建一個新的查詢集,按每組人數從大到小進行排序。這個查詢需要根據hobby先進行分組,再統計每個愛好組里學生的數量,然后進行排序。對於這個復雜查詢, django的annotate方法一句話就可以解決問題。
aggregate()方法詳解
aggregate的中文意思是聚合, 源於SQL的聚合函數。Django的aggregate()方法作用是對一組值(比如queryset的某個字段)進行統計計算,並以字典(Dict)格式返回統計計算結果。django的aggregate方法支持的聚合操作有AVG / COUNT / MAX / MIN /SUM 等。
我們現在來看下幾組實際使用案例。使用前別忘了import Avg, Max, Min或者Sum方法哦
from django.db.models import Avg, Max, Min
# 計算學生平均年齡, 返回字典。age和avg間是雙下划線哦
Student.objects.all().aggregate(Avg('age')) { 'age__avg': 12 }
# 學生平均年齡,返回字典。all()不是必須的。
Student.objects.aggregate(Avg('age')) { 'age__avg: 12' }
# 計算學生總年齡, 返回字典。
Student.objects.aggregate(Sum('age')) { 'age__sum': 144 }
# 學生平均年齡, 設置字典的key
Student.objects.aggregate(average_age = Avg('age')) { 'average_age': 12 }
# 學生最大年齡,返回字典
Student.objects.aggregate(Max('age')) { 'age__max': 12 }
# 同時獲取學生年齡均值, 最大值和最小值, 返回字典
Student.objects.aggregate(Avg('age‘), Max('age‘), Min('age‘)) { 'age__avg': 12, 'age__max': 18, 'age__min': 6, }
# 根據Hobby反查學生最大年齡。查詢字段student和age間有雙下划線哦。
Hobby.objects.aggregate(Max('student__age')) { 'student__age__max': 12 }
你注意到了嗎? aggregate方法返回Dict類型數據和django的內容對象(context object)是一樣的哦。你可以很輕松地將結果傳遞給模板, 在模板中顯示。
annotate()方法詳解
annotate的中文意思是注釋,小編我覺得是非常地詞不達意,一個更好的理解是分組(Group By)。如果你想要對數據集先進行分組然后再進行某些聚合操作或排序時,需要使用annotate方法來實現。與aggregate方法不同的是,annotate方法返回結果的不僅僅是含有統計結果的一個字典,而是包含有新增統計字段的查詢集(queryset).
我們接下來也看下幾個實際使用案例。
# 按學生分組,統計每個學生的愛好數量
Student.objects.annotate(Count('hobbies'))
返回的結果依然是Student查詢集,只不過多了hobbies__count這個字段。如果你不喜歡這個默認名字,你當然可以對這個字段進行自定義從而使它變得更直觀。
# 按學生分組,統計每個學生愛好數量,並自定義字段名
Student.objects.annotate(hobby_count_by_student=Count('hobbies'))
# 按愛好分組,再統計每組學生數量。
Hobby.objects.annotate(Count('student'))
# 按愛好分組,再統計每組學生最大年齡。
Hobby.objects.annotate(Max('student__age'))
Annotate方法與Filter方法聯用
有時我們需要先對數據集先篩選再分組,有時我們還需要先分組再對查詢集進行篩選。根據需求不同,我們可以合理地聯用annotate方法和filter方法。注意: annotate和filter方法聯用時使用順序很重要。
# 先按愛好分組,再統計每組學生數量, 然后篩選出學生數量大於1的愛好。
Hobby.objects.annotate(student_num=Count('student')).filter(student_num__gt=1)
# 先按愛好分組,篩選出以'd'開頭的愛好,再統計每組學生數量。
Hobby.objects.filter(name__startswith="d").annotate(student_num=Count('student‘))
Annotate與order_by()聯用
我們同樣可以使用order_by方法對annotate方法返回的數據集進行排序。
# 先按愛好分組,再統計每組學生數量, 然后按每組學生數量大小對愛好排序。
Hobby.objects.annotate(student_num=Count('student‘)).order_by('student_num')
# 統計最受學生歡迎的5個愛好。
Hobby.objects.annotate(student_num=Count('student‘)).order_by('-student_num')[:5]
Annotate與values()聯用
我們在前例中按學生對象進行分組,我們同樣可以按學生姓名name來進行分組。唯一區別是本例中,如果兩個學生具有相同名字,那么他們的愛好數量將疊加。
# 按學生名字分組,統計每個學生的愛好數量。
Student.objects.values('name').annotate(Count('hobbies'))
你還可以使用values方法從annotate返回的數據集里提取你所需要的字段,如下所示:
# 按學生名字分組,統計每個學生的愛好數量。
Student.objects.annotate(hobby_count=Count('hobbies')).values('name', 'hobby_count')
小結
Django的aggregate和annotate方法屬於高級查詢方法,主要用於組合查詢,可以大大提升數據庫查詢效率。當你需要對查詢集(queryset)的某些字段進行聚合操作時(比如Sum, Avg, Max),請使用aggregate方法。如果你想要對數據集先進行分組(Group By)然后再進行某些聚合操作或排序時,請使用annotate方法。