仅检索MongoDB集合中的对象数组中的查询元素
假设您的集合中包含以下文档:
{ "_id":ObjectId("562e7c594c12942f08fe4192"), "shapes":[ { "shape":"square", "color":"blue" }, { "shape":"circle", "color":"red" } ] }, { "_id":ObjectId("562e7c594c12942f08fe4193"), "shapes":[ { "shape":"square", "color":"black" }, { "shape":"circle", "color":"green" } ] }
做查询:
db.test.find({"shapes.color": "red"}, {"shapes.color": 1})
要么
db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})
返回匹配的文档(文档1) ,但始终与shapes
所有数组项目:
{ "shapes": [ {"shape": "square", "color": "blue"}, {"shape": "circle", "color": "red"} ] }
但是,我想只获取包含color=red
的数组的文档(Document 1) :
{ "shapes": [ {"shape": "circle", "color": "red"} ] }
我该怎么做?
MongoDB 2.2新增的$elemMatch
投影算子提供了另一种方法来修改返回的文档,只包含第一个匹配的shapes
元素:
db.test.find( {"shapes.color": "red"}, {_id: 0, shapes: {$elemMatch: {color: "red"}}});
返回:
{"shapes" : [{"shape": "circle", "color": "red"}]}
在2.2中,也可以使用$ projection operator
执行此$ projection operator
,投影对象字段名称中的$
表示查询中字段的第一个匹配数组元素的索引。 以下内容返回与上面相同的结果:
db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});
MongoDB 3.2更新
从3.2版本开始,可以使用新的$filter
聚合运算符在投影过程中过滤数组,这有利于包含所有匹配,而不仅仅是第一个匹配。
db.test.aggregate([ // Get just the docs that contain a shapes element where color is 'red' {$match: {'shapes.color': 'red'}}, {$project: { shapes: {$filter: { input: '$shapes', as: 'shape', cond: {$eq: ['$$shape.color', 'red']} }}, _id: 0 }} ])
结果:
[ { "shapes" : [ { "shape" : "circle", "color" : "red" } ] } ]
MongoDB 2.2+中新的聚合框架提供了Map / Reduce的替代scheme。 $unwind
操作符可用于将shapes
数组分隔成可匹配的文档stream:
db.test.aggregate( // Start with a $match pipeline which can take advantage of an index and limit documents processed { $match : { "shapes.color": "red" }}, { $unwind : "$shapes" }, { $match : { "shapes.color": "red" }} )
结果是:
{ "result" : [ { "_id" : ObjectId("504425059b7c9fa7ec92beec"), "shapes" : { "shape" : "circle", "color" : "red" } } ], "ok" : 1 }
警告:在引入MongoDB 2.2及更高版本的新function之前,此答案提供了当时相关的解决scheme。 如果您使用的是更新版本的MongoDB,请参阅其他答案。
字段select器参数被限制为完整的属性。 它不能用来select数组的一部分,只能select整个数组。 我尝试使用$位置运算符 ,但没有奏效。
最简单的方法就是过滤客户端中的形状。
如果您真的需要从MongoDB直接输出正确的输出,可以使用map-reduce来过滤形状。
function map() { filteredShapes = []; this.shapes.forEach(function (s) { if (s.color === "red") { filteredShapes.push(s); } }); emit(this._id, { shapes: filteredShapes }); } function reduce(key, values) { return values[0]; } res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } }) db[res.result].find()
另一个方法是使用$ redact ,这是MongoDB 2.6的新聚合特性之一。 如果您使用的是2.6,那么如果您有大型数组,则不需要$ unwind,这可能会导致性能问题。
db.test.aggregate([ { $match: { shapes: { $elemMatch: {color: "red"} } }}, { $redact : { $cond: { if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]}, then: "$$DESCEND", else: "$$PRUNE" } }}]);
$redact
“根据存储在文档中的信息限制文档的内容” 。 所以它只会在文件内部运行。 它基本上扫描你的文件顶部到底部,并检查它是否符合$cond
,如果匹配,它将保留内容( $$DESCEND
)或删除( $$PRUNE
)。
在上面的例子中,第一个$match
返回整个shapes
数组,$ redact将其剥离到预期的结果。
请注意, {$not:"$color"}
是必要的,因为它也会扫描顶部文档,如果$redact
没有在顶层find一个color
字段,这将返回false
,这可能会剥离整个文档不要。
更好的是,您可以使用$slice
在匹配的数组元素中进行查询,这有助于返回数组中的重要对象。
db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
当你知道元素的索引时, $slice
是有帮助的,但是有时你想要哪个数组元素符合你的条件。 您可以使用$
运算符返回匹配的元素。
find在MongoDB中的语法是
db.<collection name>.find(query, projection);
和你写的第二个查询,那就是
db.test.find( {shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color":1})
在这里你已经在查询部分使用了$elemMatch
操作符,而如果你在投影部分使用这个操作符,那么你将得到所需的结果。 你可以写下你的查询
db.users.find( {"shapes.color":"red"}, {_id:0, shapes: {$elemMatch : {color: "red"}}})
这会给你想要的结果。
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
产出
{ "shapes" : [ { "shape" : "circle", "color" : "red" } ] }
感谢JohnnyHK 。
这里我只想添加一些更复杂的用法。
// Document { "_id" : 1 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } { "_id" : 2 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } // The Query db.contents.find({ "_id" : ObjectId(1), "shapes.color":"red" },{ "_id": 0, "shapes" :{ "$elemMatch":{ "color" : "red" } } }) //And the Result {"shapes":[ { "shape" : "square", "color" : "red" } ]}
你只需要运行查询
db.test.find( {"shapes.color": "red"}, {shapes: {$elemMatch: {color: "red"}}});
这个查询的输出是
{ "_id" : ObjectId("562e7c594c12942f08fe4192"), "shapes" : [ {"shape" : "circle", "color" : "red"} ] }
正如你所期望的那样,它会给出数组中匹配颜色的确切字段:“红色”。
随着$项目将是更合适的其他明智的匹配元素将与文件中的其他元素一起棍棒。
db.test.aggregate( { "$unwind" : "$shapes" }, { "$match" : { "shapes.color": "red" }}, {"$project":{ "_id":1, "item":1 }} )