使用Mongoose聚合分组+关联查询,获取分组占比及类型名称
Mongoose聚合分组:关联订单类型、统计数量及占比问题
需求说明
我想要用Mongoose实现:对Orders集合按orderType字段分组,同时返回该类型对应的名称、该类型订单的数量,以及该类型订单占总订单数的百分比。
集合结构
Orders集合文档示例:
{ "_id" : ObjectId("5acdee5b80a4d21726f4fbaa"), "date" : ISODate("2018-04-11T11:15:40.104Z"), "orderType" : ObjectId("5a6cf47415621604942386aa") }, { "_id" : ObjectId("5acdee5b80a4d21726f4fcbb"), "date" : ISODate("2018-04-12T17:43:11.317Z"), "orderType" : ObjectId("5a6cf47415621604942386aa") }, { "_id" : ObjectId("5aba3a931cef621bd3b74ed7"), "date" : ISODate("2018-04-16T12:22:18.117Z"), "orderType" : ObjectId("5a6cf47415621604942386aa") }, { "_id" : ObjectId("5acdee5b80a4d21726f4fb70"), "date" : ISODate("2018-04-19T08:13:01.223Z"), "orderType" : ObjectId("5a6cf47415621604942386bb") }
OrderType集合文档示例:
{ "_id" : ObjectId("5a6cf47415621604942386aa"), "name" : "Order Type 1" }, { "_id" : ObjectId("5a6cf47415621604942386bb"), "name" : "Order Type 2" }
期望结果
{ "orderType" : "Order Type 1", "numOrders" : 3, "percent" : 75 }, { "orderType" : "Order Type 2", "numOrders" : 1, "percent" : 25 }
我的问题
我参考了方案写出了代码,但投影后的type字段返回undefined。刚接触Mongoose和聚合管道,想知道这个需求是否可行?如果可行,该怎么修改我的代码?
我的代码:
//retrieve the total number of documents in the collection let total = 0; await models.orders.count({}).exec((err, cnt) => { total = cnt.valueOf(); }); await models.orders.aggregate( [ { $group: { _id: "$orderType", ordersCount: { $sum: 1 } } }, { $lookup: { from: "OrderType", localField: "orderType", foreignField: "_id", as: "orderType" } }, { $project: { type: "$orderType.name", orderCount: "$totalCount", percent: { $multiply: [{$divide: [100, total]}, "$totalCount"]} } } ],(err, orders) => { orders.map(order => { //order.type is undefined here console.log("order type: " + order.type); console.log("order count: " + order.orderCount); console.log("order %: " + order.percent); }) });
解答
这个需求完全可行!你的代码里有几个关键问题导致了type字段undefined,我来帮你修正并优化:
问题分析
- $lookup关联字段错误:
$group之后,分组的字段是_id(对应原文档的orderType),但你在$lookup里用了localField: "orderType",这个字段在分组后的文档里不存在,所以关联不到数据。 - $lookup返回的是数组:即使关联正确,
$lookup返回的as字段是一个数组(因为可能匹配多个文档),直接取$orderType.name会得到数组而非单个值。 - 字段名不匹配:你在
$group里统计的是ordersCount,但在$project里用了$totalCount,这会导致数量和百分比计算都出错。 - await与回调混用:Mongoose的方法如果用了
await,就不需要再传回调函数,否则会导致执行顺序混乱。
修正后的代码(两种实现方式)
方式1:先获取总订单数,再执行聚合
async function getOrderStats() { try { // 正确获取总订单数(await和回调不要混用) const total = await models.orders.countDocuments({}); const orders = await models.orders.aggregate([ // 按orderType分组,统计数量 { $group: { _id: "$orderType", numOrders: { $sum: 1 } } }, // 关联OrderType集合,注意localField是分组后的_id { $lookup: { from: "orderTypes", // 注意:Mongoose默认会把模型名转成小写复数,这里要对应集合实际名称 localField: "_id", foreignField: "_id", as: "orderTypeInfo" } }, // 把关联的数组转成单个对象(因为每个orderType只会匹配一个文档) { $addFields: { orderType: { $arrayElemAt: ["$orderTypeInfo.name", 0] } } }, // 投影需要的字段,计算百分比 { $project: { _id: 0, // 隐藏_id字段 orderType: 1, numOrders: 1, percent: { $round: [ { $multiply: [ { $divide: ["$numOrders", total] }, 100 ] }, 0 ] } } } ]); // 输出结果 orders.forEach(order => { console.log("order type: " + order.orderType); console.log("order count: " + order.numOrders); console.log("order %: " + order.percent); }); return orders; } catch (err) { console.error(err); } } // 调用函数 getOrderStats();
方式2:完全用聚合管道计算总订单数(更高效,只需要一次查询)
async function getOrderStats() { try { const orders = await models.orders.aggregate([ // 先统计每个orderType的数量 { $group: { _id: "$orderType", numOrders: { $sum: 1 } } }, // 把所有分组结果放到一个数组里,同时计算总订单数 { $group: { _id: null, totalOrders: { $sum: "$numOrders" }, orderStats: { $push: { _id: "$_id", numOrders: "$numOrders" } } } }, // 把每个订单类型的信息拆出来,同时计算百分比 { $unwind: "$orderStats" }, // 关联OrderType集合 { $lookup: { from: "orderTypes", localField: "orderStats._id", foreignField: "_id", as: "orderTypeInfo" } }, // 整理字段格式 { $addFields: { "orderStats.orderType": { $arrayElemAt: ["$orderTypeInfo.name", 0] }, "orderStats.percent": { $round: [ { $multiply: [ { $divide: ["$orderStats.numOrders", "$totalOrders"] }, 100 ] }, 0 ] } } }, // 投影出最终需要的字段 { $project: { _id: 0, orderType: "$orderStats.orderType", numOrders: "$orderStats.numOrders", percent: "$orderStats.percent" } } ]); // 输出结果 orders.forEach(order => { console.log("order type: " + order.orderType); console.log("order count: " + order.numOrders); console.log("order %: " + order.percent); }); return orders; } catch (err) { console.error(err); } } // 调用函数 getOrderStats();
关键修改点说明
- 修正$lookup关联:把
localField改成_id(分组后的字段),同时注意集合名称要和MongoDB中实际的集合名一致(Mongoose默认会把模型名转为小写复数,比如OrderType模型对应的集合是ordertypes)。 - 处理关联数组:用
$arrayElemAt从关联的数组中取出第一个元素的name,或者也可以用$unwind先展开数组再处理。 - 统一字段名:确保
$group和$project中的字段名一致,比如numOrders。 - 避免await和回调混用:用
async/await的标准写法,让代码逻辑更清晰,也避免异步顺序问题。 - 百分比计算优化:用
$round把百分比取整,和你期望的结果格式一致。
内容的提问来源于stack exchange,提问作者DrewCo
相关产品推荐
相关产品推荐

