2013-05-19 44 views
3

鉴于这种MongoDB的集合:是否可以使用MongoDB的聚合框架对多列进行分组和汇总?

[ 
    { character: 'broquaint', race: 'Halfling', class: 'Hunter' }, 
    { character: 'broquaint', race: 'Halfling', class: 'Hunter' }, 
    { character: 'broquaint', race: 'Halfling', class: 'Rogue' }, 
    { character: 'broquaint', race: 'Naga',  class: 'Fighter' }, 
    { character: 'broquaint', race: 'Naga',  class: 'Hunter' } 
] 

我想获得每个种族和阶级即

{ 
    race: { 'Halfling': 3, 'Naga': 2 }, 
    class: { 'Hunter': 3, 'Rogue': 1, 'Fighter': 1 } 
} 

的计数,并且我一直在尝试这种使用聚合框架(以 做取代现有的地图/减少),但只能得到至今为 获得组合计数,即

{ '_id': { race: 'Halfling', class: 'Hunter' }, count: 2 } 
{ '_id': { race: 'Halfling', class: 'Rogue' } count: 1 } 
{ '_id': { race: 'Naga',  class: 'Fighter' }, count: 1 } 
{ '_id': { race: 'Naga',  class: 'Hunter' }, count: 1 } 

这很简单,以编程方式减少到期望的 结果,但我希望能够将它留给MongoDB。

仅供参考这里的代码,我到目前为止有:

db.games.aggregate(
    { '$match': { character: 'broquaint' } }, 
    { 
    '$group': { 
     _id: { race: '$race', background: '$background'}, 
     count: { '$sum': 1 } 
    } 
    } 
) 

所以,问题是 - 考虑到例如收集我可以在我的 所需的输出到达完全通过MongoDB的聚合框架?

对于任何可能提前致谢的帮助!

+0

什么是“$背景”?类? –

+0

对不起,在代码中它应该读取“类”而不是“背景”,列名实际上是“背景”,但我选择“类”为简洁起见我只是失败的一致性。 – broquaint

回答

2

是的,你可以用聚合框架来做到这一点。这不会是漂亮,但随后还是会比使用MapReduce的快多了......

这简而言之(输出不同的格式比你给什么,但相同的内容):

> group1 = { 
    "$group" : { 
     "_id" : "$race", 
     "class" : { 
      "$push" : "$class" 
     }, 
     "count" : { 
      "$sum" : 1 
     } 
    } 
}; 
> unwind = { "$unwind" : "$class" }; 
> group2 = { 
    "$group" : { 
     "_id" : "$class", 
     "classCount" : { 
      "$sum" : 1 
     }, 
     "races" : { 
      "$push" : { 
       "race" : "$_id", 
       "raceCount" : "$count" 
      } 
     } 
    } 
}; 
> unwind2 = { "$unwind" : "$races" }; 
> group3 ={ 
    "$group" : { 
     "_id" : 1, 
     "classes" : { 
      "$addToSet" : { 
       "class" : "$_id", 
       "classCount" : "$classCount" 
      } 
     }, 
     "races" : { 
      "$addToSet" : "$races" 
     } 
    } 
}; 
> db.races.aggregate(group1, unwind, group2, unwind2, group3); 
{ 
    "result" : [ 
     { 
      "_id" : 1, 
      "classes" : [ 
       { 
        "class" : "Fighter", 
        "classCount" : 1 
       }, 
       { 
        "class" : "Hunter", 
        "classCount" : 3 
       }, 
       { 
        "class" : "Rogue", 
        "classCount" : 1 
       } 
      ], 
      "races" : [ 
       { 
        "race" : "Naga", 
        "raceCount" : 2 
       }, 
       { 
        "race" : "Halfling", 
        "raceCount" : 3 
       } 
      ] 
     } 
    ], 
    "ok" : 1 
} 
+0

如果您知道可能的比赛和课程的完整集合,则可能会有不同的答案 - 在这种情况下,可以轻松生成您的问题中的确切格式。 –

+0

当我终于准备发布答案时,我看到你已经......并且快40分钟......并且减少了两个命令。 ;) – vinipsmaker

+1

只要叫我聚合框架的主人。呃,我的意思是女主人。如果可以用它来做...我可能已经做到了。 –

2

从MongoDB 3.4开始,使用多个聚合流水线可以实现更简单一些,其中包括$facet。单级内

$小

处理多个聚合管道上 同一组输入文件:

docs拍摄。每个子流水线在 的输出文档中都有自己的字段,其结果存储为 文档的数组。

const aggregatorOpts = [ 
    { $match: { character: 'broquaint' } }, // Match the character 
    { 
     // Seperate into 2 or more pipes that will count class and 
     // race seperatly 
     $facet: { 
      race: [ 
       // Group by race and get the count: 
       // [ 
       // { 
       //  _id: 'Halfling', 
       //  count: 3 
       // } 
       // { 
       //  _id: 'Naga', 
       //  count: 2 
       // } 
       // ] 

       // $sortByCount is the same as 
       // { $group: { _id: <expression>, count: { $sum: 1 } } }, 
       // { $sort: { count: -1 } } 

       { $sortByCount: '$race' }, 

       // Now we want to transform the array in to 1 document, 
       // where the '_id' field is the key, and the 'count' is the value. 
       // To achieve this we will use $arrayToObject. According the the 
       // docs, we have to first rename the fields to 'k' for the key, 
       // and 'v' for the value. We use $project for this: 
       { 
        $project: { 
         _id: 0, 
         k: '$_id', 
         v: '$count', 
        }, 
       }, 
      ], 
      // Same as above but for class instead 
      class: [ 
       { $sortByCount: '$class' }, 
       { 
        $project: { 
         _id: 0, 
         k: '$_id', 
         v: '$count', 
        }, 
       }, 
      ], 
     }, 
    }, 
    { 
     // Now apply the $arrayToObject for both class and race. 
     $addFields: { 
      // Will override the existing class and race arrays 
      // with their respective object representation instead. 
      class: { $arrayToObject: '$class' }, 
      race: { $arrayToObject: '$race' }, 
     }, 
    }, 
]; 

db.races.aggregate(aggregatorOpts) 

将会产生如下:

[ 
    { 
    "race": { 
     "Halfling": 3, 
     "Naga": 2 
    }, 
    "class": { 
     "Hunter": 3, 
     "Rogue": 1, 
     "Fighter": 1, 
    } 
    } 
] 

如果你很高兴与格式化提供输出@

所以,为您的使用情况下,这将通过以下实现Asya,那么你可以删除$project$addFields阶段,并且将$sortByCount部分留在每个子流水线中。

有了这些新功能,使用附加计数可以更容易地进行聚合, 只需在$facet中添加另一个聚合管道即可。 计算子组更容易,但这将是一个单独的问题。