Mongodb查询2级分组

时间:2014-04-11 01:06:33

标签: mongodb mapreduce aggregation-framework

假设我有文件,其中包括以下字段:

{
    "class" : String,
    "type" : String,
    "name" : String,
}

例如,许多人喜欢这样:

{
    "class": "class A",
    "type": "type 1",
    "Name": "ObjectA1"
}

{
    "class": "class A",
    "type": "type 2",
    "Name": "ObjectA2_1"
}

{
    "class": "class A",
    "type": "type 2",
    "Name": "ObjectA2_2"
}

{  
    "class": "class B ",
    "type": "type 3",
    "Name": "ObjectB3"
}

我想要的是一个返回以下结构的查询

{
    "class A" : {
        "type 1" : ["ObjectA1"],
        "type 2" : ["ObjectA2_1", "ObjectA2_2"]
    },
    "class B" : {
        "type 3" : ["ObjectB3"]
    } 
}

我尝试将聚合与$ group一起使用,但无法执行此操作。有什么想法吗?

PS:我想在mongodb shell上做这个,而不是像猫鼬一样。

2 个答案:

答案 0 :(得分:1)

使用聚合框架的问题是您无法为对象的属性指定任意键名。因此,如果不能指定所有可能的密钥名称,那么使用它就不可能进行整形。

因此,要获得结果,您需要在JavaScript中使用mapReduce

首先定义一个mapper:

var mapper = function () {

  var key = this["class"];
  delete this._id;
  delete this["class"];

  emit( key, this );

};

然后是减速器:

var reducer = function (key, values) {

  var reducedObj = {};

  values.forEach(function(value) {
    if ( !reducedObj.hasOwnProperty(value.type) )
      reducedObj[value.type] = [];

    reducedObj[value.type].push( value.Name );

  });

  return reducedObj;
};

并且因为您(至少在您的样本中)可能只使用1个键值从映射器发出的项目,您还需要一个finalize函数:

var finalize = function (key,value) {

    if ( value.hasOwnProperty("name") ) {
        value[value.type] = value.name;
        delete value.type;
        delete value.name;
    }

    return value;
};

然后按如下方式调用mapReduce函数:

db.collection.mapReduce(
    mapper,
    reducer,
   { "out": { "inline": 1 }, "finalize": finalize }
)

这给出了以下输出:

    "results" : [
            {
                    "_id" : "class A",
                    "value" : {
                            "type 1" : [
                                    "ObjectA1"
                            ],
                            "type 2" : [
                                    "ObjectA2_1",
                                    "ObjectA2_2"
                            ]
                    }
            },
            {
                    "_id" : "class B ",
                    "value" : {
                            "type" : "type 3",
                            "Name" : "ObjectB3"
                    }
            }
    ],

虽然结果是以非常mapReduce方式格式化的,但它与结果大致相同。

但如果你真的想进一步采取这种做法,你可以随时做到以下几点:

定义另一个映射器:

var mapper2 = function () {
    emit( null, this );
};

另一个减速器:

var reducer2 = function (key,values) {

  reducedObj = {};

  values.forEach(function(value) {
    reducedObj[value._id] = value.value;
  });

  return reducedObj;

};

然后将第一个mapReduce与输出一起运行到新集合:

db.collection.mapReduce(
    mapper,
    reducer,
   { "out": { "replace": "newcollection" }, "finalize": finalize }
)

接下来是新集合上的第二个mapReduce:

db.newcollection.mapReduce(
    mapper2,
    reducer2,
   { "out": { "inline": 1 } }
)

结果是:

    "results" : [
            {
                    "_id" : null,
                    "value" : {
                            "class A" : {
                                    "type 1" : [
                                            "ObjectA1"
                                    ],
                                    "type 2" : [
                                            "ObjectA2_1",
                                            "ObjectA2_2"
                                    ]
                            },
                            "class B " : {
                                    "type" : "type 3",
                                    "Name" : "ObjectB3"
                            }
                    }
            }
    ],

答案 1 :(得分:0)

我找到了我需要的解决方法。它不一样,但解决了我的问题。

db.myDb.aggregate(
{
    $group:{
        _id: {
            class_name : "$class", 
            type_name : "$name"
        }, 
        items: {
            $addToSet : "$name"
        }
    }
}, 
{
    $group:{
        _id : "$_id.class_name", 
        types : {
            $addToSet : {
                type : "$_id.type_name", 
                items : "$items"
            }
        }
    }
})
这给了我类似的东西:

{
    _id : "class A",
    types: [
        {
            type: "type 1",
            items: ["ObjectA1"]
        },
        {
            type: "type 2",
            items: ["ObjectA2_1", "ObjectA2_2"]
        }
    ]
},
{
    _id : "class B",
    types: [
        {
            type: "type 3",
            items: ["ObjectB3"]
        }
    ]
}

代码和示例都写在这里,因此可能存在拼写错误。

所以这是关于它的。我要感谢#Neil Lunn的精彩回答和奉献精神。

马塞尔