Django反向外键prefetch_related查询条件不生效问题
Django预取关联过滤失效问题解决
模型定义
class Book(models.Model): name = models.CharField(max_length=300) price = models.IntegerField(default=0) def __str__(self): return self.name class Store(models.Model): name = models.CharField(max_length=300) default = models.BooleanField(default=False) books = models.ForeignKey(Book, on_delete=models.CASCADE)
问题重现
执行以下查询试图预取并过滤default=False的Store:
Book.objects.prefetch_related(Prefetch('store_set', queryset=Store.objects.filter(default=False))) .values("store__name", "name", "store__default")
生成的SQL未包含过滤条件:
SELECT "core_store"."name", "core_book"."name", "core_store"."default" FROM "core_book" LEFT OUTER JOIN "core_store" ON ("core_book"."id" = "core_store"."books_id")
查询结果仍包含default=True的记录:
<QuerySet [{'store__name': 'Subway Store', 'name': 'Hello', 'store__default': False}, {'store__name': 'Times Square', 'name': 'Hello', 'store__default': False}, {'store__name': 'Subway Store', 'name': 'GoodBye', 'store__default': True}, {'store__name': 'Times Square', 'name': 'GoodBye', 'store__default': False}, {'store__name': 'Subway Store', 'name': 'Greetings', 'store__default': True}, {'store__name': 'Subway Store', 'name': 'HateWords', 'store__default': False}]>
问题原因
prefetch_related是在Python层面完成的二次查询数据合并,而values()会直接触发数据库层面的JOIN查询,完全忽略预取的过滤逻辑,所以预取的default=False条件不会作用到最终生成的SQL中。
解决方案
方案1:单查询直接过滤关联字段(推荐,仅需一次DB查询)
直接通过双下划线关联过滤,让SQL生成时自动带上WHERE条件,实现单查询过滤:
Book.objects.filter(store__default=False).values("store__name", "name", "store__default")
对应的SQL会新增过滤条件:
SELECT "core_store"."name", "core_book"."name", "core_store"."default" FROM "core_book" INNER JOIN "core_store" ON ("core_book"."id" = "core_store"."books_id") WHERE "core_store"."default" = False
这样返回的结果就只会包含default=False的Store记录。
方案2:预取过滤后的关联对象(需两次DB查询,适用于需操作模型对象的场景)
如果需要获取完整的Book和Store模型对象,而非仅字段值,可以用Prefetch的to_attr属性将过滤后的Store存入自定义属性,避免和默认的store_set混淆:
from django.db.models import Prefetch books = Book.objects.prefetch_related( Prefetch( 'store_set', queryset=Store.objects.filter(default=False), to_attr='filtered_stores' ) ) # 遍历获取过滤后的关联数据 for book in books: for store in book.filtered_stores: print(f"书籍:{book.name},店铺:{store.name},默认状态:{store.default}")
这种方式会先查询所有Book,再查询所有default=False的Store,最后在Python层面关联数据,确保获取的关联Store都是符合条件的。
内容的提问来源于stack exchange,提问作者Nishant Kashyap
相关产品推荐
相关产品推荐

