1. 程式人生 > >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, on_delete=models.CASCADE) 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')} # Difference between the highest priced book and the average price of all books. >>> from django.db.models import FloatField >>> Book.objects.aggregate( ... price_diff=Max('price', output_field=FloatField()) - Avg('price')) {'price_diff': 46.85} # All the following queries involve traversing the Book<->Publisher # foreign key relationship backwards. # 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 <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]> >>> pubs[0].num_books 73 # Each publisher, with a separate count of books with a rating above and below 5 >>> from django.db.models import Q >>> above_5 = Count('book', filter=Q(book__rating__gt=5)) >>> below_5 = Count('book', filter=Q(book__rating__lte=5)) >>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5) >>> pubs[0].above_5 23 >>> pubs[0].below_5 12 # 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

QuerySet上生成聚合

Django提供了兩種生成聚合的方法。第一種方法是從整個QuerySet生成彙總值。比如你想要計算所有在售書的平均價格.Django的查詢語法提供了一種用來描述所有圖書集合的方法:

>>> Book.objects.all() 

通過可以在QuerySet後新增aggregate()[主語]來計算QuerySet物件的彙總值。

>>> 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()子句的引數描述了我們想要計算的聚合值 - 在本例中,priceBook模型上欄位 的平均值

 
   

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')}

可以使用該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

當指定要在聚合函式中聚合的欄位時,Django將允許您使用在引用過濾器中的相關欄位時使用的相同雙下劃線表示法然後Django將處理檢索和聚合相關值所需的任何表連線。

例如,要查詢每個商店中提供的書籍的價格範圍,您可以使用註釋:

>>> from django.db.models import Max, Min >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price')) 

這告訴Django檢索Store模型,模型連線(通過多對多關係)Book,並在書模型的價格欄位上聚合以產生最小值和最大值。

同樣的規則適用於該aggregate()條款。如果您想知道任何商店中可供出售的任何書籍的最低價格和最高價格,您可以使用聚合:

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
 
       

我們可以要求所有釋出者註釋其各自的總賬簿計數器(注意我們如何使用'book'指定 Publisher- > Book反向外來鍵跳):

 
        
         
         
>>> from django.db.models import Avg, Count, Min, Sum >>> Publisher.objects.annotate(Count('book')) 
 
       

(每次Publisher在由此而來QuerySet將有一個名為額外屬性book__count。)

 
       

我們還可以要求每個出版商管理的最老的書籍:

 
        
         
         
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate')) 
 
       

(生成的字典會有一個名為'oldest_pubdate'。如果沒有指定這樣的別名,那就相當長了'book__pubdate__min'。)

 
       

filter()exclude()

 
       

聚合也可以參與過濾器。應用於普通模型欄位的任何filter()(或 exclude())將具有約束考慮進行聚合的物件的效果。

 
       

annotate()子句一起使用時,過濾器具有約束計算註釋的物件的效果。例如,您可以使用查詢生成帶有標題以“Django”開頭的所有書籍的帶註釋的列表:

 
        
         
         
>>> from django.db.models import Avg, Count >>> 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) 

此查詢生成帶註釋的結果集,然後基於該批註生成過濾器。

如果需要兩個帶有兩個單獨過濾器的註釋,則可以將 filter引數與任何聚合一起使用。例如,要生成具有高評價書籍數量的作者列表:

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

Author結果集中的每個都將具有num_books和 highly_rated_books屬性。

 
       

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()狀語從句:values()的順序

 
       

使用狀語從句: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()子句,則需要顯式包含聚合列。