仅检索 MongoDB 集合中对象数组中的查询元素
Posted
技术标签:
【中文标题】仅检索 MongoDB 集合中对象数组中的查询元素【英文标题】:Retrieve only the queried element in an object array in MongoDB collection 【发布时间】:2018-04-13 16:16:37 【问题描述】:假设我的收藏中有以下文档:
"_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"
]
我该怎么做?
【问题讨论】:
【参考方案1】:警告:此答案提供了一个相关的解决方案,当时,在引入 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()
【讨论】:
【参考方案2】: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
【讨论】:
@JohnnyHK:在这种情况下,$elemMatch
是另一种选择。我实际上是通过Google Group question 到达这里的,其中 $elemMatch 不起作用,因为它只返回每个文档的第一个匹配项。
谢谢,我不知道这个限制,所以很高兴知道。很抱歉删除了您正在回复的评论,我决定发布另一个答案,并且不想让人们感到困惑。
@JohnnyHK:别担心,这个问题现在有三个有用的答案;-)
对于其他搜索者,除此之外,我还尝试添加 $project : shapes : 1
- 如果随附的文档很大并且您只想查看 shapes
键值,这似乎很有帮助.
@calmbird 我更新了示例以包含初始 $match 阶段。如果您对更有效的功能建议感兴趣,我会在 MongoDB 问题跟踪器中观看/支持 SERVER-6612: Support projecting multiple array values in a projection like the $elemMatch projection specifier。【参考方案3】:
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"
]
]
【讨论】:
如果我希望它返回匹配它的每个元素而不是第一个元素,任何解决方案? 恐怕我正在使用 Mongo 3.0.X :-( @charliebrownie 然后使用使用aggregate
的其他答案之一。
这也有效:db.test.find(, shapes: $elemMatch: color: "red");
这是一个错误:$$shape.color? $filter 条件下的双 $$。【参考方案4】:
与$project
一起,将其他明智匹配的元素与文档中的其他元素合并在一起会更合适。
db.test.aggregate(
"$unwind" : "$shapes" ,
"$match" : "shapes.color": "red" ,
"$project":
"_id":1,
"item":1
)
【讨论】:
您能描述一下这是通过输入和输出集完成的吗?【参考方案5】:在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")
这会给你想要的结果。
【讨论】:
这对我有用。但是,查询参数(find 方法的第一个参数)中的"shapes.color":"red"
似乎不是必需的。您可以将其替换为
并获得相同的结果。
@ErikOlson 您的建议在上述情况下是正确的,我们需要找到所有带有红色的文档并仅在它们上应用投影。但是,假设有人需要找出所有具有蓝色的文档,但它应该只返回该形状数组中具有红色的那些元素。在这种情况下,上面的查询也可以被其他人引用..
这似乎是最简单的,但我无法让它发挥作用。它只返回第一个匹配的子文档。
@MahmoodHussain 这个答案已经快 7 年了,所以可能是版本问题。你能检查最新的文档。我将尝试在最新版本上运行类似并分享我的发现。你能解释一下你到底想达到什么目标吗?
@Vicky 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 ); );
但是随后填充抛出错误【参考方案6】:
感谢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"
]
【讨论】:
【参考方案7】:另一个有趣的方式是使用$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
,这可能会删除整个文档这是我们不想要的。
【讨论】:
完美答案。正如你提到的 $unwind 会消耗大量的内存。所以比较起来会更好。 我有一个疑问。在示例中,“shapes”是一个数组。 “$redact”会扫描“shapes”数组中的所有对象吗??这对性能有什么好处?? 不是全部,而是你第一次比赛的结果。这就是您将$match
作为您的第一个聚合阶段的原因
okkk.. 如果在“颜色”字段上创建索引,即使这样它也会扫描“形状”数组中的所有对象??? 这可能是匹配数组中多个对象的有效方法???
太棒了!我不明白 $eq 在这里是如何工作的。我最初把它关掉了,这对我不起作用。不知何故,它在形状数组中查找匹配项,但查询从不指定要查找的数组。例如,如果文档有形状,例如大小; $eq 会在两个数组中查找匹配项吗? $redact 是否只是在文档中查找与“if”条件匹配的任何内容?【参考方案8】:
您可以使用$slice
更好地查询匹配的数组元素是否有助于返回数组中的重要对象。
db.test.find("shapes.color" : "blue", "shapes.$" : 1)
$slice
在您知道元素的索引时很有帮助,但有时您需要
无论哪个数组元素符合您的条件。您可以返回匹配的元素
使用$
运算符。
【讨论】:
返回所有包含shapes.color : blue
的文档还是只返回第一个?【参考方案9】:
你只需要运行查询
db.test.find(
"shapes.color": "red",
shapes: $elemMatch: color: "red");
这个查询的输出是
"_id" : ObjectId("562e7c594c12942f08fe4192"),
"shapes" : [
"shape" : "circle", "color" : "red"
]
如您所料,它将从数组中提供与 color:'red' 匹配的确切字段。
【讨论】:
【参考方案10】: db.getCollection('aj').find("shapes.color":"red","shapes.$":1)
输出
"shapes" : [
"shape" : "circle",
"color" : "red"
]
【讨论】:
感谢您的查询,但即使条件与数组中的多个元素匹配,它也只是返回第一个,有什么建议吗?【参考方案11】:db.test.find( "shapes.color": "red", _id: 0)
【讨论】:
欢迎来到 Stack Overflow!感谢您提供代码 sn-p,它可能会提供一些有限的即时帮助。通过描述为什么这是解决问题的好方法,正确的解释将极大地改进其long-term value,并使其对有其他类似问题的未来读者更有用。请编辑您的答案以添加一些解释,包括您所做的假设。【参考方案12】:使用聚合函数和$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")
]
【讨论】:
【参考方案13】:同样你可以找到倍数
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']]
])
【讨论】:
这个答案确实是首选的 4.x 方式:$match
减少空间,然后$filter
保留您想要的内容,覆盖输入字段(使用 $filter
的输出字段shapes
到$project
回到shapes
。样式注意:最好不要使用字段名称作为as
参数,因为这可能会导致以后与$$shape
和$shape
混淆。我更喜欢@ 987654331@ 作为as
字段,因为它真的很突出。【参考方案14】:
虽然这个问题是在 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
]
【讨论】:
【参考方案15】:如果您想同时进行过滤、设置和查找。
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,
);
【讨论】:
以上是关于仅检索 MongoDB 集合中对象数组中的查询元素的主要内容,如果未能解决你的问题,请参考以下文章
mongodb - 如果数组中的某个元素与查询匹配,则忽略文档