【问题标题】:How to Implement $bucket to group by multiple fields如何实现 $bucket 以按多个字段分组
【发布时间】:2017-06-11 07:13:12
【问题描述】:

起初bucket 按年龄和boundaries[0,20,30,40,50,200]

db.user.aggregate(
    {$project: {_id:0, age:{$subtract:[{$year:new Date()}, {$year:"$birthDay"}]} } },
    {$bucket:{
        groupBy:"$age",
        boundaries:[0,20,30,40,50,200]
    }},
    { $project:{ _id:0,age:"$_id",count:1 } }
)

得到以下结果

{ "count" : 5, "age" : 20 }
{ "count" : 1, "age" : 30 }

然后我想进一步统计每个城市的每个年龄范围

{ city : "SH", age: 20, count: 2 }
{ city : "BJ", age: 20, count: 3 }
{ city : "BJ", age: 30, count: 1 }

那么在这种情况下如何实现呢?

另外

db.user.aggregate(
    { $project: {_id:0, city:1, age:{$subtract:[{$year:new Date()}, {$year:"$birthDay"}]} } },
    { $group: { _id:"$city",ages:{$push:"$age"} } },
    { $project: {_id:0, city:"$_id",ages:1} }
)

{ "city" : "SH", "ages" : [ 26, 26 ] }
{ "city" : "BJ", "ages" : [ 27, 26, 26, 36 ] }

【问题讨论】:

  • 源文档是什么样的?可以使用样本和您想要获得的结果的清晰图片。

标签: mongodb aggregation-framework


【解决方案1】:

你所说的实际上是用$switch 实现的,在一个普通的$group 阶段:

db.user.aggregate([
  { "$group": {
    "_id": {
      "city": "$city",
      "age": {
        "$let": {
          "vars": { 
            "age": { "$subtract" :[{ "$year": new Date() },{ "$year": "$birthDay" }] }
          },
          "in": {
            "$switch": {
              "branches": [
                { "case": { "$lt": [ "$$age", 20 ] }, "then": 0 },
                { "case": { "$lt": [ "$$age", 30 ] }, "then": 20 },
                { "case": { "$lt": [ "$$age", 40 ] }, "then": 30 },
                { "case": { "$lt": [ "$$age", 50 ] }, "then": 40 },
                { "case": { "$lt": [ "$$age", 200 ] }, "then": 50 }
              ]
            }
          }
        }
      }
    },
    "count": { "$sum": 1 }
  }}
])

有了结果:

{ "_id" : { "city" : "BJ", "age" : 30 }, "count" : 1 }
{ "_id" : { "city" : "BJ", "age" : 20 }, "count" : 3 }
{ "_id" : { "city" : "SH", "age" : 20 }, "count" : 2 }

$bucket 管道阶段仅采用单个字段路径。您可以通过"output" 选项拥有多个累加器,但"groupBy" 是单个表达式。

请注意,您也可以在此处使用$let 而不是单独的$project 管道阶段来计算“年龄”。

N.B 如果您实际上向$bucket 抛出了一些错误的表达式,您将收到关于$switch 的错误,这应该会提示您这是内部实现的方式。


如果您担心$switch 中的编码,那么只需生成它:

var ranges = [0,20,30,40,50,200];
var branches = [];
for ( var i=1; i < ranges.length; i++) {
  branches.push({ "case": { "$lt": [ "$$age", ranges[i] ] }, "then": ranges[i-1] });
}

db.user.aggregate([
  { "$group": {
    "_id": {
      "city": "$city",
      "age": {
        "$let": {
          "vars": {
            "age": { 
              "$subtract": [{ "$year": new Date() },{ "$year": "$birthDay" }]
            }
          },
          "in": {
            "$switch": { "branches": branches }
          }
        }
      }
    },
    "count": { "$sum": 1 }
  }}
])

【讨论】:

  • 谢谢!我也是用 Map-Reduce 来实现的,我觉得在某些情况下,比如有很多分支,这种 map-reduce 方式更可取。请看下面我的回答
  • @zhguuowei 可能不是一个好主意。与本机操作相比,Map reduce 的运行速度要慢很多。而您需要做的就是使用相同类型的for 循环在此处生成$switch 中的条件。事实上,我差点把它写进去,但基本上是在等你问。
  • @zhguuowei 其实加了。简单的例子,我认为你没有意识到它有多么简单。
  • 是的,你是对的。只需比较for loopswitch,switch 只需比较一次,但for loop 平均需要array.length / 2 次比较。我不知道 c++ 是否可以有一些 JIT 像 java 可以对热代码做一些优化
  • @zhguuowei 我认为你在这里跑题了,需要做一些阅读。这就是答案中的链接的用途。阅读它们
【解决方案2】:

使用Map-Reduce 提供另一个实现

db.user.mapReduce(
    function(){
        var age = new Date().getFullYear() - this.birthDay.getFullYear();
        var ages = [0,20,30,40,50,200]
        for(var i=1; i<ages.length; i++){
            if(age < ages[i]){
                emit({city:this.city,age:ages[i-1]},1);
                break;
            }
        }        
    },
    function(key, counts){
        return Array.sum(counts);
    },
    { out: "user_city_age_count" }
)

【讨论】:

    猜你喜欢
    • 2018-11-28
    • 2013-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-20
    相关资源
    最近更新 更多