You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django中Aggregate引发额外查询的问题求解

解决Django中书籍平均评分的N+1查询问题

你当前遇到的是典型的N+1查询问题:通过Book模型的average_rating属性计算评分时,每遍历一个书籍对象,就会额外执行一次Review表的聚合查询。当展示书籍列表时,会先执行1次查询获取所有书籍,再执行N次查询分别获取每本书的评分,导致查询效率低下。

解决方案:在查询时预计算平均评分

利用Django的annotate结合聚合函数Avg,在查询书籍的同时直接从数据库层面计算好平均评分,只需要一次查询即可获取所有数据。

步骤1:修改视图中的书籍查询逻辑

移除模型中average_rating属性的聚合查询(或保留属性但后续不再通过它触发查询),在视图中通过annotate注入平均评分字段:

from django.db.models import Avg
from .models import Book

def book_list(request):
    # 用annotate为每个Book对象添加average_rating字段
    books = Book.objects.annotate(
        average_rating=Avg('reviews__rating')
    ).filter(publish=True)
    return render(request, 'book_list.html', {'books': books})

这里利用Review模型中related_name='reviews'的外键关联,通过reviews__rating关联到书籍的所有评分,再用Avg计算平均值。

步骤2:模板中直接访问预计算的字段

在模板里不需要调用任何方法,直接访问average_rating即可,同时处理无评论的情况:

{% for book in books %}
    <div class="book-item">
        <h3>{{ book.title }}</h3>
        <p>平均评分:{{ book.average_rating|default:"暂无评分" }}</p>
        <!-- 其他书籍信息展示 -->
    </div>
{% endfor %}

原理说明

annotate会在数据库层面执行LEFT OUTER JOIN关联Review表,并通过GROUP BY对每本书的评分进行聚合计算,最终将平均评分作为Book查询集的一个字段返回,彻底避免了N+1次查询的问题。

如果需要保留模型中的average_rating属性,也可以修改为优先使用预计算的字段,回退到原逻辑(仅在单独获取单个Book对象时使用):

@property
def average_rating(self):
    # 优先使用annotate注入的字段
    if hasattr(self, 'average_rating'):
        return self.average_rating
    # 回退到原查询逻辑
    avg_rating = Review.objects.filter(book_id=self.id).aggregate(Avg('rating'))
    return avg_rating['rating__avg']

内容的提问来源于stack exchange,提问作者Ryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 04:20:29