Django:用第一个模型的字段过滤不同非相关模型的计数来注释查询
标题简单说就是:我有一个复杂的查询需要处理。
示例模型:
class FirstModel(models.Model):
master_tag = models.CharField()
... other fields
class SecondModel(models.Model):
ref_name = models.CharField()
我想从FirstModel中获取所有对象,并计算与这些对象的master_tag相同的SecondModel对象的数量。
我尝试过的:
我试着用注解(annotate)结合子查询(Subquery)和外部引用(OuterRef),但是一直出错,搞不定。
from django.db.models import OuterRef, Subquery, Count
sub_query = Subquery(
SecondModel.objects.filter(ref_name=OuterRef("master_tag")).values_list("pk", flat=True)
)
FirstModel.objects.annotate(sm_count=Count(sub_query))
我遇到的错误是:“django.db.utils.ProgrammingError: 子查询返回了多于一行的结果,无法作为表达式使用。”我尝试了很多其他的方法,其中一个是把“.count()”放在子查询的最后,但这又导致了另一个错误,因为计数会急切地评估查询,结果因为OuterRef而失败。
所以有没有办法用计数注解来获取这样的查询?我在写上面的查询时有没有犯什么低级错误?
1 个回答
我尝试了很久用OuterRef和Subquery来解决这个问题,但主要的问题是Subquery表达式设计上是为了返回单个值,而在这种情况下你需要返回多个值。
Django似乎不直接支持根据字段比较来给查询集添加不相关模型实例的计数。
我成功地用RawSQL达到了你想要的效果,而没有使用Subquery:
from django.db.models.expressions import RawSQL
# Annotate FirstModel queryset with RawSQL
qs = FirstModel.objects.annotate(
sm_count=RawSQL(
"""
SELECT COUNT(*) FROM yourapp_secondmodel
WHERE yourapp_secondmodel.ref_name = yourapp_firstmodel.master_tag
""",
[]
)
)
# Check the sm_count values
qs.values('master_tag','sm_count').order_by('-sm_count')
另一种选择是遍历所有的FirstModel实例,计算ref_name出现的次数,但这样效率太低,只适合小数据集...
总之:如果这两个模型之间有外键关系,那就简单多了。
如果有人能用Subquery让它工作,我会很高兴被证明是错的,但在此之前,RawSQL可能会解决你的问题,而且它仍然会返回一个查询集。
好吧,忘掉我之前说的,我相信我找到了办法,感谢这个回答:
这个方法在SubQuery中使用了Count,同时也使用了Coalesce来确保当Subquery返回None时有一个值:
from django.db.models import Count, OuterRef, Subquery, IntegerField
from django.db.models.functions import Coalesce
sub_query = SecondModel.objects.filter(ref_name=OuterRef('master_tag')).values('ref_name').annotate(count=Count('*')).values('count')
# Apply the Subquery, similarly to the original approach
# Use distinct() to avoid duplicates if master_tag exists multiple times in FirstModel
qs = FirstModel.objects.annotate(sm_count=Coalesce(Subquery(sub_query,output_field=IntegerField()), 0)).distinct()
qs.values('master_tag','sm_count').order_by('-sm_count')
这方法稍微复杂一点,但我觉得这会返回你想要的结果,并且可以避免你在Subquery中遇到的基数错误。