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

如何统计MongoDB中关联productId的同category总数?

问题描述

我是MongoDB新手,需求是统计Cart集合中关联productId对应的相同category的总数量,预期结果为7。最初使用populate方法获取了关联的category数据,但不知如何统计;改用aggregate结合$lookup操作时,却得到了空的product数组。

相关代码及返回结果

CartSchema.js

const CartSchema = new mongoose.Schema({
    productId: {type: mongoose.Schema.Types.ObjectId, ref: 'Product'}
})

export default mongoose.model('Cart', CartSchema)

ProductSchema.js

const ProductSchema = new mongoose.Schema({
    category: {type: String, required: true},

})

export default mongoose.model('Product', ProductSchema)

初始populate路由代码

router.get('/categories', async (req, res) => {
    
  try {
    const cart = await Cart.find()
    .populate([
      {path: 'productId', select: 'category' },
    ]).exec()
    res.status(200).json(cart);     

  } catch (error) { 
    res.status(500).json({error: error.message})
  }
})

populate返回结果

[
    {
        "_id": "63b410fdde61a124ffd95a51",
        "productId": {
            "_id": "63b410d6de61a124ffd9585b",
            "category": "CASE"
        }
    },
    {
        "_id": "63b41a679950cb7c5293bf12",
        "productId": {
            "_id": "63b41637e3957a541eb59e81",
            "category": "CASE"
        }
    },
    {
        "_id": "63b433ef226742ae6b30b991",
        "productId": {
            "_id": "63b41637e3957a541eb59e81",
            "category": "CASE"
        }
    },
    {
        "_id": "63b670dc62b0f91ee4f8fbd9",
        "productId": {
            "_id": "63b410d6de61a124ffd9585b",
            "category": "CASE"
        }
    },
    {
        "_id": "63b6710b62b0f91ee4f8fc13",
        "productId": {
            "_id": "63b410d6de61a124ffd9585b",
            "category": "CASE"
        }
    },
    {
        "_id": "63b671bc62b0f91ee4f8fc49",
        "productId": {
            "_id": "63b410d6de61a124ffd9585b",
            "category": "CASE"
        }
    },
    {
        "_id": "63b6721c62b0f91ee4f8fcc5",
        "productId": {
            "_id": "63b410d6de61a124ffd9585b",
            "category": "CASE"
        }
    }
]

改用的aggregate路由代码

router.get('/categories', async (req, res) => {
 try {
    const cart = await Cart.aggregate([
      {
        $lookup: {
          from: 'product',
          localField: 'productId',
          foreignField: '_id',
          as: 'product'
        }
      },
      {
        $unwind: "$product"
      },
      {
        $group: {
          _id: "$product.category",
          total: {
            $sum: 1
          }
        }
      },
      {
        $sort: {total: -1}
      },
      {
        $project: {
          _id: 0,
          category: "$_id",
          total: 1
        }
      }
    ])
    res.status(200).json(cart);     
  } catch (error) { 
    res.status(500).json({error: error.message})
  }
})

解决方案

1. 修复aggregate的$lookup空数组问题

问题出在$lookup的from参数上:Mongoose默认会把模型名转为小写复数形式作为集合名,你的Product模型对应的集合实际是products,不是product。修改from参数即可:

router.get('/categories', async (req, res) => {
 try {
    const cart = await Cart.aggregate([
      {
        $lookup: {
          from: 'products', // 此处改为products
          localField: 'productId',
          foreignField: '_id',
          as: 'product'
        }
      },
      {
        $unwind: "$product"
      },
      {
        $group: {
          _id: "$product.category",
          total: {
            $sum: 1
          }
        }
      },
      {
        $sort: {total: -1}
      },
      {
        $project: {
          _id: 0,
          category: "$_id",
          total: 1
        }
      }
    ])
    res.status(200).json(cart);     
  } catch (error) { 
    res.status(500).json({error: error.message})
  }
})

修改后执行,就能正确关联Product集合,得到预期结果:[{"category":"CASE","total":7}]

2. 基于populate结果的统计方案

如果想继续用populate拿到数据后统计,可以用JavaScript的reduce方法在内存中计算:

router.get('/categories', async (req, res) => {
  try {
    const cart = await Cart.find()
      .populate([{path: 'productId', select: 'category' }])
      .exec();

    // 统计相同category的数量
    const categoryCount = cart.reduce((acc, item) => {
      const category = item.productId?.category;
      if (category) {
        acc[category] = (acc[category] || 0) + 1;
      }
      return acc;
    }, {});

    // 转换成目标格式
    const result = Object.entries(categoryCount).map(([category, total]) => ({
      category,
      total
    }));

    res.status(200).json(result);     
  } catch (error) { 
    res.status(500).json({error: error.message})
  }
})

该方案适合数据量不大的场景,数据量大时推荐用aggregate在数据库层面统计,性能更优。


内容的提问来源于stack exchange,提问作者Zurcemozz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:35:47