Django使用annotate聚合时如何获取完整关联模型实例而非仅字段值
解决方案
以下是两种可直接落地的实现方式,无需依赖第三方工具:
方法1:通用基础实现(兼容Django 1.11及以上所有版本)
基于Django原生的Subquery和预取逻辑实现,不需要额外封装:
from django.db.models import Subquery, OuterRef from django.db.models.query import Prefetch # 构造子查询:匹配当前店铺,按价格升序取第一本图书的ID cheapest_book_subq = Book.objects.filter( store_id=OuterRef("id") ).order_by("price").values("id")[:1] # 标注最低价格图书ID,再预取加载完整的图书实例 store_queryset = Store.objects.annotate( cheapest_book_pk = Subquery(cheapest_book_subq) ).prefetch_related( Prefetch( "books", queryset=Book.objects.filter(id=OuterRef("cheapest_book_pk")), to_attr="cheapest_book" ) )
调用方式和你预期的基本一致,仅需要处理预取默认返回列表的特性:
for store in store_queryset: if store.cheapest_book: book = store.cheapest_book[0] print(book.price, book.title)
方法2:封装为通用工具,实现你要求的AggregateRelation调用效果
你可以把上述逻辑封装为可复用的工具,直接匹配你示例中的调用习惯:
from django.db.models import Subquery, OuterRef from django.db.models.query import Prefetch def AggregateRelation(relation_field, sort_field, aggregate_type): order_prefix = "-" if aggregate_type.__name__ == "Max" else "" rel_name = relation_field.split("__")[0] def wrap_queryset(queryset): parent_model = queryset.model related_model = parent_model._meta.get_field(rel_name).related_model parent_ref_name = parent_model._meta.model_name subq = related_model.objects.filter( **{f"{parent_ref_name}_id": OuterRef("id")} ).order_by(f"{order_prefix}{sort_field}").values("id")[:1] return queryset.annotate( **{f"_tmp_{rel_name}_id": Subquery(subq)} ).prefetch_related( Prefetch( rel_name, queryset=related_model.objects.filter(id=OuterRef(f"_tmp_{rel_name}_id")), to_attr=rel_name ) ) return wrap_queryset
调用方式和你示例完全一致:
for store in AggregateRelation("books", "price", Min)(Store.objects.all()): if store.books: cheapest_book = store.books[0] print(cheapest_book.price, cheapest_book.title)
补充说明
- 如果同一店铺存在多本价格相同的最低价格图书,默认返回排序后的第一条,如需返回所有符合条件的图书,删除子查询末尾的
[:1]限制即可 - 该方案兼容Django支持的所有数据库,不需要依赖特定数据库独有的语法特性
- 数据量较大的场景下,给关联外键和排序字段添加联合索引,可大幅提升查询性能
内容的提问来源于stack exchange,提问作者J Eti
相关产品推荐
相关产品推荐

