在聚合中对多个值进行分组

时间:2015-04-30 22:11:41

标签: mongodb mongodb-query aggregation-framework

我想将一个集合的所有字段分组为唯一总计。我们假设有这样的集合:

 id  country state operator
 121  IN       HR    AIRTEL
 212  IN       MH    AIRTEL
 213  US       LA    AT&T
 214  UK       JK    VODAFONE        

输出应该是这样的:

{
    "country": { "IN": 2, "US":1, "UK":1 }, 
    "state": { "HR":1, "MH":1, "LA":1, "JK": 1 }, 
    "operator": { "AIRTEL":2, "AT&T": 1, "VODAFONE": 1 }
}

我正在尝试使用mongo聚合框架,但是真的不能想到如何做到这一点?

2 个答案:

答案 0 :(得分:1)

我在代码

下使用聚合检查找到了一些与您的输出相似的内容
db.collectionName.aggregate({
  "$group": {
    "_id": null,
    "countryOfIN": {
      "$sum": {
        "$cond": [{
          $eq: ["$country", "IN"]
        }, 1, 0]
      }
    },
    "countryOfUK": {
      "$sum": {
        "$cond": [{
          $eq: ["$country", "UK"]
        }, 1, 0]
      }
    },
    "countryOfUS": {
      "$sum": {
        "$cond": [{
          $eq: ["$country", "US"]
        }, 1, 0]
      }
    },
    "stateOfHR": {
      "$sum": {
        "$cond": [{
          $eq: ["$state", "HR"]
        }, 1, 0]
      }
    },
    "stateOfMH": {
      "$sum": {
        "$cond": [{
          $eq: ["$state", "MH"]
        }, 1, 0]
      }
    },
    "stateOfLA": {
      "$sum": {
        "$cond": [{
          $eq: ["$state", "LA"]
        }, 1, 0]
      }
    },
    "stateOfJK": {
      "$sum": {
        "$cond": [{
          $eq: ["$state", "JK"]
        }, 1, 0]
      }
    },
    "operatorOfAIRTEL": {
      "$sum": {
        "$cond": [{
          $eq: ["$operator", "AIRTEL"]
        }, 1, 0]
      }
    },
    "operatorOfAT&T": {
      "$sum": {
        "$cond": [{
          $eq: ["$operator", "AT&T"]
        }, 1, 0]
      }
    },
    "operatorOfVODAFONE": {
      "$sum": {
        "$cond": [{
          $eq: ["$operator", "VODAFONE"]
        }, 1, 0]
      }
    }
  }
}, {
  "$group": {
    "_id": null,
    "country": {
      "$push": {
        "IN": "$countryOfIN",
        "UK": "$countryOfUK",
        "US": "$countryOfUS"
      }
    },
    "STATE": {
      "$push": {
        "HR": "$stateOfHR",
        "MH": "$stateOfMH",
        "LA": "$stateOfLA",
        "JK": "$stateOfJK"
      }
    },
    "operator": {
      "$push": {
        "AIRTEL": "$operatorOfAIRTEL",
        "AT&T": "$operatorOfAT&T",
        "VODAFONE": "$operatorOfVODAFONE"
      }
    }
  }
}, {
  "$project": {
    "_id": 0,
    "country": 1,
    "STATE": 1,
    "operator": 1
  }
})

使用$cond创建的匹配数据组,并将它们推送到第二组进行组合。

答案 1 :(得分:1)

您正在寻找的输出格式并不适合聚合框架,因为您正在将部分数据转换为" key"名。聚合框架不是这样做,而是坚持数据库"最佳实践"因为没有改变"数据"到"关键"以任何方式命名。

您可以执行mapReduce操作,但允许更灵活的操作,但由于需要使用JavaScript代码来执行操作,因此效果不佳:

db.collection.mapReduce(
  function () {
    var obj = {},
        doc = this;

    delete doc._id;
    Object.keys(doc).forEach(function(key) {
      obj[key] = {};
      obj[key][doc[key]] = 1;
    });
    emit( null, obj );
  },
  function (key,values) {
    var result = {};

    values.forEach(function(value) {
      Object.keys(value).forEach(function(outerKey) {
        Object.keys(value[outerKey]).forEach(function(innerKey) {
          if ( !result.hasOwnProperty(outerKey) ) {
            result[outerKey] = {};
          }
          if ( result[outerKey].hasOwnProperty(innerKey) ) {
            result[outerKey][innerKey] += value[outerKey][innerKey];
          } else {
            result[outerKey][innerKey] = value[outerKey][innerKey];
          }
        });
      });
    });

    return result;
  },
  { "out": { "inline": 1 } }
)

在适用于所有mapReduce结果的结构中:

{
    "results" : [
            {
                    "_id" : null,
                    "value" : {
                            "country" : {
                                    "IN" : 2,
                                    "US" : 1,
                                    "UK" : 1
                            },
                            "state" : {
                                    "HR" : 1,
                                    "MH" : 1,
                                    "LA" : 1,
                                    "JK" : 1
                            },
                            "operator" : {
                                    "AIRTEL" : 2,
                                    "AT&T" : 1,
                                    "VODAFONE" : 1
                            }
                    }
            }
    ]
}

对于聚合框架本身,它更适合生成结构更一致的聚合结果:

db.mapex.aggregate([
    { "$project": {
        "country": 1,
        "state": 1,
        "operator": 1,
        "type": { "$literal": ["country","state","operator"] }
    }},
    { "$unwind": "$type" },
    { "$group": {
        "_id": {
           "type": "$type",
           "key": { "$cond": {
               "if": { "$eq": [ "$type", "country" ] },
               "then": "$country",
               "else": { "$cond": {
                   "if": { "$eq": [ "$type", "state" ] },
                   "then": "$state",
                   "else": "$operator"
               }}
           }}
        },
        "count": { "$sum": 1 }
    }}
])

哪个会输出:

{ "_id" : { "type" : "state", "key" : "JK" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "UK" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "US" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "AT&T" }, "count" : 1 }
{ "_id" : { "type" : "state", "key" : "LA" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "AIRTEL" }, "count" : 2 }
{ "_id" : { "type" : "state", "key" : "MH" }, "count" : 1 }
{ "_id" : { "type" : "state", "key" : "HR" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "VODAFONE" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "IN" }, "count" : 2 }

但是在迭代结果时很容易在客户端代码中进行转换:

var result = {};

db.mapex.aggregate([
    { "$project": {
        "country": 1,
        "state": 1,
        "operator": 1,
        "type": { "$literal": ["country","state","operator"] }
    }},
    { "$unwind": "$type" },
    { "$group": {
        "_id": {
           "type": "$type",
           "key": { "$cond": {
               "if": { "$eq": [ "$type", "country" ] },
               "then": "$country",
               "else": { "$cond": {
                   "if": { "$eq": [ "$type", "state" ] },
                   "then": "$state",
                   "else": "$operator"
               }}
           }}
        },
        "count": { "$sum": 1 }
    }}
]).forEach(function(doc) {
    if ( !result.hasOwnProperty(doc._id.type) )
        result[doc._id.type] = {};
    result[doc._id.type][doc._id.key] = doc.count;
})

其中给出了最终结构"结果":

{
    "state" : {
            "JK" : 1,
            "LA" : 1,
            "MH" : 1,
            "HR" : 1
    },
    "country" : {
            "UK" : 1,
            "US" : 1,
            "IN" : 2
    },
    "operator" : {
            "AT&T" : 1,
            "AIRTEL" : 2,
            "VODAFONE" : 1
    }
}