You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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,我来帮你修正并优化:

问题分析

  1. $lookup关联字段错误:$group之后,分组的字段是_id(对应原文档的orderType),但你在$lookup里用了localField: "orderType",这个字段在分组后的文档里不存在,所以关联不到数据。
  2. $lookup返回的是数组:即使关联正确,$lookup返回的as字段是一个数组(因为可能匹配多个文档),直接取$orderType.name会得到数组而非单个值。
  3. 字段名不匹配:你在$group里统计的是ordersCount,但在$project里用了$totalCount,这会导致数量和百分比计算都出错。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:43:13