假设您在我的收藏中有以下文档:
{
"_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})
返回匹配的文档 (Document 1),但始终包含 shapes
中的所有数组项:
{ "shapes":
[
{"shape": "square", "color": "blue"},
{"shape": "circle", "color": "red"}
]
}
但是,我希望仅使用包含 color=red
的数组来获取文档 (Document 1):
{ "shapes":
[
{"shape": "circle", "color": "red"}
]
}
我怎样才能做到这一点?
MongoDB 2.2 的新 $elemMatch
投影运算符提供了另一种方法来更改返回的文档以仅包含 first 匹配的 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"
}
]
}
]
MongoDB 2.2+ 中的新 Aggregation Framework 提供了 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
}
$elemMatch
是另一种选择。我实际上是通过 Google Group question 到达这里的,其中 $elemMatch 不起作用,因为它只返回每个文档的第一个匹配项。
{ $project : { shapes : 1 } }
- 如果包含的文档很大并且您只想查看 shapes
键值,这似乎有效并且会很有帮助。
警告:在 MongoDB 2.2 及更高版本的新功能引入之前,此答案提供了当时相关的解决方案。如果您使用的是更新版本的 MongoDB,请参阅其他答案。
字段选择器参数仅限于完整的属性。它不能用于选择数组的一部分,只能选择整个数组。我尝试使用 $ positional operator,但没有奏效。
最简单的方法是只过滤客户端中的形状。
如果你真的需要直接从 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
中的 if
条件匹配,如果匹配,它将保留内容($$DESCEND
)或删除($$PRUNE
) .
在上面的示例中,第一个 $match
返回整个 shapes
数组,然后 $redact 将其剥离到预期的结果。
请注意,{$not:"$color"}
是必需的,因为它也会扫描顶层文档,如果 $redact
在顶层找不到 color
字段,这将返回 false
,这可能会删除我们没有的整个文档不想。
$match
作为第一个聚合阶段的原因
您可以更好地使用 $slice
查询匹配的数组元素,这有助于返回数组中的重要对象。
db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
当您知道元素的索引时,$slice
会很有帮助,但有时您需要与您的条件匹配的数组元素。您可以使用 $
运算符返回匹配元素。
shapes.color : blue
的文档还是只返回第一个?
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
输出
{
"shapes" : [
{
"shape" : "circle",
"color" : "red"
}
]
}
在 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"}}})
这将为您提供所需的结果。
"shapes.color":"red"
似乎不是必需的。您可以将其替换为 {}
并获得相同的结果。
Patient.find( { user: req.user._id, _id: req.params.patientId, "tests.test": req.params.testId, }, { "tests.$": 1, name: 1, } ) .populate({ path: "tests", populate: { path: "test", model: "Test", }, }) .exec((err, patient) => { if (err || !patient) { return res.status(404).send({ error: { message: err } }); } return res.send({ patient }); });
但随后填充抛出错误
感谢 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"}
]
}
如您所料,它将给出与颜色匹配的数组中的确切字段:'red'。
与 $project
一起,将其他明智匹配的元素与文档中的其他元素组合在一起会更合适。
db.test.aggregate(
{ "$unwind" : "$shapes" },
{ "$match" : { "shapes.color": "red" } },
{
"$project": {
"_id":1,
"item":1
}
}
)
同样,您可以找到多个
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']]}
}}
}}
])
$match
减少空间,然后 $filter
保留您想要的内容,覆盖输入字段(在字段 shapes
上使用 $filter
的输出到 $project
回到 shapes
。样式注意:最好不要将字段名称用作 as
参数,因为这可能会导致以后与 $$shape
和 $shape
混淆。我更喜欢 zz
作为 as
字段因为它真的很突出。
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")
}
]
}
尽管这个问题是在 9.6 年前提出的,但这对很多人都有巨大的帮助,我就是其中之一。谢谢大家的所有疑问,提示和答案。从这里的一个答案中挑选。我发现以下方法也可以用于投影父文档中的其他字段。这可能对某人有所帮助。
对于以下文档,需要确定员工 (emp #7839) 是否设置了 2020 年的休假历史记录。休假历史记录是作为父 Employee 文档中的嵌入文档实现的。
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,
}
);
aggregate
的其他答案之一。db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});