本文為 留言評分功能 系列教學,第 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) 表格
TitleLogo Design
FreelancerAlice
TitleWeb Design
FreelancerBob
評論 (Comment) 表格
Service ID1
Rating5
UserUserA
Service ID1
Rating4
UserUserB
Service ID2
Rating3
UserUserC
查詢結果 (使用 annotate() 後)
services = Service.objects.annotate(average_rating=Avg('comments__rating'))FreelancerAlice
Average Rating4.5
FreelancerBob
Average Rating3.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 問題)。
這樣的解釋清楚了嗎?😊 如果還有疑問,隨時問我!