如何在Express.js中使用Mongoose Aggregate统计子数组数量(解决空数组问题)
Mongoose聚合统计子数组hitshistory数量的示例代码
嘿,我来帮你解决这个Mongoose聚合统计子数组数量的问题!首先得先明确你的数据结构,假设你的文档大概是这样的(如果结构不一样,你可以调整代码里的字段名):
{ _id: ObjectId("60d21b4667d0d8992e610c85"), title: "前端性能优化技巧", hitshistory: [ { userId: ObjectId("60d21b4667d0d8992e610c86"), hitTime: ISODate("2024-05-20T14:30:00Z") }, { userId: ObjectId("60d21b4667d0d8992e610c87"), hitTime: ISODate("2024-05-21T09:15:00Z") } ] }
第一步:确认你的Model定义(参考)
假设你的Model是Article,结构大概如下,确保hitshistory字段是数组类型:
// models/Article.js const mongoose = require('mongoose'); // 子数组的Schema const hitHistoryItemSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, hitTime: { type: Date, default: Date.now } }); // 主文档Schema const articleSchema = new mongoose.Schema({ title: { type: String, required: true }, hitshistory: [hitHistoryItemSchema] // 这里就是你要统计的子数组 }); module.exports = mongoose.model('Article', articleSchema);
第二步:Controller中的聚合实现
根据你的需求,分两种场景给出示例:
场景1:统计每个文档的hitshistory数量(每个文档返回对应的hits值)
这种场景适合你要查看每篇文章的访问记录数,用$size操作符直接获取子数组长度,简单高效:
// controllers/articleController.js const Article = require('../models/Article'); const mongoose = require('mongoose'); async function getEachArticleHitCount(req, res) { try { // 如果你需要过滤特定文档,比如按ID查询,就加上$match阶段 const matchCondition = req.params.id ? { $match: { _id: new mongoose.Types.ObjectId(req.params.id) } } : {}; const hitCounts = await Article.aggregate([ matchCondition, // 可选:过滤特定文档 { $project: { _id: 1, title: 1, hits: { $size: "$hitshistory" } // 核心:用$size统计子数组长度,命名为hits } } ]); if (hitCounts.length === 0) { return res.status(404).json({ message: "未找到匹配的文档" }); } res.status(200).json(hitCounts); } catch (err) { console.error('统计访问数失败:', err); res.status(500).json({ message: '服务器内部错误', error: err.message }); } } module.exports = { getEachArticleHitCount };
场景2:统计所有文档的hitshistory总数量(总访问次数)
如果要统计所有文章的访问记录总和,需要用$unwind展开子数组,再用$group求和:
async function getTotalHitCount(req, res) { try { const totalResult = await Article.aggregate([ { $unwind: "$hitshistory" }, // 展开每个hitshistory元素 { $group: { _id: null, // 按null分组表示统计所有数据 totalHits: { $sum: 1 } // 每展开一个元素加1 } }, { $project: { _id: 0, hits: "$totalHits" // 把结果命名为hits } } ]); // 处理没有任何访问记录的情况 const totalHits = totalResult.length > 0 ? totalResult[0].hits : 0; res.status(200).json({ hits: totalHits }); } catch (err) { console.error('统计总访问数失败:', err); res.status(500).json({ message: '服务器内部错误', error: err.message }); } }
为啥你之前返回空数组?可能的原因:
- 字段名拼写错误:MongoDB区分大小写,确认你的子数组字段是
hitshistory(不是hitHistory或其他拼写); - $match条件太严格:如果加了过滤条件,可能没有匹配到任何文档,先去掉
$match测试; - 数据库无数据:检查你的集合里是否真的有包含
hitshistory的文档; - 空数组不影响结果:即使
hitshistory是空数组,$size会返回0,不会导致结果为空数组,除非没有匹配的文档。
内容的提问来源于stack exchange,提问作者Jones Stephen
相关产品推荐
相关产品推荐

