Django 中使用 annotate() 與 Avg() 進行平均評分計算
更新日期: 2025 年 1 月 7 日
本文為 留言評分功能 系列教學,第 15 篇:
- 使用 Django + Tailwind + Alpine.js 實作「五顆星評分」功能教學
- Django validators 驗證器完整教學
- Django PositiveSmallIntegerField 新手指南
- 在 esbuild 專案中整合 Alpine.js 的完整指南
- 使用 Alpine.js 建立星星評分表單 — 新手指南
- 深入理解 Alpine.js 中的 template 標籤使用指南
- Django 網站如何新增「星星評分」功能 — 後端接收邏輯
- Django 表單:如何讓使用者選擇性提交星星評分與留言
- Django 如何限制同一使用者只能對同一服務留言一次?
- Django 留言軟刪除邏輯|程式碼解析
- Django 計算評分摘要教學 — 使用 ORM 進行星等統計
- Python Django 使用 annotate、aggregate 統計教學
- 使用 Alpine.js 實作星級評分分佈 – 詳細教學
- Alpine.js 與 Tailwind CSS 動態樣式解析:為什麼有些樣式無法生效?
- Django 中使用 annotate() 與 Avg() 進行平均評分計算 👈 所在位置
在 Django 開發中,我們經常需要針對資料庫進行統計計算,例如:
- 計算每個服務的平均評分
- 統計每位用戶的總評論數
為了提高效能並減少不必要的查詢次數,Django 提供了強大的 ORM 聚合工具,如 annotate()
與 Avg()
,可以讓我們直接在資料庫層完成統計計算。
本篇文章將詳細解釋如何使用 annotate()
和 Avg()
來計算 服務的平均評分,並透過範例與數據圖解幫助新手理解。
什麼是 annotate()
?
定義:
annotate()
是 Django ORM 中的註解方法,用來為查詢集 (QuerySet) 附加額外的計算結果。
這些計算結果可以是:
- 平均值 (Avg)
- 計數 (Count)
- 總和 (Sum)
- 最大值 (Max)
- 最小值 (Min)
如何使用 annotate()
?
範例:計算每個服務的平均評分
from django.db.models import Avg
from .models import Service
services = Service.objects.annotate(average_rating=Avg('comments__rating'))
這段程式碼的作用:
Service.objects.annotate(...)
- 查詢所有
Service
(服務) 物件
- 查詢所有
average_rating=Avg('comments__rating')
- 使用
Avg
函數計算每個服務的所有Comment
(評論) 中的rating
平均值
- 使用
- 結果:
- 每個
Service
物件會自動附加一個average_rating
欄位
- 每個
資料範例
服務 (Service) 表格
ID | Title | Freelancer |
---|---|---|
1 | Logo Design | Alice |
2 | Web Design | Bob |
評論 (Comment) 表格
ID | Service ID | Rating | User |
---|---|---|---|
1 | 1 | 5 | UserA |
2 | 1 | 4 | UserB |
3 | 2 | 3 | UserC |
查詢結果 (使用 annotate()
後)
services = Service.objects.annotate(average_rating=Avg('comments__rating'))
Service Title | Freelancer | Average Rating |
---|---|---|
Logo Design | Alice | 4.5 |
Web Design | Bob | 3.0 |
annotate()
的三大核心步驟
使用 annotate()
時,Django 其實做了三件事:
- 🎯 分組 (Grouping):將資料按照服務 (
Service
) 進行分組。 - 🎯 計算 (Calculation):針對每組數據 (同一個服務的所有評論),計算
comments__rating
的平均分數 (Avg
)。 - 🎯 產生新欄位 (Add New Field):將計算結果 (
average_rating
) 附加 到查詢結果中作為新的欄位。
使用 SQL 解釋 annotate()
背後的原理
其實,annotate()
在資料庫底層會自動生成 SQL 查詢:
SELECT
service.id,
service.title,
AVG(comment.rating) AS average_rating
FROM
service
LEFT JOIN
comment ON service.id = comment.service_id
GROUP BY
service.id;
- SELECT:選擇需要的欄位 (服務 ID、標題與平均分數)。
- FROM:設定查詢的主表 (
service
)。 - LEFT JOIN:連接
comment
表,保留所有服務資料,即使沒有評論。 - GROUP BY:根據服務分組,每個服務計算一次。
- AVG:計算每個服務的平均分數。
為什麼使用 annotate()
是更好的做法?
效能更佳
- 在 資料庫層 完成計算 (SQL 層計算),減少了 Python 端的重複查詢與計算 (N+1 查詢問題)。
可擴展性更高
- 可以輕鬆擴展計算其他統計數據,如
Count
、Sum
、Max
等。
更簡潔的程式碼
- 對比傳統的 Python 迴圈計算,程式碼更簡潔且容易維護。
範例:在 freelancer_dashboard
中使用 annotate()
views.py
from django.db.models import Avg
from django.shortcuts import render
from .models import Service
def freelancer_dashboard(request, id):
freelancer = request.user
# 使用 annotate 計算每個服務的平均評分
services = (
Service.objects.filter(freelancer_user=freelancer)
.annotate(average_rating=Avg('comments__rating'))
.order_by("-created_at")
)
return render(request, "services/freelancer_dashboard.html", {"services": services})
前端範例 (freelancer_dashboard.html
)
{% for service in services %}
<div class="border p-4 rounded-lg shadow mb-4">
<h3 class="text-xl font-bold">{{ service.title }}</h3>
<p class="text-gray-600">{{ service.description }}</p>
<!-- ⭐ 顯示平均評分 -->
<p class="text-yellow-500 text-lg">
平均評分: {{ service.average_rating|floatformat:1 }} ★
</p>
<!-- ✅ 無評分時的處理 -->
{% if service.average_rating is None %}
<p class="text-gray-500">尚無評分</p>
{% endif %}
</div>
{% endfor %}
總結
annotate()
是 Django ORM 中的註解工具,用於對查詢結果附加統計數據。- 在計算平均分數時,使用
Avg()
可以直接在資料庫層完成計算,效能更佳。 - 推薦做法:在資料庫端進行計算,避免 Python 端重複查詢 (N+1 問題)。
這樣的解釋清楚了嗎?😊 如果還有疑問,隨時問我!