在 Mongodb 中查找和聚合多个级别的子文档
Posted
技术标签:
【中文标题】在 Mongodb 中查找和聚合多个级别的子文档【英文标题】:Lookup and aggregate multiple levels of subdocument in Mongodb 【发布时间】:2020-05-29 17:39:49 【问题描述】:我已经使用 $lookup、$unwind 和 $match 尝试了许多类似问题的答案,但我无法让它适用于我的 sub-sub-sub-subdocument 情况。
我有这个收藏,东西:
"_id" : ObjectId("5a7241f7912cfc256468cb27"),
"name" : "Fortress of Solitude",
"alias" : "fortress_of_solitude",
,
"_id" : ObjectId("5a7247ec548c9ad042f579e2"),
"name" : "Batcave",
"alias" : "batcave",
,
"_id" : ObjectId("6a7247bc548c9ad042f579e8"),
"name" : "Oz",
"alias" : "oz",
,
还有这个单文档集合,Venues:
"_id" : ObjectId("5b9acabbbf71f39223f8de6e"),
"name" : "The Office",
"floors" : [
"name" : "1st Floor",
"places" : [
"name" : "Front Entrance",
"alias" : "front_entrance"
]
,
"name" : "2nd Floor",
"places" : [
"name" : "Batcave",
"alias" : "batcave"
,
"name" : "Oz",
"alias" : "oz"
]
]
我想返回所有事物,但如果事物和地点之间的别名匹配,则将地点的 floors.places.name
与每个事物聚合(如果存在)。所以,我想返回:
"_id" : ObjectId("5a7241f7912cfc256468cb27"),
"name" : "Fortress of Solitude",
"alias" : "fortress_of_solitude",
<-- nothing added here because
<-- it's not found in Venues
,
"_id" : ObjectId("5a7247ec548c9ad042f579e2"),
"name" : "Batcave",
"alias" : "batcave",
"floors" : [ <-- this should be
<-- returned
"places" : [ <-- because
<-- the alias
name" : "Batcave" <-- matches
<-- in Venues
] <--
<--
] <--
,
"_id" : ObjectId("6a7247bc548c9ad042f579e8"),
"name" : "Oz",
"alias" : "oz",
"floors" : [ <-- this should be
<-- returned
"places" : [ <-- because
<-- the alias
name" : "Oz" <-- matches
<-- in Venues
] <--
<--
] <--
我已经了解了以下查询,但它只返回整个 Venues.floors 数组作为每个事物的聚合,这聚合了太多无关的数据。我只想将 Venues 中的每个相关 floor.place 子子文档合并到其对应的 Thing 中(如果它存在于 Venues 中)。
db.getCollection('things').aggregate([
$lookup: from: "venues",localField: "alias",foreignField: "floors.places.alias",as: "matches",
$replaceRoot: newRoot: $mergeObjects: [ $arrayElemAt: [ "$matches", 0 ] , "$$ROOT" ]
,
$project: matches: 0
])
我正在为现有答案苦苦挣扎,这些答案似乎在 MongoDB 版本 3.2、3.4、3.6 或 4.2 中发生了变化,包括或不包括 $unwind、$pipeline 和其他术语。有人可以解释如何获得像这样聚合的子子子文档吗?谢谢!
【问题讨论】:
【参考方案1】:从 MongoDB v3.6 开始,我们可以执行uncorrelated sub-queries,这让我们可以更灵活地加入两个集合。
试试这个:
db.things.aggregate([
$lookup:
from: "venues",
let:
"alias": "$alias"
,
pipeline: [
$unwind: "$floors"
,
$project:
_id: 0,
places:
$filter:
input: "$floors.places",
cond:
$eq: [
"$$alias",
"$$this.alias"
]
,
$match:
"places.0":
$exists: true
,
$unset: "places.name"
],
as: "floors"
])
MongoPlayground
【讨论】:
谢谢!您的回答使 floor 成为新的根,并且大部分都有效,但它返回每个结果中的所有位置(如果它们都在场地中定义,它会返回 floor.places 中的堡垒和蝙蝠洞),所以我与使用 addFields 和 group 来摆脱无关的地方的答案一起去。不过,您的解决方案显示了其他 userul 位。 (我更新了示例数据,在场地中也列出了要塞,这样你就可以明白我的意思了)。 添加了 Oz,实际上是为了显示无关的地方输出。 @RealHandy 请再试一次,我已经更新了管道条件 看起来你的更新让places[] 完全消失了,所以它只是floors[ name: Batcave ] 而不是floors[ places: [ name: Batcave ] ] @RealHandy 再次检查:)【参考方案2】:你可以试试这个:
db.things.aggregate([
$lookup:
from: "venues",
let: alias: "$alias" ,
pipeline: [
$unwind: path: "$floors", preserveNullAndEmptyArrays: true ,
$match: $expr: $in: ['$$alias', '$floors.places.alias'] ,
/** Below stages are only if you've docs like doc 2 in Venues */
$addFields: 'floors.places': $filter: input: '$floors.places', cond: $eq: ['$$this.alias', '$$alias'] ,
$group: _id: '$_id', name: $first: '$name' , floors: $push: '$floors' ,
$project : 'floors.places.alias': 1, _id :0 // Optional
],
as: "matches"
])
测试: MongoDB-Playground
【讨论】:
我喜欢在@Valijon 答案中使用 replaceRoot 来使 floor 成为附加数据的根,而不是匹配。我的几次添加尝试都没有奏效(我对这些 mongodb 语法很陌生)。不过,除此之外,您的答案确实提供了我所希望的,即删除所有无关的楼层和位置数据。谢谢! @RealHandy :当你说将附加数据的根放在底层而不是匹配时,我有点困惑(你确实将floors
带到了顶层但是如果你有两层楼怎么办?)请针对所有当前问题提出另一个问题并在此处标记它,我们当然可以帮助你..
让我试着澄清一下(和很多人一样,我希望你能格式化 cmets)。另一个解决方案有 floor[],仍然是一个数组,作为添加到输出中每个事物的子文档。因此,如果说,电梯 1 是一个事物并且存在于场地的两个楼层,那么电梯 1 将有楼层,添加两个元素的阵列。在您的解决方案中,matches[0] 是附加数据的根。只有一个场地,所以matches[]中总是只有一个元素。但是,如果我添加了第二个场地,其中也有一个电梯 1,那么我需要你的 match[] 来显示两个场地。
我只是喜欢从附加输出的根目录中删除matches[],因为在我的场景中只有一个匹配(地点)。
我不确定最终是否需要比名称更多的字段,但我很清楚如何对您的解决方案进行这些编辑。以上是关于在 Mongodb 中查找和聚合多个级别的子文档的主要内容,如果未能解决你的问题,请参考以下文章
mongodb Aggregation聚合操作之$facet
MongoDB,如何将查找和排序与聚合中的 $cond 结合起来?