如何利用Assignment自定义管理器高效筛选拥有已批准作业的用户?
现有一组用户和用户提交的作业,对应的Django模型定义如下:
class User(models.Model): name = models.CharField() class Assignment(models.Model): user = models.ForeignKey( "User", related_name="assignments" ) status = models.CharField() approved = AssignmentActiveManager() rejected = AssignmentRejectedManager() ...
由于作业状态的判断逻辑复杂,需要封装在模型内部,因此为Assignment创建了自定义管理器,示例如下:
class AssignmentActiveManager(models.Manager): def get_queryset(self): return Assignment.objects.filter(status__in=["Approved", "Accepted"])
现在希望通过Assignment.approved管理器筛选出所有拥有已批准作业的用户,避免重复编写筛选逻辑。目前尝试的代码:
User.objects.filter(assignments__in=Assignment.approved.all()).all()
但该查询会生成WHERE status IN (SELECT ...)子查询,效率低于直接编写筛选条件的查询:
User.objects.filter(assignments__status__in=["Approved", "Accepted"]).all()
后者会执行INNER JOIN并通过WHERE user.assignment_id IN (Approved, Accepted)筛选。
问题:能否通过Assignment自定义管理器高效筛选出符合条件的用户?
当然可以,以下几种方法都能利用自定义管理器的逻辑,同时生成高效的JOIN查询而非子查询:
1. 封装筛选条件为可复用的Q对象
修改AssignmentActiveManager,将筛选逻辑封装为Q对象,既供管理器自身使用,也能在关联查询中直接复用:
class AssignmentActiveManager(models.Manager): # 把状态筛选逻辑封装成Q对象 approved_status_filter = models.Q(status__in=["Approved", "Accepted"]) def get_queryset(self): return Assignment.objects.filter(self.approved_status_filter)
查询用户时直接调用这个Q对象:
User.objects.filter(assignments__in=Assignment.approved_status_filter).all()
这种方式生成的SQL和直接写assignments__status__in=["Approved", "Accepted"]完全一致,会使用INNER JOIN,避免子查询的性能损耗。
2. 为User模型添加关联逻辑的自定义管理器
给User模型创建专属管理器,内部复用Assignment.approved的逻辑:
class UserManager(models.Manager): def with_approved_assignments(self): # 复用Assignment.approved的查询条件,生成高效JOIN return self.filter(assignments__in=Assignment.approved.get_queryset().query)
在User模型中注册该管理器:
class User(models.Model): name = models.CharField() objects = UserManager()
调用时只需:
User.objects.with_approved_assignments().all()
这种方式完全封装了查询逻辑,外部调用简洁,同时保证查询效率。
3. 利用反向关联的查询集推导
如果不想修改现有管理器,也可以通过Assignment.approved的查询集直接提取用户ID,再去重查询:
User.objects.filter(pk__in=Assignment.approved.values_list('user_id', flat=True)).distinct()
注:这种方式可能生成子查询,但多数现代数据库会对这类子查询做优化,性能差距不大;若追求严格的JOIN查询,优先选择前两种方法。
内容的提问来源于stack exchange,提问作者Enrico Borba

