在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中的ALL数组项:

{ "shapes":
[
{"shape": "square", "color": "blue"},
{"shape": "circle", "color": "red"}
]
}

但是,我希望只使用包含color=red的数组来获取文档文档(1):

{ "shapes":
[
{"shape": "circle", "color": "red"}
]
}

我该怎么做呢?

438607 次浏览

这个答案提供了一个相关的解决方案,在MongoDB 2.2及更高版本的新特性被引入之前。如果您使用的是最新版本的MongoDB,请参阅其他答案。

字段选择器参数仅限于完整的属性。它不能用于选择数组的一部分,只能用于选择整个数组。我尝试使用$位置运算符,但那不工作。

最简单的方法是过滤形状在客户端

如果你真的需要正确的直接从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()

MongoDB 2.2+中的新聚合框架提供了Map/Reduce的替代方案。$unwind操作符可以用来将你的shapes数组分离成一个可以匹配的文档流:

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新的$elemMatch投影运算符提供了另一种方法来修改返回的文档,使其只包含首先< em > < / em >匹配的shapes元素:

db.test.find(
{"shapes.color": "red"},
{_id: 0, shapes: {$elemMatch: {color: "red"}}});

返回:

{"shapes" : [{"shape": "circle", "color": "red"}]}

在2.2中,也可以使用$ 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"
}
]
}
]

$project一起使用更合适的其他匹配元素将与文档中的其他元素组合在一起。

db.test.aggregate(
{ "$unwind" : "$shapes" },
{ "$match" : { "shapes.color": "red" } },
{
"$project": {
"_id":1,
"item":1
}
}
)

mongodb中的find语法是

    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"}}})

这会给你想要的结果。

感谢< a href = " https://stackoverflow.com/users/1259510/johnnyhk " > 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"
}
]}

另一种有趣的方法是使用< >强劲美元修订< / >强,这是MongoDB 2.6的新聚合特性之一。如果您使用的是2.6,则不需要$unwind,如果您使用的是大型数组,$unwind可能会导致性能问题。

db.test.aggregate([
{ $match: {
shapes: { $elemMatch: {color: "red"} }
}},
{ $redact : {
$cond: {
if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
then: "$$DESCEND",
else: "$$PRUNE"
}
}}]);

$redact “根据存储在文件本身中的信息来限制文件的内容”。所以它只会运行文档内部。它基本上从头到尾扫描你的文档,并检查它是否与你的$cond中的if条件匹配,如果有匹配,它将保留内容($$DESCEND)或删除内容($$PRUNE)。

在上面的例子中,首先$match返回整个shapes数组,$ react将其剥离到预期的结果。

注意,{$not:"$color"}是必要的,因为它也会扫描顶部的文档,如果$redact在顶层没有找到color字段,这将返回false,这可能会剥离整个文档,这是我们不想要的。

更好的方法是使用$slice在匹配的数组元素中查询,它有助于返回数组中的重要对象。

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

$slice在你知道元素的索引时是有用的,但有时你想要 匹配条件的数组元素。您可以返回匹配的元素

.使用$操作符

你只需要运行query

db.test.find(
{"shapes.color": "red"},
{shapes: {$elemMatch: {color: "red"}}});

此查询的输出为

{
"_id" : ObjectId("562e7c594c12942f08fe4192"),
"shapes" : [
{"shape" : "circle", "color" : "red"}
]
}

正如你所期望的那样,它会从数组中给出匹配颜色的精确字段:'red'。

 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

输出

{


"shapes" : [
{
"shape" : "circle",
"color" : "red"
}
]
}
db.test.find( {"shapes.color": "red"}, {_id: 0})

使用聚合函数和$project来获取文档中的特定对象字段

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

结果:

{
"_id" : ObjectId("5e3ee15968879c0d5942464b"),
"geolocation" : [
{
"_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
"latitude" : 12.9718313,
"longitude" : 77.593551,
"country" : "India",
"city" : "Chennai",
"zipcode" : "560001",
"streetName" : "Sidney Road",
"countryCode" : "in",
"ip" : "116.75.115.248",
"date" : ISODate("2020-02-08T16:38:06.584Z")
}
]
}

同样,你也可以求出倍数

db.getCollection('localData').aggregate([
// Get just the docs that contain a shapes element where color is 'red'
{$match: {'shapes.color': {$in : ['red','yellow'] } }},
{$project: {
shapes: {$filter: {
input: '$shapes',
as: 'shape',
cond: {$in: ['$$shape.color', ['red', 'yellow']]}
}}
}}
])

虽然这个问题是9.6年前问的,但这对很多人都有很大的帮助,我就是其中之一。感谢大家的提问、提示和回答。从这里的一个答案中…我发现下面的方法也可以用来投影父文档中的其他字段。这可能对某些人有帮助。

对于下面的文档,需要查明员工(emp #7839)是否将其休假历史设置为2020年。休假历史记录被实现为父雇员文档中的嵌入式文档。

db.employees.find( {"leave_history.calendar_year": 2020},
{leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()




{
"_id" : ObjectId("5e907ad23997181dde06e8fc"),
"empno" : 7839,
"ename" : "KING",
"mgrno" : 0,
"hiredate" : "1990-05-09",
"sal" : 100000,
"deptno" : {
"_id" : ObjectId("5e9065f53997181dde06e8f8")
},
"username" : "none",
"password" : "none",
"is_admin" : "N",
"is_approver" : "Y",
"is_manager" : "Y",
"user_role" : "AP",
"admin_approval_received" : "Y",
"active" : "Y",
"created_date" : "2020-04-10",
"updated_date" : "2020-04-10",
"application_usage_log" : [
{
"logged_in_as" : "AP",
"log_in_date" : "2020-04-10"
},
{
"logged_in_as" : "EM",
"log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
}
],
"leave_history" : [
{
"calendar_year" : 2020,
"pl_used" : 0,
"cl_used" : 0,
"sl_used" : 0
},
{
"calendar_year" : 2021,
"pl_used" : 0,
"cl_used" : 0,
"sl_used" : 0
}
]
}

如果你想同时做筛选、设置和查找

let post = await Post.findOneAndUpdate(
{
_id: req.params.id,
tasks: {
$elemMatch: {
id: req.params.jobId,
date,
},
},
},
{
$set: {
'jobs.$[i].performer': performer,
'jobs.$[i].status': status,
'jobs.$[i].type': type,
},
},
{
arrayFilters: [
{
'i.id': req.params.jobId,
},
],
new: true,
}
);

这个答案没有完全回答问题,但它是相关的,我把它写下来,因为有人决定关闭另一个问题,将这个标记为重复(这不是)。

在我的例子中,我只想过滤数组元素,但仍然返回数组的完整元素。所有之前的答案(包括问题中给出的解决方案)在应用到我的特定情况时都让我头疼,因为:

  • 我需要我的解决方案能够返回子数组元素的多个结果
  • 使用$unwind + $match + $group会导致根文档丢失而没有匹配数组元素,在我的例子中,我不想这样做,因为实际上我只是想过滤不需要的元素。
  • 使用$project >$filter导致丢失其余的字段或根文档,或迫使我在投影中指定所有这些字段,这是不可取的。

所以最后我用$addFields >$filter像这样:

db.test.aggregate([
{ $match: { 'shapes.color': 'red' } },
{ $addFields: { 'shapes': { $filter: {
input: '$shapes',
as: 'shape',
cond: { $eq: ['$$shape.color', 'red'] }
} } } },
])

解释:

  1. 首先将文件与红色形状匹配。
  2. 对于这些文档,添加一个名为shapes的字段,在这种情况下,它将以同样的方式替换原来的字段。
  3. 为了计算shapes的新值,$filter原始$shapes数组的元素,临时将每个数组元素命名为shape,以便稍后我们可以检查$$shape.color是否为红色。
  4. 现在新的shapes数组只包含所需的元素。