如何在MongoDB中查询过去7天的文档并补全无数据日期的0值
解决MongoDB查询过去7天数据时补全无工单日期的问题
我明白你现在的困扰:当前的查询只会返回有工单创建的日期,像示例里的05/04/2022这种没有工单的日期直接缺失了,导致返回的数据不完整。下面提供两种解决方案,你可以根据场景选择:
方案一:Node.js代码层面补全日期(适合快速修改现有代码)
思路很简单:先生成过去7天的完整日期列表,再把你统计好的工单数据和这个列表做匹配,缺失的日期就填充numOfTickets: 0。
修改后的代码如下:
const companyId = req.query.companyId; // Last 7 days const endDate = new Date(); const startDate = new Date(Date.now() - 604800000); // 1. 生成过去7天的完整日期列表(格式和toLocaleDateString一致) const generateDateRange = (start, end) => { const dates = []; const currentDate = new Date(start); while (currentDate <= end) { // 保持和你现有代码一致的日期格式 dates.push(currentDate.toLocaleDateString()); currentDate.setDate(currentDate.getDate() + 1); } return dates; }; const fullDateRange = generateDateRange(startDate, endDate); // 2. 查询并统计有工单的日期 const allCompanyTickets = await ticketModel .find({ company_id: companyId, createdAt: { $gte: startDate, $lte: endDate } }) .sort({ createdAt: 1 }); // 统计日期出现次数 const dateCountMap = allCompanyTickets.reduce((map, ticket) => { const dateStr = ticket.createdAt.toLocaleDateString(); map[dateStr] = (map[dateStr] || 0) + 1; return map; }, {}); // 3. 合并完整日期列表和统计数据,补全0值 const data = fullDateRange.map(date => ({ date, numOfTickets: dateCountMap[date] || 0 })); res.status(200).json({ datesUsed: { startDate, endDate }, data });
这个方法的好处是不需要改动数据库查询逻辑,只需要在现有代码基础上添加日期生成和合并的逻辑,快速解决问题。
方案二:MongoDB聚合管道处理(更高效,适合大数据量)
如果你的工单数据量很大,在Node.js层面处理可能会有性能问题,推荐直接用MongoDB的聚合管道在数据库层面完成统计和补全,减少数据传输量。
核心思路:
- 用
$dateToString把createdAt转成目标格式的日期字符串 - 按日期分组统计工单数量
- 生成过去7天的日期范围数组
- 把统计结果和日期范围做左连接,补全缺失日期的0值
示例聚合代码:
const companyId = req.query.companyId; const endDate = new Date(); const startDate = new Date(Date.now() - 604800000); // 计算过去7天的日期天数(用于生成范围) const daysDiff = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); const result = await ticketModel.aggregate([ // 1. 筛选目标公司和时间范围的工单 { $match: { company_id: companyId, createdAt: { $gte: startDate, $lte: endDate } } }, // 2. 把createdAt转成dd/mm/yyyy格式的日期字符串 { $addFields: { dateStr: { $dateToString: { format: "%d/%m/%Y", date: "$createdAt", // 如果需要时区,这里可以添加timezone参数,比如"Asia/Shanghai" } } } }, // 3. 按日期分组统计数量 { $group: { _id: "$dateStr", numOfTickets: { $sum: 1 } } }, // 4. 生成过去7天的日期范围数组 { $lookup: { from: { $expr: { $map: { input: { $range: [0, daysDiff + 1] }, as: "day", in: { dateStr: { $dateToString: { format: "%d/%m/%Y", date: { $add: [startDate, { $multiply: ["$$day", 86400000] }] } } } } } } }, localField: "_id", foreignField: "dateStr", as: "dateMatch" } }, // 5. 合并数据,补全0值 { $replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: ["$dateMatch", 0] }, { numOfTickets: { $ifNull: ["$numOfTickets", 0] } } ] } } }, // 6. 重命名字段并排序 { $project: { _id: 0, date: "$dateStr", numOfTickets: 1 } }, { $sort: { date: 1 } } ]); res.status(200).json({ datesUsed: { startDate, endDate }, data: result });
这个方法把所有逻辑放在数据库端处理,返回的数据直接就是完整的日期列表,性能更优,尤其是工单数量较多的时候。
需要注意的是,$dateToString的时区设置要和你之前toLocaleDateString保持一致,避免日期格式出现偏差。
内容的提问来源于stack exchange,提问作者Maiky




