如何使用annotate与Min实现Django QuerySet去重并取同书名最低价
问题根源
- 缺少按书名字段分组的逻辑:直接对全量Book对象执行annotate时,聚合函数Min/Count是针对单条记录本身计算,因此每条记录的
min_price都等于自身价格,过滤条件等于未生效,才会返回重复书名的结果。 - 存在字段名笔误:Book模型的书名字段定义为
name,你写的查询语句中误用了title作为聚合字段。
可行实现方案
方案1:PostgreSQL专属简洁写法(性能最优)
如果你的项目使用PostgreSQL作为数据库,直接用distinct指定去重字段配合排序即可实现需求,代码最简洁:
from django.db.models import F result = Book.objects.filter( library_id=21 ).order_by( "name", # 先按书名分组 "price", # 同书名按价格升序,最低价排在最前 "id" # 同书名同价时保留ID最小的记录,不需要可删除 ).distinct("name") # 按书名去重,只保留每组第一条
返回的结果就是你需要的每个书名对应最低价的Book实例。
方案2:跨数据库兼容写法
如果使用MySQL、SQLite等不支持distinct带字段参数的数据库,用子查询过滤+内存去重实现:
from django.db.models import Min, OuterRef, Subquery # 构造子查询:匹配同图书馆同书名的最低价格 min_price_subq = Book.objects.filter( library_id=OuterRef("library_id"), name=OuterRef("name") ).values("name").annotate( min_p=Min("price") ).values("min_p")[:1] # 过滤出所有价格等于对应书名最低价的记录 filtered_books = Book.objects.filter( library_id=21, price=Subquery(min_price_subq) ).order_by("name", "price", "id") # 内存层面按书名去重(如果同书名同价需要保留全部则跳过下面这段) seen = set() result = [] for book in filtered_books: if book.name not in seen: seen.add(book.name) result.append(book)
如果需要保留同书名所有最低价的记录,直接使用filtered_books即可,不需要后续内存去重逻辑。
内容的提问来源于stack exchange,提问作者Wood Pecker
相关产品推荐
相关产品推荐

