如何使用MongoDB上的计数创建嵌套聚合?

时间:2014-11-03 19:40:18

标签: mongodb mongodb-query aggregation-framework

我正在学习MongoDB,以确定它是否符合我们的需求。 目前我们使用大量聚合,因此我正在测试聚合框架的灵活性。 我从这个层次结构开始

db.companytest3.insert({"name":"A", age:7})

db.companytest3.insert({"name":"B", age:17, owner:"A"})
db.companytest3.insert({"name":"C", age:12, owner:"A"})
db.companytest3.insert({"name":"D", age:7, owner:"B"})
db.companytest3.insert({"name":"E", age:13, owner:"B"})
db.companytest3.insert({"name":"F", age:23, owner:"C"})

所以我有:

db.companytest3.find()
{ "_id" : ObjectId("5457c2c0fa82c305e0b80006"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2cafa82c305e0b80007"), "name" : "A", "age" : 7 }
{ "_id" : ObjectId("5457c2d0fa82c305e0b80008"), "name" : "B", "age" : 17, "owner" : "A" }
{ "_id" : ObjectId("5457c2d6fa82c305e0b80009"), "name" : "C", "age" : 12, "owner" : "A" }
{ "_id" : ObjectId("5457c2ddfa82c305e0b8000a"), "name" : "D", "age" : 7, "owner" : "B" }
{ "_id" : ObjectId("5457c2e4fa82c305e0b8000b"), "name" : "E", "age" : 13, "owner" : "B" }
{ "_id" : ObjectId("5457c2eafa82c305e0b8000c"), "name" : "F", "age" : 23, "owner" : "C" }

我的目标是使用他们的年龄聚集孩子,所以我有这样的事情:

{
  "_id" : null,
  "children" : [
      {
        "range:" : "lower than 10",
        total: 1,
        names: ["A"]
      }
      {
        "range:" : "higher than 10",
        total: 0,
        names: []
      }
    ],
  "total" : 1
}
{
  "_id" : "A",
  "children" : [
      {
        "range:" : "lower than 10",
        total: 0,
        names: []
      }
      {
        "range:" : "higher than 10",
        total: 2,
        names: ["C","B"]
      }
    ],
  "total" : 1
}
{
  "_id" : "B",
  "children" : [
      {
        "range:" : "lower than 10",
        total: 1,
        names: ["D"]
      }
      {
        "range:" : "higher than 10",
        total: 13,
        names: ["E"]
      }
    ],
  "total" : 1
}
{
  "_id" : "C",
  "children" : [
      {
        "range:" : "lower than 10",
        total: 0,
        names: []
      }
      {
        "range:" : "higher than 10",
        total: 1,
        names: ["F"]
      }
    ],
  "total" : 1
}

我觉得我已经接近了,我已经得到了这个问题:

db.companytest3.aggregate(
{ $project: {
    "_id": 0,
    "range": {
      $concat: [{
        $cond: [ { $lte: ["$age", 10] }, "até 10", "" ]
      }, {
        $cond: [ { $gte: ["$age", 11] }, "mais de 10", "" ]
      }]
    },
    "owner": "$owner",
    "name" : "$name"
  }
},
   { 
    $group: { 
      _id:   { owner: "$owner", range: "$range" }, 
      children: { $addToSet: { name: "$name", range: "$range"} } ,
      total: { $sum: 1} 
    } 
},
   { 
    $group: { 
      _id:   { owner:"$_id.owner" },
      children: { $addToSet: "$children" }
    } 
}
)

给出了以下输出:

{ "_id" : { "owner" : null }, "children" : [ [ { "name" : "A", "range" : "até 10" } ] ] }
{ "_id" : { "owner" : "A" }, "children" : [ [ { "name" : "C", "range" : "mais de 10" }, { "name" : "B", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "B" }, "children" : [ [ { "name" : "D", "range" : "até 10" } ], [ { "name" : "E", "range" : "mais de 10" } ] ] }
{ "_id" : { "owner" : "C" }, "children" : [ [ { "name" : "F", "range" : "mais de 10" } ] ] }

现在我有问题要按照所有者对项目进行分组并保持总和,我被卡住了,我不知道如何继续。我一直在尝试使用群组变体尝试许多不同的替代方案,但我觉得它们不值得在这里发布。

如何更改当前查询,以便按范围对子项进行分组并添加计数?

谢谢! :d

1 个答案:

答案 0 :(得分:1)

在早期版本中应该是可能的,但是基本上看看你想如何操作结果,我能看到的最简单的方法是在MongoDB 2.6中引入的一些运算符的帮助。

db.companytest3.aggregate([
  { "$group": {
    "_id": "$owner",
    "lowerThanTenNames": {
      "$addToSet": {
        "$cond": [
          { "$lte": [ "$age", 10 ] },
          "$name",
          false
        ]
      }
    },
    "lowerThanTenTotal": { 
      "$sum": { 
        "$cond": [
          { "$lte": [ "$age", 10 ] },
          1,
          0
        ]
      }
    },
    "moreThanTenNames": {
      "$addToSet": {
        "$cond": [
          { "$gte": [ "$age", 11 ] },
          "$name",
          false
        ]
      }
    },
    "moreThanTenTotal": {
      "$sum": { 
        "$cond": [
          { "$gte": [ "$age", 11 ] },
          1,
          0
        ]
      }
    }
  }},
  { "$project": {
    "children": {
      "$map": {
        "input": { "$literal": ["L", "M"] },
        "as": "el",
        "in": {
          "$cond": [
            { "$eq": [ "$$el", "L" ] },
            {
              "range": { "$literal": "lower than 10" },
              "total": "$lowerThanTenTotal",
              "names": {
                "$setDifference": [
                  "$lowerThanTenNames",
                  [false]
                ]
              }
            },
            {
              "range": { "$literal": "higher than 10" },
              "total": "$moreThanTenTotal",
              "names": {
                "$setDifference": [
                  "$moreThanTenNames",
                  [false]
                ]
              }
            }
          ]
        }
      }
    },
    "total": { "$add": [ "$lowerThanTenTotal",  "$moreThanTenTotal" ]},
  }},
  { "$sort": { "_id": 1 } }
])

基本上,您希望将每个分组的结果分成两组,每个分组对应一个结果。由于使用了条件运算符,"名称"然后需要对条件不匹配的任何false值进行过滤。

需要做的另一件事是将这些结果从单独的字段强制转换为数组。 $map运算符只需提供一个有效的" A / B"选择进行重新映射。

由于在重新映射到数组之前我们在这里有离散字段,你可以只提供每个"总数"字段作为$add的参数,以获得合计的总数。

产生这个:

{
    "_id" : null,
    "children" : [
        {
            "range" : "lower than 10",
            "total" : 1,
            "names" : ["A"]
        },
        {
            "range" : "higher than 10",
            "total" : 0,
            "names" : [ ]
        }
    ],
    "total" : 1
}
{
    "_id" : "A",
    "children" : [
        {
            "range" : "lower than 10",
            "total" : 0,
            "names" : [ ]
        },
        {
            "range" : "higher than 10",
            "total" : 2,
            "names" : ["C","B"]
        }
    ],
    "total" : 2
}
{
    "_id" : "B",
    "children" : [
        {
            "range" : "lower than 10",
            "total" : 1,
            "names" : ["D"]
        },
        {
            "range" : "higher than 10",
            "total" : 1,
            "names" : ["E"]
        }
    ],
    "total" : 2
}
{
    "_id" : "C",
    "children" : [
        {
            "range" : "lower than 10",
            "total" : 0,
            "names" : [ ]
        },
        {
            "range" : "higher than 10",
            "total" : 1,
            "names" : ["F"]
        }
    ],
    "total" : 1
}