使用MongoDB投影嵌套整个文档?
-
21-12-2019 - |
题
我有一个文件的平面集合,其中一些文件有一个 parent: ObjectId
字段,它指向同一集合中的另一个文档,即:
{id: 1, metadata: {text: "I'm a parent"}}
{id: 2, metadata: {text: "I'm child 1", parent: 1}}
现在我想找回来 所有家长在哪里 metadata.text = "I'm a parent"
加上它的子元素.但是我希望这些数据采用嵌套格式,所以我可以在之后简单地处理它,而无需查看 metadata.parent
.输出应该是这样的:
{
id: 1,
metadata: {text: "I'm a parent"},
children: [
{id: 2, metadata: {text: "I'm child 1", parent: 1}}
]
}
(children
也可能是父母的一部分 metadata
(如果这更容易)
为什么不将文档保存在嵌套结构中? 我不想将数据以嵌套格式存储在DB中,因为这些文档是GridFS的一部分。
主要问题是: 我怎样才能告诉MongoDB嵌套整个文档?或者我必须使用Mongo的聚合框架来完成该任务?
解决方案
对于你所要求的那种"投影",那么聚合框架是正确的工具,因为这种"文档重新塑造"只在那里得到真正的支持。
另一种情况是"父/子"的事情,在使用聚合框架进行分组时,您再次需要"创造性"。完整的操作显示了本质上涉及的内容:
db.collection.aggregate([
// Group parent and children together with conditionals
{ "$group": {
"_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
"metadata": {
"$addToSet": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
false,
"$metadata"
]
}
},
"children": {
"$push": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
"$$ROOT",
false
]
}
}
}},
// Filter out "false" values
{ "$project": {
"metadata": { "$setDifference": [ "$metadata", [false] ] },
"children": { "$setDifference": [ "$children", [false] ] }
}},
// metadata is an array but should only have one item
{ "$unwind": "$metadata" },
// This is essentially sorting the children as "sets" are un-ordered
{ "$unwind": "$children" },
{ "$sort": { "_id": 1, "children._id": 1 } },
{ "$group": {
"_id": "$_id",
"metadata": { "$first": "$metadata" },
"children": { "$push": "$children" }
}}
])
这里主要的是 $ifNull
分组上使用的运算符 _id
.这将选择 $group
在存在的"父"字段上,否则使用通用文档 _id
.
类似的事情是用 $cond
运算符稍后,其中评估是由哪些数据添加到数组或"设置"。在以下 $project
该 false
值通过使用过滤掉 $setDifference
接线员。
如果决赛 $sort
和 $group
似乎令人困惑,那么实际的原因是因为使用的运算符是"set"运算符所得到的"set"被认为是未排序的。所以实际上,这部分只是为了确保数组内容按自己的顺序出现 _id
场。
如果没有来自MongoDB2.6的额外运算符,这仍然可以完成,但只是有点不同。
db.collection.aggregate([
{ "$group": {
"_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
"metadata": {
"$addToSet": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
false,
"$metadata"
]
}
},
"children": {
"$push": {
"$cond": [
{ "$ifNull": [ "$metadata.parent", false ] },
{ "_id": "$_id","metadata": "$metadata" },
false
]
}
}
}},
{ "$unwind": "$metadata" },
{ "$match": { "metadata": { "$ne": false } } },
{ "$unwind": "$children" },
{ "$match": { "children": { "$ne": false } } },
{ "$sort": { "_id": 1, "children._id": 1 } },
{ "$group": {
"_id": "$_id",
"metadata": { "$first": "$metadata" },
"children": { "$push": "$children" }
}}
])
本质上是同样的事情,但没有在MongoDB2.6中引入的较新的运算符,所以这也适用于早期版本。
只要你的关系是父母和孩子的单一层次,这一切都会很好。对于嵌套级别,您需要调用mapReduce进程。
其他提示
我想要一个类似于Neil Lunn的答案的结果,除了我想获取所有父母,无论他们是否有孩子。我还想将其概括为适用于任何具有单个嵌套子级的集合。
这是我根据Neil Lunn的答案提出的查询
db.collection.aggregate([
{
$group: {
_id: {
$ifNull: ["$parent", "$_id"]
},
parent: {
$addToSet: {
$cond: [
{
$ifNull: ["$parent", false]
}, false, "$$ROOT"
]
}
},
children: {
$push: {
$cond: [
{
$ifNull: ["$parent", false]
}, "$$ROOT", false
]
}
}
}
}, {
$project: {
parent: {
$setDifference: ["$parent", [false]]
},
children: {
$setDifference: ["$children", [false]]
}
}
}, {
$unwind: "$parent"
}
])
这将导致返回每个父级,其中父级字段包含整个父级文档,如果父级没有子级或子级文档数组,则返回子级字段。
{
_id: PARENT_ID
parent: PARENT_OBJECT
children: [CHILD_OBJECTS]
}