Django一对多关系中按分组嵌套聚合Avg查询问题
解决方案:为商家实例关联评价问题的平均评分
嘿,我来帮你搞定这个需求!首先得说,我注意到你给出的模型里缺了关键的Review(评价)模型——毕竟要计算平均评分,总得有个模型关联商家、评价问题和具体的rate值对吧?我先默认你有这么个模型(后面会给出示例定义),然后给你几种可行的实现方案:
第一步:补全必要的Review模型
如果还没定义这个模型,先补上:
class Review(models.Model): business = models.ForeignKey(Business, on_delete=models.CASCADE, related_name='reviews') review_question = models.ForeignKey(ReviewQuestion, on_delete=models.CASCADE, related_name='reviews') rate = models.IntegerField() # 假设是1-5的评分值
方法1:用Django ORM的Prefetch+Annotate(最推荐,性能友好)
这种方法可以一次性预取所有需要的数据,避免N+1查询,而且结构清晰:
from django.db.models import Avg, Prefetch, Q from django.db.models.expressions import OuterRef from .models import Business, ReviewQuestion, Review # 先定义一个查询集:给每个评价问题带上当前商家的平均评分 question_with_avg_query = ReviewQuestion.objects.annotate( avg_rate=Avg( 'reviews__rate', filter=Q(reviews__business=OuterRef('pk')) # 只计算当前商家的评分 ) ) # 查询商家时,用Prefetch把上面的查询集关联进来 business_list = Business.objects.prefetch_related( Prefetch( 'business_type__reviewquestion_set', # 关联到商家类型下的所有评价问题 queryset=question_with_avg_query, to_attr='questions_with_avg' # 自定义属性名,方便后续访问 ) ).all() # 遍历使用的示例 for business in business_list: print(f"商家:{business.name}") # 假设Business有name字段 for question in business.business_type.questions_with_avg: avg_rate = question.avg_rate or 0 # 处理没有评分的情况 print(f" 问题:{question.question_text},平均评分:{avg_rate}")
这种方式的好处是,所有数据一次查询完成,而且每个评价问题的平均评分直接绑定在对应的ReviewQuestion实例上,非常好处理。
方法2:用Subquery实现商家级别的评分聚合
如果需要把所有评分聚合结果直接放在商家实例上,可以用Subquery:
from django.db.models import Avg, Subquery, OuterRef from .models import Business, Review # 先为每个商家+评价问题的组合计算平均评分 avg_rate_subquery = Review.objects.filter( business=OuterRef('pk') ).values('review_question').annotate(avg_rate=Avg('rate')).values('avg_rate') # 查询商家时,把聚合结果annotate进去 business_list = Business.objects.annotate( question_avg_rates=Subquery(avg_rate_subquery) ).select_related('business_type').prefetch_related('business_type__reviewquestion_set')
不过这种方式返回的question_avg_rates是一个列表,需要和商家的评价问题一一对应,处理起来不如第一种直观。
方法3:原生SQL查询(适合复杂场景)
如果ORM的写法满足不了你的复杂需求,可以直接用原生SQL:
business_list = Business.objects.raw(''' SELECT b.id, b.name, -- 替换成你的Business模型字段 rq.id as question_id, rq.question_text, COALESCE(AVG(r.rate), 0) as avg_rate FROM your_app_business b JOIN your_app_businesstype bt ON b.business_type_id = bt.id LEFT JOIN your_app_reviewquestion rq ON bt.id = rq.business_type_id LEFT JOIN your_app_review r ON b.id = r.business_id AND rq.id = r.review_question_id GROUP BY b.id, rq.id ORDER BY b.id ''') # 遍历示例 for item in business_list: print(f"商家ID:{item.id},商家名称:{item.name}") print(f" 问题ID:{item.question_id},问题内容:{item.question_text},平均评分:{item.avg_rate}")
注意要把your_app替换成你的Django应用名称,字段也要对应你的实际模型字段。
内容的提问来源于stack exchange,提问作者ermir
相关产品推荐
相关产品推荐

