在MongoDB的实际业务中,数组字段几乎无处不在:一篇文章的标签列表、一个用户的订单记录、一件商品的评论集合,都是以数组形式存储的。当我们只想取出数组中满足某些条件的元素时,直接查询会返回整个数组,显然不够灵活。聚合管道中的$filter操作符正是处理这类需求的利器,它可以在管道的任意阶段对数组做条件过滤,只保留符合条件的元素,输出一个全新的数组。

$filter的基本语法与参数说明
$filter的语法结构并不复杂,核心是三个参数:input指定要过滤的数组字段或数组表达式,as定义一个变量名用来代表当前遍历的数组元素,cond则是过滤条件表达式。完整写法如下:
{
$filter: {
input: <array>,
as: <variable>,
cond: <expression>,
limit: <number>
}
}其中as是可选参数,如果不写,默认的变量名是this。但在实际项目中强烈建议显式指定,因为cond中引用这个变量时需要加上$$前缀,写成$$变量名.字段的形式,显式命名能让表达式可读性大幅提升。limit参数是MongoDB 5.2之后才引入的,用来限制返回的元素数量,老版本中要实现同样的效果得配合$slice使用。
来看一个最基础的例子。假设有一个商品集合,每个商品有多个价格记录,我们只想取出低于100元的记录:
db.products.aggregate([
{
$project: {
name: 1,
cheapPrices: {
$filter: {
input: "$prices",
as: "price",
cond: { $lt: ["$$price", 100] }
}
}
}
}
])这里$$price引用的就是被遍历到的每一个价格元素。注意$filter只是个表达式操作符,不能单独作为管道阶段使用,必须放在$project、$addFields、$set等阶段内部,这一点初学者经常搞混。
处理对象数组与嵌套数组的实战技巧
真实业务中的数组元素往往不是简单数值,而是嵌套文档。比如订单集合里每个订单包含多个商品明细,想过滤出数量大于2的明细,就需要在cond里访问元素的子字段:
db.orders.aggregate([
{
$addFields: {
hotItems: {
$filter: {
input: "$items",
as: "item",
cond: { $gt: ["$$item.quantity", 2] }
}
}
}
}
])当数组元素本身还包含数组时,$filter同样可以嵌套使用。例如评论集合中每条评论有多个回复,每个回复又有多个点赞用户,想找出回复中包含指定用户的评论,可以把内层$filter的返回值配合$size或$gt做条件判断:
db.comments.aggregate([
{
$project: {
content: 1,
targetReplies: {
$filter: {
input: "$replies",
as: "reply",
cond: {
$gt: [
{
$size: {
$filter: {
input: "$$reply.likes",
as: "user",
cond: { $eq: ["$$user", "user_001"] }
}
}
},
0
]
}
}
}
}
}
])嵌套写法虽然强大,但可读性会明显下降。建议在条件复杂时分两步处理:先用$addFields算出中间结果,再做外层过滤,这样排查问题会轻松很多。另外要注意,如果input指定的字段不存在,$filter会返回空结果;如果字段值不是数组,则会直接报错,遇到数据格式不统一的情况,可以先用$cond判断$type再决定是否过滤。
$filter与$elemMatch、$unwind的方案对比
同样是处理数组条件,很多人分不清什么时候用$filter,什么时候用查询阶段的$elemMatch或者管道中的$unwind。三者的定位其实完全不同,选错了不仅效率低,还可能得到错误的结果。
$elemMatch用在find查询或$match阶段,作用是筛选文档,返回的是完整文档和完整数组,它不能裁剪数组内容。而$filter作用在表达式层面,输出的是裁剪后的数组,不负责筛选文档。比如要找出至少有一条价格低于100的商品记录,用$elemMatch做查询条件更合适;而要在结果中只展示低价记录,就得靠$filter。两者经常配合使用:先用$match加$elemMatch缩小文档范围,再用$filter精修数组字段。
$unwind则是把数组拆成多条独立文档,适合后续需要按数组元素做统计聚合的场景,比如统计每种商品卖出的总数。它的代价是文档数量膨胀,拆分后再用$group重组的开销不小。如果只是想在返回结果里过滤数组元素,根本不需要拆文档,$filter一步到位,性能也更好。简单总结:筛文档用$elemMatch,裁数组用$filter,按元素统计用$unwind。
常见坑点与注意事项
第一个坑是变量引用符号。$$双美元符引用的是as定义的变量,而$单美元符引用的是文档字段,写混了会导致字段解析不到或者报错。第二个坑是cond里如果要同时引用当前遍历元素和文档其他字段,写法是$$item.xxx配合$docField,两者前缀不同,不能混淆。
第三个坑是空数组与null的处理。$filter对空数组返回空数组,对null或缺失字段返回null,如果不希望结果里出现null,可以在外层套一个$ifNull给默认值:{ $ifNull: [filteredResult, []] }。第四个坑是索引问题,$filter作为聚合表达式无法利用索引,数据量大时务必在管道前面的$match阶段先用索引字段过滤,把参与运算的文档量降下来,否则聚合会非常慢。
掌握这些细节后,$filter基本能覆盖日常开发中绝大多数数组裁剪需求。把它和$map、$reduce、$slice等数组表达式组合使用,还能进一步实现数组转换、汇总、截取等复杂操作,让聚合管道真正发挥出文档型数据库的灵活优势。
MongoDB聚合管道$filter数组过滤修改时间:2026-09-08 14:02:58