You need to enable JavaScript to run this app. 如何使用annotate与Min实现Django QuerySet去重并取同书名最低价
优惠活动
大模型
产品
解决方案
定价
更多

如何使用annotate与Min实现Django QuerySet去重并取同书名最低价

问题根源
  1. 缺少按书名字段分组的逻辑:直接对全量Book对象执行annotate时,聚合函数Min/Count是针对单条记录本身计算,因此每条记录的min_price都等于自身价格,过滤条件等于未生效,才会返回重复书名的结果。
  2. 存在字段名笔误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 06:27:04