【问题标题】:Mongodb query for 2 level groupingMongodb查询2级分组
【发布时间】:2014-04-11 01:06:33
【问题描述】:

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

{
    "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 上执行此操作,而不是 mongoose 或类似的东西。

【问题讨论】:

    标签: mongodb mapreduce aggregation-framework


    【解决方案1】:

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

    因此,要获得结果,您需要在 JavaScript 中工作,例如 mapReduce:

    首先定义一个映射器:

    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"
                                }
                        }
                }
        ],
    

    【讨论】:

      【解决方案2】:

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

      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 的出色回答和奉献精神。

      马塞尔

      【讨论】:

        猜你喜欢
        • 2015-10-09
        • 2023-03-09
        • 2017-05-11
        • 2018-05-16
        • 1970-01-01
        • 1970-01-01
        • 2014-03-05
        • 2017-06-28
        • 2021-05-19
        相关资源
        最近更新 更多