如何用 MongoDB 过滤子文档中的数组

我在子文档中有这样的数组

{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 1
},
{
"a" : 2
},
{
"a" : 3
},
{
"a" : 4
},
{
"a" : 5
}
]
}

我可以过滤 > 3的子文档吗

我的预期结果如下

{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 4
},
{
"a" : 5
}
]
}

我尝试使用 $elemMatch,但返回数组中的第一个匹配元素

我的问题是:

db.test.find( { _id" : ObjectId("512e28984815cbfcb21646a7") }, {
list: {
$elemMatch:
{ a: { $gt:3 }
}
}
} )

结果返回数组中的一个元素

{ "_id" : ObjectId("512e28984815cbfcb21646a7"), "list" : [ { "a" : 4 } ] }

我尝试使用与 $match的聚合,但不工作

db.test.aggregate({$match:{_id:ObjectId("512e28984815cbfcb21646a7"), 'list.a':{$gte:5}  }})

它返回数组中的所有元素

{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 1
},
{
"a" : 2
},
{
"a" : 3
},
{
"a" : 4
},
{
"a" : 5
}
]
}

我可以过滤数组中的元素以得到预期的结果吗?

211182 次浏览

使用 aggregate是正确的方法,但是在应用 $match之前,您需要 $unwind list数组,这样您就可以过滤单个元素,然后使用 $group将其重新组合在一起:

db.test.aggregate([
{ $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
{ $unwind: '$list'},
{ $match: {'list.a': {$gt: 3}}},
{ $group: {_id: '$_id', list: {$push: '$list.a'}}}
])

产出:

{
"result": [
{
"_id": ObjectId("512e28984815cbfcb21646a7"),
"list": [
4,
5
]
}
],
"ok": 1
}

MongoDB 3.2更新

从3.2版本开始,您可以使用新的 $filter聚合操作符,通过在 $project期间只包含您想要的 list元素来更有效地完成这项工作:

db.test.aggregate([
{ $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
{ $project: {
list: {$filter: {
input: '$list',
as: 'item',
cond: {$gt: ['$$item.a', 3]}
}}
}}
])

如果需要多个匹配子文档,以上解决方案效果最好。 如果需要单个匹配的子文档作为输出,$elemMatch 也非常有用

db.test.find({list: {$elemMatch: {a: 1}}}, {'list.$': 1})

结果:

{
"_id": ObjectId("..."),
"list": [{a: 1}]
}

使用 $过滤器聚合

选择要基于指定的 返回一个数组,该数组只包含与 返回的元素按原始顺序排列。

db.test.aggregate([
{$match: {"list.a": {$gt:3}}}, // <-- match only the document which have a matching element
{$project: {
list: {$filter: {
input: "$list",
as: "list",
cond: {$gt: ["$$list.a", 3]} //<-- filter sub-array based on condition
}}
}}
]);