Django 2.0.0中基于关联Project高效筛选ToolResult的方法
高效筛选关联特定Project的ToolResult方案
首先先明确你的模型结构(方便参考):
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.utils.translation import gettext_lazy as _ class Project(models.Model): name = models.CharField(_(u'Name'), max_length=32) class Domain(models.Model): project = models.ForeignKey('projects.Project', on_delete=models.CASCADE) tool_result = GenericRelation('projects.ToolResult') class ToolResult(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id')
你遇到的问题很典型:用content_object__project__in=[1,2]无法过滤,因为GenericForeignKey是Django提供的上层抽象,数据库中并没有对应的外键关联,所以Django没办法将这个链式查询转换成有效的SQL语句,自然无法生效。而且你的数据量极大(50万Domain、300万ToolResult),遍历对象完全不现实,必须用数据库层面的高效查询方案。
下面给你几个适合大数量级的高效实现方式:
方案一:使用Exists相关性子查询(推荐)
这种方式在数据库层面会生成高效的相关性子查询,避免大的IN集合,性能最优:
from django.db.models import Exists, OuterRef from django.contrib.contenttypes.models import ContentType # 获取Domain对应的ContentType domain_ct = ContentType.objects.get_for_model(Domain) # 筛选关联指定Project的ToolResult target_tool_results = ToolResult.objects.filter( content_type=domain_ct, Exists( Domain.objects.filter( id=OuterRef('object_id'), # 关联ToolResult的object_id和Domain的id project_id__in=[1, 2] ) ) )
方案二:使用Subquery子查询
如果你的数据库对Exists支持一般,也可以用Subquery先拿到符合条件的Domain ID集合,再过滤ToolResult:
from django.db.models import Subquery from django.contrib.contenttypes.models import ContentType domain_ct = ContentType.objects.get_for_model(Domain) # 子查询获取目标Project下的所有Domain ID target_domain_ids = Domain.objects.filter(project_id__in=[1,2]).values('id') # 过滤ToolResult target_tool_results = ToolResult.objects.filter( content_type=domain_ct, object_id__in=Subquery(target_domain_ids) )
方案三:直接用JOIN(Django 2.0兼容)
因为你用的是Django 2.0,也可以用extra直接指定表关联,生成JOIN语句,性能也很可观:
from django.contrib.contenttypes.models import ContentType domain_ct = ContentType.objects.get_for_model(Domain) target_tool_results = ToolResult.objects.filter( content_type=domain_ct ).extra( tables=['projects_domain'], # 注意替换成你的Domain表实际名称,可通过Domain._meta.db_table查看 where=[ 'projects_domain.id = toolresult_object_id', # 关联ToolResult的object_id和Domain的id 'projects_domain.project_id IN (1, 2)' ] )
注意事项:
- 确保
projects_domain是你的Domain模型对应的数据库表名,一般格式是应用名_模型名,不确定的话可以在Django shell里执行Domain._meta.db_table查看。 - 这三种方案都是直接在数据库层面完成过滤,不会把大量数据加载到Python内存,完全适配你的百万级数据量场景。
内容的提问来源于stack exchange,提问作者Arti
相关产品推荐
相关产品推荐

