Django 1.8中不使用Exists/OuterRef实现查询集标注的替代方案
Django 1.8 替代Exists/OuterRef实现查询集标注的方案
在Django 1.8中没有Exists和OuterRef,要实现给Question查询集标注是否存在关联Answers的功能,过去常用以下几种方案:
方案一:Count聚合 + Case/When转换标记
这是最推荐的ORM原生方案,通过统计关联Answer的数量,再转换为布尔型标记(用1/0模拟,Django 1.8对Case中BooleanField支持有限,IntegerField更稳妥):
from django.db.models import Count, Case, When, IntegerField # 分步标注:先统计数量,再转换为存在标记 recordset = Question.objects.annotate( answer_count=Count('answers', distinct=True) # distinct=True避免重复计数 ).annotate( has_answers=Case( When(answer_count__gt=0, then=1), default=0, output_field=IntegerField() ) ) # 也可以合并为单个annotate recordset = Question.objects.annotate( has_answers=Case( When(answers__isnull=False, then=1), default=0, output_field=IntegerField() ), answer_count=Count('answers', distinct=True) )
原理:Count('answers')会自动左外连接Answer表,统计每个Question的关联记录数;Case/When将计数结果转换为1(存在)或0(不存在),在Python中可直接当作布尔值使用。
方案二:用extra()直接编写EXISTS原生SQL
如果追求性能,可通过extra()直接写与Exists等价的SQL子查询,这是早期Django版本实现存在性检查的常用方式:
recordset = Question.objects.extra( select={ 'has_answers': "EXISTS(SELECT 1 FROM answers WHERE answers.question_id = question.id)" } )
注意:需替换SQL中的表名为数据库实际表名(比如你的app名为polls,则表名可能是polls_question和polls_answers)。此方法性能最优,但耦合原生SQL,后续迁移到新版本时需要替换为ORM语法,维护性较差。
方案三:拆分查询后合并结果
通过拆分"有答案"和"无答案"的查询集,分别标注后合并,逻辑直观但性能一般:
from django.db.models import Value, BooleanField # 标注有答案的Question has_answers_qs = Question.objects.filter(answers__isnull=False).distinct().annotate( has_answers=Value(True, output_field=BooleanField()) ) # 标注无答案的Question no_answers_qs = Question.objects.filter(answers__isnull=True).annotate( has_answers=Value(False, output_field=BooleanField()) ) # 合并两个查询集 recordset = has_answers_qs.union(no_answers_qs)
此方法适合数据量较小的场景,数据量大时会因两次查询+union操作导致性能下降。
内容的提问来源于stack exchange,提问作者alj
相关产品推荐
相关产品推荐

