Django 數據統計查詢


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"]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM