Django 聚合與查詢集API實現側邊欄

本文從Django官方文檔總結而來,將聚合的主要用法和查詢集的常見方法做一歸納。

聚合

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)
    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
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

注意幾個點: .all() 和 .filter()    和  .count() 和 .aggregate() 爲方法,前兩者是返回新的查詢集的方法(括號裏面有參數),後兩者是不返回查詢集的方法 。 publisher 顯然爲字段名, name爲字段查找,兩者之間以雙下劃線連接 :__       price也是字段名,Avg 爲聚合函數,用來求平均值。 以上提及的方法字段查找聚合函數將在查詢集API中介紹。那麼,我們先介紹聚合。

2.django提供了兩種生成聚合的方法

1)從整個查詢集生成統計值,主要用法:aggregate(*args, **kwargs)

 aggregate()是QuerySet 的一個終止子句,也就是說aggregate返回一個字典,包含根據QuerySet 計算得到的聚合值(平均數、和等等)。aggregate() 的每個參數指定返回的字典中將要包含的值。eg:

Book.objects.all()  # 返回所有圖書的集合

>>> from django.db.models import Avg  # 引入用來求平均值的聚合函數 Avg
>>> Book.objects.all().aggregate(Avg('price'))  # 要計算所有書的平均價格,通過在查詢集後面附加aggregate()子句實現
{'price__avg': 34.35} # 返回的是字典

>>> Book.objects.aggregate(Avg('price'))  # all()在這裏多餘,可以省掉
{'price__avg': 34.35}                     # 返回的字典中,鍵爲聚合值的標識符,由字段和聚合函數的名稱自動生成 ,值爲計算出來的聚合值

>>> Book.objects.aggregate(average_price=Avg('price'))
 {'average_price': 34.35}                 # 爲聚合值更換名稱,提供參數average_price
>>> 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')}

2)爲查詢集的每一項成聚合,主要用法:annotate(*args, **kwargs)

這種方法爲每一個對象都生成一個獨立的彙總值,比如,如果你在檢索一列圖書,你可能想知道每一本書有多少作者參與。每本書和作者是多對多的關係。我們想要彙總QuerySet.中每本書裏的這種關係。逐個對象的彙總結果可以由annotate()子句生成。當annotate()子句被指定之後,QuerySet中的每個對象都會被註上特定的值。這些註解的語法都和aggregate()子句所使用的相同。annotate()的每個參數都描述了將要被計算的聚合。

# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors')) # 和aggregate語法相同,不同的是annotate返回的q是各個對象,可用q[0],q[1]等取出對象
# Interrogate the first object in the queryset
>>> q[0]  # 返回的是第一個Book對象
<Book: The Definitive Guide to Django>
>>> q[0].authors__count   # 編寫第一本書的作者數目爲2, authors爲Book模型中的字段名,count爲字段查詢,所以用雙下劃線連接
2
# Interrogate the second object in the queryset
>>> q[1]  # 返回的是第二個Book對象
<Book: Practical Django Projects>
>>> q[1].authors__count
1

>>> q = Book.objects.annotate(num_authors=Count('authors'))  # 提供了自定義的num_authors別名代替了authors__count
>>> q[0].num_authors
2
>>> q[1].num_authors

1

與 aggregate() 不同的是, annotate() 不是一個終止子句。annotate()子句的返回結果是一個查詢集 (QuerySet);這個 QuerySet可以用任何QuerySet方法進行修改,包括 filter(), order_by()。發現aggregate 和 annotate用法的區別了嗎,再次舉例如下(在聚合函數中指定聚合字段時,Django 允許你使用同樣的 雙下劃線 表示關聯關係,):

>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))
# 查找每個商店提供的圖書的價格範圍

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
# 查找所有書店中最便宜的書和最貴的書的價格

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
# 利用雙下劃線延伸關係鏈,查找所有書店中的所有作者的最小年齡

3. 聚合和其他查詢子句

filter() 和 exclude()

>>> from django.db.models import Count, Avg
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))
# 使用annotate() 子句時,過濾器有限制註解對象的作用。例如,得到每本以 "Django" 爲書名開頭的圖書作者的總數

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))
# 使用aggregate()子句時,過濾器有限制聚合對象的作用。例如,算出所有以 "Django" 爲書名開頭的圖書平均價格
>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
# 得到不止一個作者的圖書

注意以上annotate() 和 filter()子句的順序,順序不同查詢結果也會不同(後者篩選的出版商爲前者的子集。):

>>> Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)
# 返回了至少出版了一本好書(評分大於 3 分)的出版商, 在這些出版商中包含出版商所發行的所有圖書!(這些出版商中每個出版商只要發行過一本>3的書就算)

>>> 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()

>>> Author.objects.annotate(average_rating=Avg('book__rating'))
# 返回所有作者及他所著圖書的平均評分

>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
# 作者先按名稱分組,意味着若兩位作者同名則查詢結果被合併!,兩者均分被算爲一個
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

# 這段代碼交換了value和average順序,將給每個作者添加一個唯一的字段,但只有作者名稱和average_rating 註解會返回在輸出結果中

4.查詢集(QuerySet)API 查詢

本質上,可以創建、過濾、切片和傳遞查詢集而不用真實操作數據庫。在你對查詢集做求值之前,不會發生任何實際的數據庫操作。可以通過迭代、切片、序列化/緩存、repr()、len()、list()、bool()

1)返回新的查詢集方法

filter(): 返回一個新的QuerySet,包含與給定的查詢參數匹配的對象。

exclude():返回一個新的QuerySet,它包含不滿足給定的查找參數的對象。

annotate(*args, **kwargs): 使用提供的查詢表達式Annotate查詢集中的每個對象。

order_by(*fields):  默認情況下,QuerySet 根據模型Meta 類的ordering 選項排序。你可以使用order_by 方法給每個QuerySet 指定特定的排序。

...

QuerySet API參考

tricks: 利用聚合解決博客中增加點擊排行和站長推薦側邊欄的方法:

views.py:

 # 點擊排行
  click_list = Article.objects.all().order_by('-click_count')

 # 站長推薦
  command_list = Article.objects.filter(is_recommend__isnull=False)

base.html:

    <div  class="bd bd-news">
     <ul>
       {% for article in article_comment_list %}
       <li><a href="/" target="_blank">{{ article.title }}</a></li>
       {% endfor %}
     </ul>
   </div>

   <div class="bd bd-news">
     <ul>
       {% for article in command_list %}
       <li><a href="/" target="_blank">{{ article.title }}</a></li>
       {% endfor %}
     </ul>
    </div>

final:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章