MongoDB匹配指定bookId后如何仅返回匹配嵌套项的指定属性
MongoDB嵌套数组定向过滤查询方案
问题背景
现有一个名为books的MongoDB集合,单条文档结构示例如下:
{ "_id" : ObjectId("62bf10951fecaed4dba275b1"), "name" : "Library 1", "positions" : [ { "number" : 2, "nodes" : [ { "number" : 2, "bookId" : "6254674d3711f90bd8e76036" }, { "number" : 1, "bookId" : "621e9b5aa7951d0be4516c18" } ] }, { "number" : 1, "nodes" : [ { "number" : 1, "bookId" : "6254674d3711f90bd8e76037" }, { "number" : 3, "bookId" : "6254674d3711f90bd8e76039" }, { "number" : 2, "bookId" : "6254674d3711f90bd8e76035" } ] } ] }
需求为根据传入的bookId查询,返回结果仅保留文档name字段、匹配项对应的positions.number、匹配节点对应的positions.nodes.number字段。例如查询bookId = 6254674d3711f90bd8e76035时,期望返回结构如下:
{ "_id": ObjectId("62bf10951fecaed4dba275b1"), "name": "Library 1", "positions": { "number": 1, "nodes": { "number": 2 } } }
原有查询写法如下,执行后会返回positions数组下所有nodes节点,无法过滤出仅匹配目标bookId的嵌套项:
db.getCollection('books').aggregate([ { $match: { "positions.nodes.bookId": "6254674d3711f90bd8e76035" } }, { $project: { name: 1, "positions.number": 1, "positions.nodes.number": 1 } } ])
核心问题:直接用点号投影嵌套数组字段时,MongoDB会返回数组中所有元素的对应投影字段,不会自动按嵌套条件过滤数组元素,需要手动通过数组操作符逐层过滤。
可行实现方案
以下聚合写法兼容MongoDB 3.2及以上版本,可实现预期的过滤效果:
// 替换为实际要查询的bookId const targetBookId = "6254674d3711f90bd8e76035" db.getCollection('books').aggregate([ // 第一步:粗筛包含目标bookId的文档,减少后续计算量 { $match: { "positions.nodes.bookId": targetBookId } }, // 第二步:逐层过滤两层嵌套数组 { $project: { name: 1, positions: { $map: { // 过滤外层positions数组,仅保留包含目标bookId的position项 input: { $filter: { input: "$positions", cond: { $in: [targetBookId, "$$this.nodes.bookId"] } } }, as: "posItem", in: { number: "$$posItem.number", // 过滤内层nodes数组,仅保留bookId匹配的节点 nodes: { $map: { input: { $filter: { input: "$$posItem.nodes", cond: { $eq: ["$$this.bookId", targetBookId] } } }, as: "nodeItem", in: { number: "$$nodeItem.number" } } } } } } } }, // 过滤后positions、nodes均为长度为1的数组,拆解为对象结构匹配预期返回格式 { $unwind: "$positions" }, { $unwind: "$positions.nodes" } ])
逻辑说明
$filter操作符用于按条件过滤数组元素,这里先后作用于外层positions数组和内层nodes数组,逐层剔除不满足条件的项$map操作符用于对过滤后的数组做投影,只保留需要的number字段,去掉无关的bookId等冗余字段- 两次
$unwind将过滤后长度为1的数组拆解为单对象,最终返回结构和需求完全一致 - 如果使用MongoDB 3.4及以上版本,可替换为
$indexOfArray先定位匹配元素的下标再直接取值,相比全数组遍历过滤性能更优。
内容的提问来源于stack exchange,提问作者KeeperOfTheSevenKeys
相关产品推荐
相关产品推荐

