Django使用ORM查询A模型时如何过滤关联B模型数据
Django查询时过滤关联子对象实现方法
场景说明
现有模型定义:
class A(models.Model): a_name = models.CharField(max_length=100) class B(models.Model): b_name = models.CharField(max_length=100) a_name = models.ForeignKey(A, on_delete=models.CASCADE, to_field="a_name", db_column="a_name")
原有查询为获取指定a_name的A实例,同时附带所有关联B对象:
A.objects.filter(a_name="a_name").all()
需求是在保留目标A实例的前提下,仅附带符合b_name过滤规则的关联B对象。
推荐实现:自定义Prefetch预取
使用Django内置的Prefetch对象自定义预取逻辑,是最符合需求的方案:该方案不会因为关联B不符合过滤条件就排除对应的A实例,且仅执行2次SQL,查询效率高。
示例代码(假设需要过滤b_name等于target_val的B对象,可自行替换过滤规则):
from django.db.models import Prefetch # 先定义关联B的过滤查询集 filtered_b_queryset = B.objects.filter(b_name="target_val") result = A.objects.filter( a_name="a_name" ).prefetch_related( # 第一个参数为反向关联名,ForeignKey未指定related_name时默认是{小写模型名}_set,即b_set Prefetch("b_set", queryset=filtered_b_queryset, to_attr="matched_b_objs") )
查询完成后,每个A实例会新增matched_b_objs属性,直接访问即可拿到过滤后的关联B列表:
for a_item in result: print(a_item.a_name) # 遍历符合条件的关联B for b_item in a_item.matched_b_objs: print(b_item.b_name)
常见误区
不要直接在A的filter中使用双下划线跨表过滤,例如:
# 错误写法 A.objects.filter(a_name="a_name", b__b_name="target_val")
这种写法存在两个问题:
- 会直接排除掉「没有符合b_name过滤条件的关联B」的A实例,不符合保留所有目标A的需求
- 不会对A实例挂载的关联B做过滤,取
a.b_set.all()时还是会拿到所有关联的B对象
如果你的需求调整为仅保留存在符合条件B的A实例,可以在上述Prefetch写法的基础上,给A的查询条件加上跨表过滤,同时加distinct()去重:
result = A.objects.filter( a_name="a_name", b__b_name="target_val" ).prefetch_related( Prefetch("b_set", queryset=B.objects.filter(b_name="target_val"), to_attr="matched_b_objs") ).distinct()
注:如果定义ForeignKey时手动设置了
related_name参数,将上述代码中的b_set替换为你自定义的related_name即可。
内容的提问来源于stack exchange,提问作者翁锦程
相关产品推荐
相关产品推荐

