使用MongoDB投影嵌套整个文档?

时间:2014-07-29 14:13:11

标签: mongodb mongodb-query aggregation-framework

我有一个单独的文档集合,其中一些文档有一个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的聚合框架来完成该任务?

2 个答案:

答案 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]
}