按两个单独的字段分组并具有两个单独的计数

时间:2019-04-28 04:44:52

标签: mongodb mongoose

我有以下字段的用户集合。 -ObjectId - 名称 -电子邮件 -userType(管理员或用户) -状态(有效或已禁止)

现在,我想要一次查询的管理员总数和活动用户总数。

我正在使用聚合函数。

user.aggregate([{
   $group:{
      _id: "$userType",
      count: { "$sum" : 1 }
   }
},{
   $group:{
      _id: "$status",
      count: { "$sum" : 1 }
   }
}])

我想要这样的输出...

[{
   "userType" : "admin",
   "count" : 5
},{
   "status" : "Active",
   "count" : 10
}]

2 个答案:

答案 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"
    }
  ]
}