$min/$max 必须配合$group使用,因它们是累加器操作符,需先定义分组;单独用于$project会报错;处理数组极值需先$unwind;多极值应合并至同一$group避免重复扫描。

为什么 $min/$max 在聚合管道里不直接返回单个值?
因为 $min 和 $max 是累加器(accumulator)操作符,必须配合 $group 使用,不能单独写在 $project 或查询条件里。直接写 { $min: "$price" } 会报错 "unknown operator: $min" —— 这是最常见的误用起点。
它们的作用是「对一组文档的某个字段做归约」,所以天然需要先定义“哪一组”。哪怕你只想查整个集合的极值,也得显式用 $group: { _id: null } 构造一个组。
- 正确姿势:
{$group: {_id: null, minPrice: {$min: "$price"}, maxPrice: {$max: "$price"}}} - 错误姿势:
{$project: {minPrice: {$min: "$price"}}}→ 报错 - 如果字段可能为
null或缺失,$min/$max会跳过这些值(不是当作 0 或空字符串处理)
怎么同时获取极值和对应文档的其他字段?
单纯极值数字往往不够,比如你想知道“最贵商品的名字”,但 $min/$max 只返回字段值,不保留原始文档上下文。这时候不能只靠 $min,得用 $sort + $limit 或 $first/$last 配合 $group。
例如查价格最高商品的完整信息:
[
{ $sort: { price: -1 } },
{ $limit: 1 }
]
或者用聚合变量保序(MongoDB 5.0+):
[
{ $setWindowFields: {
sortBy: { price: -1 },
output: { rank: { $rank: {} } }
}
},
{ $match: { rank: 1 } }
]
-
$min/$max本身不支持“连带返回同文档其他字段”,这是设计限制 - 若数据量大且频繁查极值+详情,建议在应用层分两步:先用
$group拿到极值,再用find({ price: 极值 })查文档 - 注意:多个文档并列极值时,
$sort+$limit只返回其中一个,而$min/$max仍只返回那个值
在 $facet 里用 $min/$max 会不会重复扫描集合?
会。每个 $facet 分支都是独立执行的聚合流水线,$min 和 $max 各自触发一次全集合扫描(或索引扫描)。如果集合大、QPS高,这很伤性能。
- 避免写成:
{$facet: {min: [{$group: {_id: null, v: {$min: "$x"}}}], max: [{$group: {_id: null, v: {$max: "$x"}}}]}} - 更优解:合并到一条流水线,一次算出两个值:
{$group: {_id: null, minV: {$min: "$x"}, maxV: {$max: "$x"}}} - 如果还要算平均值、计数等,全部塞进同一个
$group,别拆开 - 确保
$min/$max字段上有索引(如{ price: 1 }),否则全表扫描不可避免
数组字段里的 $min/$max 怎么用?
直接写 $min: "$tags" 不会报错,但结果可能不是你想要的——它把整个数组当做一个值比较(按 BSON 数组比较规则),而不是展开数组取元素极值。
要求数组内元素的极值,必须先用 $unwind 展开,再 $group:
[
{ $unwind: "$scores" },
{ $group: { _id: null, minScore: { $min: "$scores" }, maxScore: { $max: "$scores" } } }
]
- 如果数组为空或全是
null,$unwind后无输出,$group结果为空文档 - 想保留空数组的极值为
null,得加$ifNull或预处理:{$addFields: {scores: {$ifNull: ["$scores", []]}}} -
$min对字符串数组按字典序比较,对混合类型数组按 BSON 类型优先级排序(比如数字
实际用的时候,先想清楚你要的是“字段值的极值”还是“文档的极值”,前者用 $min/$max,后者得靠排序或窗口函数;再检查字段是否存在、是否被索引、是否需展开数组——这些细节比语法本身更容易导致结果偏差。











