Django 中使用 annotate() 與 Avg() 進行平均評分計算

更新日期: 2025 年 1 月 7 日

在 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'))

這段程式碼的作用:

  1. Service.objects.annotate(...)
    • 查詢所有 Service (服務) 物件
  2. average_rating=Avg('comments__rating')
    • 使用 Avg 函數計算每個服務的所有 Comment (評論) 中的 rating 平均值
  3. 結果
    • 每個 Service 物件會自動附加一個 average_rating 欄位

資料範例

服務 (Service) 表格

IDTitleFreelancer
1Logo DesignAlice
2Web DesignBob

評論 (Comment) 表格

IDService IDRatingUser
115UserA
214UserB
323UserC

查詢結果 (使用 annotate() 後)

services = Service.objects.annotate(average_rating=Avg('comments__rating'))
Service TitleFreelancerAverage Rating
Logo DesignAlice4.5
Web DesignBob3.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;
  1. SELECT:選擇需要的欄位 (服務 ID、標題與平均分數)。
  2. FROM:設定查詢的主表 (service)。
  3. LEFT JOIN:連接 comment 表,保留所有服務資料,即使沒有評論。
  4. GROUP BY:根據服務分組,每個服務計算一次。
  5. AVG:計算每個服務的平均分數。

為什麼使用 annotate() 是更好的做法?

效能更佳

  • 資料庫層 完成計算 (SQL 層計算),減少了 Python 端的重複查詢與計算 (N+1 查詢問題)。

可擴展性更高

  • 可以輕鬆擴展計算其他統計數據,如 CountSumMax 等。

更簡潔的程式碼

  • 對比傳統的 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 %}

總結

  1. annotate() 是 Django ORM 中的註解工具,用於對查詢結果附加統計數據。
  2. 在計算平均分數時,使用 Avg() 可以直接在資料庫層完成計算,效能更佳。
  3. 推薦做法:在資料庫端進行計算,避免 Python 端重複查詢 (N+1 問題)。

這樣的解釋清楚了嗎?😊 如果還有疑問,隨時問我!

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *