我有一个单独的文档集合,其中一些文档有一个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
也可以成为父{q} metadata
对象的一部分,如果这更容易的话)
为什么不将文档保存在嵌套结构中?我不想将数据以嵌套格式存储在DB中,因为这些文档是GridFS的一部分。
主要问题是:如何告诉MongoDB嵌套整个文档?或者我是否必须使用Mongo的聚合框架来完成该任务?
答案 0 :(得分:4)
对于你要求的那种“投影”,聚合框架是正确的工具,因为这种“文档重新塑造”只是真正支持它。
另一种情况是“父/子”事物,在使用聚合框架进行分组时,您再次需要“创造性”。完整的操作显示了实质上涉及的内容:
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" }
}}
])
这里的主要内容是分组_id
上使用的$ifNull
运算符。这将在“父”字段中选择$group
,否则使用一般文档_id
。
稍后使用$cond
运算符完成类似的操作,其中评估要添加到数组或“set”的数据。在以下$project
中,使用$setDifference
运算符过滤掉false
值。
如果最终的$sort
和$group
似乎令人困惑,那么实际原因是因为使用的运算符是“set”运算符,结果“set”被认为是无序的。所以真正的那部分就是确保数组内容按照自己的_id
字段的顺序显示。
如果没有MongoDB 2.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" }
}}
])
基本上是相同的,但没有在MongoDB 2.6中引入更新的运算符,所以这也适用于早期版本。
只要您的关系是父级和子级的单一级别,这一切都会很好。对于嵌套级别,您需要调用mapReduce进程。
答案 1 :(得分:0)
我想要一个与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]
}