我有以下字段的用户集合。 -ObjectId - 名称 -电子邮件 -userType(管理员或用户) -状态(有效或已禁止)
现在,我想要一次查询的管理员总数和活动用户总数。
我正在使用聚合函数。
user.aggregate([{
$group:{
_id: "$userType",
count: { "$sum" : 1 }
}
},{
$group:{
_id: "$status",
count: { "$sum" : 1 }
}
}])
我想要这样的输出...
[{
"userType" : "admin",
"count" : 5
},{
"status" : "Active",
"count" : 10
}]
答案 0 :(得分:1)
最简单的方法是使用$facet,它会创建两个单独的聚合,这正是您想要的。
{
$facet:
{
userType: [ {$group: {_id: "$userType" , count: {$sum: 1}}} ],
status: [ {$group: {_id: "$status", count: {$sum: 1} }} ],
}
}
现在我不确定您的数据是什么样子,以及将获得多少文档,但是您可以使用$ unwind和$ addFields重新格式化这两个字段,以查找所需的样式。
答案 1 :(得分:0)
如果您所追求的只是“单独计数”且返回的文档数量有限,可能是最简单的形式,将使用$facet
Model.aggregate([
{ "$facet": {
"userType": [
{ "$group": {
"_id": "$userType",
"count": { "$sum": 1 }
}}
],
"status": [
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}}
]
}}
])
如果您没有(或者预期的结果是exceed the 16MB BSON size limit,这是$facet
的局限性,而不是它的预期目的),那么您可以采用其他方法,例如对聚合管道中的数据进行一些操作:
Model.aggregate([
// Adds an array for each "type"
{ "$project": {
"_id": 0,
"type": [ "user", "status" ],
"userType": 1,
"count": 1
}},
// Unwind the array, creating a "document copy" for each "type" entry
{ "$unwind": "$type" },
// Group on alternating type
{ "$group": {
"_id": {
"type": "$type", // optional, just lets you know which "type" it is
"key": {
"$cond": [{ "$eq": [ "$type", "user" ] }, "$userType", "$status" ]
}
},
"count": { "$sum": 1 }
}}
])
这提供了一些不同的输出样式($facet
也是如此),但是基本数据仍然存在。在这种情况下,使用的聚合运算符可以一直追溯到MongoDB 2.2,因此没有不支持此功能的版本。
如果您确实必须以问题中指定的格式输出,那么您实际上可以将其从3.4版本起应用于现代版本的MongoDB:
Model.aggregate([
// Adds an array for each "type"
{ "$project": {
"_id": 0,
"type": [ "userType", "status" ],
// optionally using $const if you really need to be compatible
// "type": { "$const": [ "userType", "status" ] },
"userType": 1,
"count": 1
}},
// Unwind the array, creating a "document copy" for each "type" entry
{ "$unwind": "$type" },
// Group on alternating type
{ "$group": {
"_id": {
"k": "$type", // optional, just lets you know which "type" it is
"v": {
"$cond": [{ "$eq": [ "$type", "userType" ] }, "$userType", "$status" ]
}
},
"count": { "$sum": 1 }
}},
// Reshape the results
{ "$replaceRoot": {
"newRoot": {
"$mergeDocuments": [
{ "$arrayToObject": [["$_id"]] },
{ "count": "$count" }
]
}
}}
])
所以,这很酷,但是我想定期声明花哨的诸如$replaceRoot
和$arrayToObject
之类的东西,通常会在之后使用(如果不是),最后一个聚合阶段通常在返回“聚合”结果后,可以在客户端代码中更好地处理。而且主要是因为这样的“转换” 通常看起来更整洁,而且似乎并没有包含经常包含的钝来返回更少的数据 em>聚合运算符为此:
let result = await Model.aggregate([
{ "$project": {
"type": { "$const": [ "userType", "status" ] },
"userType": 1,
"status": 1
}},
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"k": "$type",
"v": {
"$cond": [{ "$eq": [ "$type", "userType" ] }, "$userType", "$status" ]
}
},
"count": { "$sum": 1 }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": [["$_id"]] },
{ "count": "$count" }
]
}
}}
*/
]);
result = result.map(({ _id: { k, v }, count }) => ({ [k]: v, count }) );
就像$facet
的例子一样:
let result = await Model.aggregate([
{ "$facet": {
"userType": [
{ "$group": {
"_id": "$userType",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"userType": "$_id",
"count": 1
}}
],
"status": [
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"status": "$_id",
"count": 1
}}
]
}},
/*
{ "$project": {
"results": { "$concatArrays": [ "$userType", "$status" ] }
}},
{ "$unwind": "$results" },
{ "$replaceRoot": { "newRoot": "$results" } }
*/
]);
result = [ ...result[0].userType, ...result[0].status ];
最重要的是,甚至可以追溯到以任何方式支持聚合框架的最早版本的MongoDB
以及可运行的演示
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/test';
const opts = { useNewUrlParser: true };
mongoose.set('debug', true);
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);
const modelSchema = new Schema({
userType: { type: String, enum: [ 'admin', 'user', 'moderator' ] },
status: { type: String, enum: [ 'active', 'closed', 'suspended' ] }
});
const Model = mongoose.model('Model', modelSchema, 'userDemo');
const log = data => console.log(JSON.stringify(data, undefined, 2));
const inputData = [
[ "admin", "active" ],
[ "admin", "closed" ],
[ "user", "active" ],
[ "user", "suspended" ],
[ "user", "active" ],
[ "moderator", "active" ],
[ "user", "closed" ]
];
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
);
await Model.insertMany(
inputData.map(([userType, status]) => ({ userType, status }))
);
// $facet example
{
let result = await Model.aggregate([
{ "$facet": {
"userType": [
{ "$group": {
"_id": "$userType",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"userType": "$_id",
"count": 1
}}
],
"status": [
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"status": "$_id",
"count": 1
}}
]
}},
{ "$project": {
"results": { "$concatArrays": [ "$userType", "$status" ] }
}},
{ "$unwind": "$results" },
{ "$replaceRoot": { "newRoot": "$results" } }
]);
log({ "title": "facet example", result })
}
// Traditional example
{
let result = await Model.aggregate([
{ "$project": {
"type": { "$const": [ "userType", "status" ] },
"userType": 1,
"status": 1
}},
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"k": "$type",
"v": {
"$cond": [{ "$eq": [ "$type", "userType" ] }, "$userType", "$status" ]
}
},
"count": { "$sum": 1 }
}},
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": [["$_id"]] },
{ "count": "$count" }
]
}
}}
]);
log({ "title": "Traditional approach", result });
}
// And "tranforming" in the client
{
let result = await Model.aggregate([
{ "$project": {
"type": { "$const": [ "userType", "status" ] },
"userType": 1,
"status": 1
}},
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"k": "$type",
"v": {
"$cond": [{ "$eq": [ "$type", "userType" ] }, "$userType", "$status" ]
}
},
"count": { "$sum": 1 }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayToObject": [["$_id"]] },
{ "count": "$count" }
]
}
}}
*/
]);
result = result.map(({ _id: { k, v }, count }) => ({ [k]: v, count }) );
log({ "title": "Traditional approach - Client", result });
}
// $facet example - client
{
let result = await Model.aggregate([
{ "$facet": {
"userType": [
{ "$group": {
"_id": "$userType",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"userType": "$_id",
"count": 1
}}
],
"status": [
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"status": "$_id",
"count": 1
}}
]
}},
/*
{ "$project": {
"results": { "$concatArrays": [ "$userType", "$status" ] }
}},
{ "$unwind": "$results" },
{ "$replaceRoot": { "newRoot": "$results" } }
*/
]);
result = [ ...result[0].userType, ...result[0].status ];
log({ "title": "facet example", result })
}
} catch (e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
并输出:
Mongoose: userDemo.deleteMany({}, {})
Mongoose: userDemo.insertMany([ { _id: 5cc54a034dabe81496cb244d, userType: 'admin', status: 'active', __v: 0 }, { _id: 5cc54a034dabe81496cb244e, userType: 'admin', status: 'closed', __v: 0 }, { _id: 5cc54a034dabe81496cb244f, userType: 'user', status: 'active', __v: 0 }, { _id: 5cc54a034dabe81496cb2450, userType: 'user', status: 'suspended', __v: 0 }, { _id: 5cc54a034dabe81496cb2451, userType: 'user', status: 'active', __v: 0 }, { _id: 5cc54a034dabe81496cb2452, userType: 'moderator', status: 'active', __v: 0 }, { _id: 5cc54a034dabe81496cb2453, userType: 'user', status: 'closed', __v: 0 } ], {})
Mongoose: userDemo.aggregate([ { '$facet': { userType: [ { '$group': { _id: '$userType', count: { '$sum': 1 } } }, { '$project': { _id: 0, userType: '$_id', count: 1 } } ], status: [ { '$group': { _id: '$status', count: { '$sum': 1 } } }, { '$project': { _id: 0, status: '$_id', count: 1 } } ] } }, { '$project': { results: { '$concatArrays': [ '$userType', '$status' ] } } }, { '$unwind': '$results' }, { '$replaceRoot': { newRoot: '$results' } } ], {})
{
"title": "facet example",
"result": [
{
"count": 1,
"userType": "moderator"
},
{
"count": 4,
"userType": "user"
},
{
"count": 2,
"userType": "admin"
},
{
"count": 2,
"status": "closed"
},
{
"count": 1,
"status": "suspended"
},
{
"count": 4,
"status": "active"
}
]
}
Mongoose: userDemo.aggregate([ { '$project': { type: { '$const': [ 'userType', 'status' ] }, userType: 1, status: 1 } }, { '$unwind': '$type' }, { '$group': { _id: { k: '$type', v: { '$cond': [ { '$eq': [ '$type', 'userType' ] }, '$userType', '$status' ] } }, count: { '$sum': 1 } } }, { '$replaceRoot': { newRoot: { '$mergeObjects': [ { '$arrayToObject': [ [ '$_id' ] ] }, { count: '$count' } ] } } } ], {})
{
"title": "Traditional approach",
"result": [
{
"userType": "moderator",
"count": 1
},
{
"status": "suspended",
"count": 1
},
{
"status": "active",
"count": 4
},
{
"userType": "admin",
"count": 2
},
{
"userType": "user",
"count": 4
},
{
"status": "closed",
"count": 2
}
]
}
Mongoose: userDemo.aggregate([ { '$project': { type: { '$const': [ 'userType', 'status' ] }, userType: 1, status: 1 } }, { '$unwind': '$type' }, { '$group': { _id: { k: '$type', v: { '$cond': [ { '$eq': [ '$type', 'userType' ] }, '$userType', '$status' ] } }, count: { '$sum': 1 } } } ], {})
{
"title": "Traditional approach - Client",
"result": [
{
"userType": "moderator",
"count": 1
},
{
"status": "suspended",
"count": 1
},
{
"status": "active",
"count": 4
},
{
"userType": "admin",
"count": 2
},
{
"userType": "user",
"count": 4
},
{
"status": "closed",
"count": 2
}
]
}
Mongoose: userDemo.aggregate([ { '$facet': { userType: [ { '$group': { _id: '$userType', count: { '$sum': 1 } } }, { '$project': { _id: 0, userType: '$_id', count: 1 } } ], status: [ { '$group': { _id: '$status', count: { '$sum': 1 } } }, { '$project': { _id: 0, status: '$_id', count: 1 } } ] } } ], {})
{
"title": "facet example",
"result": [
{
"count": 1,
"userType": "moderator"
},
{
"count": 4,
"userType": "user"
},
{
"count": 2,
"userType": "admin"
},
{
"count": 2,
"status": "closed"
},
{
"count": 1,
"status": "suspended"
},
{
"count": 4,
"status": "active"
}
]
}