Django ORM如何关联子查询实现Join操作统计书籍点赞点踩数量
实现方案
你可以通过以下几种方式实现需求,按适配性和性能排序:
方案1:直接使用原生SQL查询(性能最优,完全匹配你需要的SQL结构)
如果要100%复用你编写的SQL,直接使用Django的raw()方法即可,返回的结果依然是Book模型实例,额外携带n_like和n_dislike属性:
books = Book.objects.raw(""" SELECT b.*, r.n_like, r.n_dislike FROM book b JOIN ( SELECT book_id, COALESCE(SUM(CASE WHEN outcome='like' THEN 1 ELSE 0 END), 0) n_like, COALESCE(SUM(CASE WHEN outcome='dislike' THEN 1 ELSE 0 END), 0) n_dislike FROM reaction GROUP BY book_id) r ON b.id=r.book_id """)
方案2:使用ORM聚合语法(纯ORM,Django 2.0+支持)
使用聚合函数的filter参数简化写法,生成的SQL执行效率和你给出的语句几乎一致,适合不想写原生SQL的场景:
from django.db.models import Sum, Q, Coalesce books = Book.objects.filter( # 过滤仅保留有反馈的书籍,等价于INNER JOIN reaction__isnull=False ).annotate( n_like=Coalesce(Sum(1, filter=Q(reaction__outcome='like')), 0), n_dislike=Coalesce(Sum(1, filter=Q(reaction__outcome='dislike')), 0) ).distinct()
方案3:基于你编写的子查询改造
如果你要复用已经写好的子查询逻辑,可以通过OuterRef关联Book主键,分别注解两个统计字段:
from django.db.models import OuterRef, Subquery, IntegerField, Coalesce, Case, When, Sum # 点赞统计子查询 n_like_sub = Subquery( Reaction.objects.filter(book_id=OuterRef('pk')) .values('book_id') .annotate(cnt=Coalesce(Sum(Case(When(outcome='like', then=1), default=0, output_field=IntegerField())), 0)) .values('cnt'), output_field=IntegerField() ) # 点踩统计子查询 n_dislike_sub = Subquery( Reaction.objects.filter(book_id=OuterRef('pk')) .values('book_id') .annotate(cnt=Coalesce(Sum(Case(When(outcome='dislike', then=1), default=0, output_field=IntegerField())), 0)) .values('cnt'), output_field=IntegerField() ) # 最终查询 books = Book.objects.filter( pk__in=Reaction.objects.values_list('book_id', flat=True) ).annotate( n_like = n_like_sub, n_dislike = n_dislike_sub )
注意:Django原生ORM目前没有直接支持将自定义子查询作为JOIN表的语法,以上三种方案都可以实现你的需求,优先推荐方案1和方案2。
内容的提问来源于stack exchange,提问作者melmi
相关产品推荐
相关产品推荐

