说白了就是,如果你的数组里有一堆元素,你想根据条件只保留一部分,用 `$filter` 是最清爽的。它不会把整个文档结构拆散,也不会触发 `$unwind` 那种额外的运算开销,而且结果依然是个数组,后续怎么处理都方便。 用 $filter 在 $project 阶段做数组筛选,是目前最轻量、最可控
说白了就是,如果你的数组里有一堆元素,你想根据条件只保留一部分,用 `$filter` 是最清爽的。它不会把整个文档结构拆散,也不会触发 `$unwind` 那种额外的运算开销,而且结果依然是个数组,后续怎么处理都方便。

用 $filter 在 $project 阶段做数组筛选,是目前最轻量、最可控的方式。它不改变文档结构,不触发数组展开,也不需要额外的 $unwind 开销。说白了就是,如果你的数组里有一堆元素,你想根据条件只保留一部分,用 $filter 是最清爽的。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
$elemMatch 投影?你可能会想,为什么不用 $elemMatch 投影呢?它看起来多简单啊!但 $elemMatch 有个坑:它只返回数组中「第一个」匹配的元素,就算有 5 个符合条件的子文档,它也只会吐出一个。这其实是个「匹配+截断」操作,不是真正的「过滤+保留」。如果你后续要遍历所有匹配项,或者统计匹配的数量,用 $elemMatch 就会掩盖真实的数据分布。比如你写 { "items": { "$elemMatch": { "price": { "$gt": 100 } } } },结果里 items 字段永远是单个对象,不是数组。所以,场合不对,别乱用。
$filter 必须配合 $project 使用要注意,$filter 是聚合操作符,不能单独出现在 find() 的 projection 参数里。它只能嵌在聚合管道的 $project、$addFields 或其他支持表达式的 stage 中。错误的写法是 db.collection.find({}, { items: { $filter: { ... } } }),这会导致报错 unknown operator: $filter。正确的写法是 db.collection.aggregate([ { $project: { items: { $filter: { input: "$items", cond: { $gt: ["$$this.price", 100] } } } } } ])。注意,$$this 是默认的变量名,你也可以显式地写 as: "item",然后用 $$item.price,这样更清晰。
$filter 的 input 字段必须是数组,否则报错这是很容易翻车的地方。如果字段不存在、值为 null,或者值是字符串、数字等非数组类型,$filter 会直接中断整个 pipeline 并抛出错误,它不是静默跳过。安全做法是先用 $ifNull 或 $cond 兜底,比如 input: { $ifNull: ["$items", []] }。调试的时候可以加 $type 检查,确认返回的是 "array"。另外,MongoDB 6.0+ 还支持 limit 参数,如果你想只取前 3 个匹配项,可以写 limit: 3。
$filter 本身不走索引,它是在内存中对已加载的数组做遍历。所以真正影响性能的不是 $filter 本身有多慢,而是有多少文档被拉进了 pipeline。关键的一步是:务必在 $match 阶段先缩小文档集,比如加一个 { "items.price": { "$gt": 100 } } 的条件,这样能命中索引,减少进入后续管道的文档数量。
兼容性方面,$filter 在 MongoDB Community 4.2+、Atlas 所有版本、Enterprise 4.0+ 均可用,基本覆盖了大部分生产环境,不用特意升级到最新版。另外,不要在 $filter 的 cond 里调用复杂的函数(比如 $regex)处理大量文本,这会显著拖慢响应。
最后,有一个容易被忽略的点:你写的 $filter 表达式,它的作用域仅限于当前数组字段。它没法访问同级的其他字段,更没法跨文档引用。如果业务需要根据父文档的动态状态来决定子数组的筛选条件,那就得换用 $map + $cond 的组合,或者提前把逻辑下沉到应用层去处理。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述