MongoDB组并以id作为键

时间:2015-11-03 16:15:59

标签: mongodb mongodb-query aggregation-framework

是否可以将聚合函数的结果作为key:count

示例:

我有以下聚合查询:

db.users.aggregate([
  {
    $group: {
      _id: "$role",
      count: {
        $sum: 1
      }
    }
  }
])

所以结果如下:

{ "_id" : "moderator", "count" : 469 }
{ "_id" : "superadmin", "count" : 1 }
{ "_id" : "user", "count" : 2238 }
{ "_id" : "admin", "count" : 11 }

所以这一切都很好,但有没有一种方法(可能使用$project)使结果看起来像这样(即以role为关键点和{{1} }作为值):

count

我可以通过使用JS对结果进行后处理来实现这一点,但我的目标是直接通过聚合函数来实现。

2 个答案:

答案 0 :(得分:3)

使用MongoDb 3.6及更新版本,您可以利用 $arrayToObject 运算符和 $replaceRoot 管道来获得所需的结果。您需要运行以下聚合管道:

db.users.aggregate([
    { 
        "$group": {
            "_id": { "$toLower": "$role" },
            "count": { "$sum": 1 }
        }
    },
    { 
        "$group": {
            "_id": null,
            "counts": {
                "$push": {
                    "k": "$_id",
                    "v": "$count"
                }
            }
        }
    },
    { 
        "$replaceRoot": {
            "newRoot": { "$arrayToObject": "$counts" }
        } 
    }    
])

对于旧版本,$cond管道步骤中的$group运算符可以有效地用于根据角色字段值评估计数。您可以按如下方式构建整体聚合管道,以便以所需格式生成结果:

db.users.aggregate([    
    { 
        "$group": { 
            "_id": null,             
            "moderator_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$role", "moderator" ] }, 1, 0 ]
                }
            },
            "superadmin_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$role", "superadmin" ] }, 1, 0 ]
                }
            },
            "user_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$role", "user" ] }, 1, 0 ]
                }
            },
            "admin_count": {
                "$sum": {
                    "$cond": [ { "$eq": [ "$role", "admin" ] }, 1, 0 ]
                }
            } 
        }  
    },
    {
        "$project": {
            "_id": 0, 
            "moderator": "$moderator_count",
            "superadmin": "$superadmin_count",
            "user": "$user_count",
            "admin": "$admin_count"
        }
    }
])

在评论路径中,如果您事先不知道角色并想要动态创建管道阵列,请在角色字段上运行 distinct 命令。这将为您提供一个包含不同角色列表的对象:

var result = db.runCommand ( { distinct: "users", key: "role" } )
var roles = result.values;
printjson(roles); // this will print ["moderator", "superadmin", "user",  "admin"]

现在给出上面的列表,您可以通过创建一个使用JavaScript的 reduce() 方法设置其属性的对象来组装管道。以下内容证明了这一点:

var groupObj = { "_id": null },
    projectObj = { "_id": 0 }

var groupPipeline = roles.reduce(function(obj, role) { // set the group pipeline object 
    obj[role + "_count"] = {
        "$sum": {
            "$cond": [ { "$eq": [ "$role", role ] }, 1, 0 ]
        }
    };
    return obj;
}, groupObj );

var projectPipeline = roles.reduce(function(obj, role) { // set the project pipeline object 
    obj[role] = "$" + role + "_count";
    return obj;
}, projectObj );

在最终聚合管道中使用这两个文档:

db.users.aggregate([groupPipeline, projectPipeline]);

查看下面的演示。

var roles = ["moderator", "superadmin", "user",  "admin"],
	groupObj = { "_id": null },
	projectObj = { "_id": 0 };

var groupPipeline = roles.reduce(function(obj, role) { // set the group pipeline object 
	obj[role + "_count"] = {
		"$sum": {
			"$cond": [ { "$eq": [ "$role", role ] }, 1, 0 ]
		}
	};
	return obj;
}, groupObj );

var projectPipeline = roles.reduce(function(obj, role) { // set the project pipeline object 
	obj[role] = "$" + role + "_count";
	return obj;
}, projectObj );

var pipeline = [groupPipeline, projectPipeline]

pre.innerHTML = JSON.stringify(pipeline, null, 4);
<pre id="pre"></pre>

答案 1 :(得分:0)

您可以使用map-reduce:

db.users.mapReduce(
  function map() { emit(this.role, 1); },
  function reduce(key, values) {
    var sum = 0;
    for (var i = 0; i < values.length; i++) {
      sum += i;
    }
    return sum;
  },
  {
      out: { inline: 1 },
      finalize: function (key, reducedValue) {
          var obj = {};
          obj[key] = reducedValue;
          return obj;
      }
  }
)

并仅映射value