Django 數據聚合函數 annotate


統計各個分類下的文章數

在我們的博客側邊欄有分類列表,顯示博客已有的全部文章分類。現在想在分類名后顯示該分類下有多少篇文章,該怎么做呢?最優雅的方式就是使用 Django 模型管理器的 annotate 方法。

模型回顧

回顧一下我們的模型代碼,Django 博客有一個 Post 和 Category 模型,分別表示文章和分類:

blog/models.py class Post(models.Model): title = models.CharField(max_length=70) body = models.TextField() category = models.ForeignKey('Category') # 其它屬性... def __str__(self): return self.title class Category(models.Model): name = models.CharField(max_length=100) 

我們知道從數據庫取數據都是使用模型管理器 objects 的方法實現的。比如獲取全部分類是:Category.objects.all() ,假設有一個名為 test 的分類,那么獲取該分類的方法是:Category.objects.get(name='test') 。objects 除了 allget 等方法外,還有很多操作數據庫的方法,而其中有一個 annotate 方法,該方法正可以幫我們實現本文所關注的統計分類下的文章數量的功能。

數據庫數據聚合

annotate 方法在底層調用了數據庫的數據聚合函數,下面使用一個實際的數據庫表來幫助我們理解 annotate 方法的工作原理。在 Post 模型中我們通過 ForeignKey 把 Post 和 Category 關聯了起來,這時候它們的數據庫表結構就像下面這樣:

Post 表:

id title body category_id
1 post 1 ... 1
2 post 2 ... 1
3 post 3 ... 1
4 post 4 ... 2

Category 表:

name id
category 1 1
category 2 2

這里前 3 篇文章屬於 category 1,第 4 篇文章屬於 category 2。

當 Django 要查詢某篇 post 對應的分類時,比如 post 1,首先查詢到它分類的 id 為 1,然后 Django 再去 Category 表找到 id 為 1 的那一行,這一行就是 post 1 對應的分類。反過來,如果要查詢 category 1 對應的全部文章呢?category 1 在 Category 表中對應的 id 是 1,Django 就在 Post 表中搜索哪些行的 category_id 為 1,發現前 3 行都是,把這些行取出來就是 category 1 下的全部文章了。同理,這里 annotate 做的事情就是把全部 Category 取出來,然后去 Post 查詢每一個 Category 對應的文章,查詢完成后只需算一下每個 category id 對應有多少行記錄,這樣就可以統計出每個 Category 下有多少篇文章了。把這個統計數字保存到每一條 Category 的記錄就可以了(當然並非保存到數據庫,在 Django ORM 中是保存到 Category 的實例的屬性中,每個實例對應一條記錄)。

使用 Annotate

以上是原理方面的分析,具體到 Django 中該如何用呢?在我們的博客中,獲取側邊欄的分類列表的方法寫在模板標簽 get_categories 里,因此我們修改一下這個函數,具體代碼如下:

blog/templatetags/blog_tags.py from django.db.models.aggregates import Count from blog.models import Category @register.simple_tag def get_categories(): # 記得在頂部引入 count 函數 # Count 計算分類下的文章數,其接受的參數為需要計數的模型的名稱 return Category.objects.annotate(num_posts=Count('post')).filter(num_posts__gt=0) 

這個 Category.objects.annotate 方法和 Category.objects.all 有點類似,它會返回數據庫中全部 Category 的記錄,但同時它還會做一些額外的事情,在這里我們希望它做的額外事情就是去統計返回的 Category 記錄的集合中每條記錄下的文章數。代碼中的 Count 方法為我們做了這個事,它接收一個和 Categoty 相關聯的模型參數名(這里是 Post,通過 ForeignKey 關聯的),然后它便會統計 Category 記錄的集合中每條記錄下的與之關聯的 Post 記錄的行數,也就是文章數,最后把這個值保存到 num_posts 屬性中。

此外,我們還對結果集做了一個過濾,使用 filter 方法把 num_posts 的值小於 1 的分類過濾掉。因為 num_posts 的值小於 1 表示該分類下沒有文章,沒有文章的分類我們不希望它在頁面中顯示。關於 filter 函數以及查詢表達式(雙下划線)在之前已經講過,具體請參考 分類與歸檔

在模板中引用新增的屬性

現在在 Category 列表中每一項都新增了一個 num_posts 屬性記錄該 Category 下的文章數量,我們就可以在模板中引用這個屬性來顯示分類下的文章數量了。

templates/base.html

<ul> {% for category in category_list %} <li> <a href="{% url 'blog:category' category.pk %}">{{ category.name }} <span class="post-count">({{ category.num_posts }})</span> </a> </li> {% empty %} 暫無分類! {% endfor %} </ul> 

也就是在模板中通過模板變量 {{ category.num_posts }} 顯示 num_posts 的值。開啟開發服務器,可以看到分類名后正確地顯示了該分類下的文章數了,而沒有文章分類則不會在分類列表中出現。

將 Annotate 用於其它關聯關系

此外,annotate 方法不局限於用於本文提到的統計分類下的文章數,你也可以舉一反三,只要是兩個 model 類通過 ForeignKey 或者 ManyToMany 關聯起來,那么就可以使用 annotate 方法來統計數量。比如下面這樣一個標簽系統:

class Post(models.Model): title = models.CharField(max_length=70) body = models.TextField() Tags = models.ManyToMany('Tag') def __str__(self): return self.title class Tag(models.Model): name = models.CharField(max_length=100) 

統計標簽下的文章數:

from django.db.models.aggregates import Count from blog.models import Tag # Count 計算分類下的文章數,其接受的參數為需要計數的模型的名稱 tag_list = Tag.objects.annotate(num_posts=Count('post')) 

關於 annotate 方法官方文檔的說明在這里:annotate。同時也建議了解了解 objects 下的其它操作數據庫的方法,以便在遇到相關問題時知道去哪里查閱。


免責聲明!

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



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