如何让Mongoose聚合查询返回含无预订日期(count为0)的结果?
解决Mongoose聚合查询中补全无预订日期的问题
原聚合查询仅返回存在预订记录的日期,要补全无预订日期的count:0条目,核心思路是先生成目标日期区间的完整日期序列,再与统计结果关联。以下是两种可行方案:
方案一:聚合管道内生成日期序列并关联
直接在MongoDB聚合管道中生成完整日期范围,通过左连接关联原统计数据,自动补全无数据日期的count值:
const startDate = new Date("2022-09-04"); const endDate = new Date("2022-09-10"); // 计算日期区间的总天数(包含起止日) const daysDiff = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)); const bookings = await Bookings.aggregate([ // 创建空文档用于生成日期序列 { $documents: [{}] }, // 生成0到总天数的数字偏移量数组 { $addFields: { dayOffsets: { $range: [0, daysDiff + 1] } } }, // 拆分偏移量数组为单个文档 { $unwind: "$dayOffsets" }, // 根据偏移量生成目标日期 { $addFields: { targetDate: { $dateFromParts: { year: { $year: startDate }, month: { $month: startDate }, day: { $add: [{ $dayOfMonth: startDate }, "$dayOffsets"] } } } } }, // 左连接原集合的预订统计数据 { $lookup: { from: "bookings", // 注意这里填写集合的实际名称(不是模型名) let: { targetDate: "$targetDate" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$bookingDate", "$$targetDate"] }, { $in: ["$store", storeIds] } ] } } }, { $count: "count" } ], as: "bookingData" } }, // 处理count字段:有数据取统计值,无数据设为0 { $addFields: { count: { $cond: { if: { $gt: [{ $size: "$bookingData" }, 0] }, then: { $arrayElemAt: ["$bookingData.count", 0] }, else: 0 } }, _id: "$targetDate" } }, // 保留需要的字段 { $project: { _id: 1, count: 1 } }, // 按日期排序 { $sort: { _id: 1 } } ]);
方案二:聚合后用JavaScript补全日期
先执行原聚合查询拿到已有数据,再通过JS生成完整日期序列并填充缺失值,逻辑更直观易维护:
const startDate = new Date("2022-09-04"); const endDate = new Date("2022-09-10"); // 1. 执行原聚合查询获取有预订记录的日期统计 const existingBookings = await Bookings.aggregate([ { $match: { store: { $in: storeIds }, bookingDate: { $gte: startDate, $lte: endDate } } }, { $group: { _id: "$bookingDate", count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]); // 2. 生成目标区间的完整日期映射表,默认count为0 const dateMap = new Map(); const currentDate = new Date(startDate); while (currentDate <= endDate) { const dateISO = currentDate.toISOString(); dateMap.set(dateISO, { _id: dateISO, count: 0 }); currentDate.setDate(currentDate.getDate() + 1); } // 3. 将已有统计数据填充到映射表中 existingBookings.forEach(item => { const dateISO = item._id.toISOString(); if (dateMap.has(dateISO)) { dateMap.set(dateISO, item); } }); // 4. 转换为最终结果数组 const bookings = Array.from(dateMap.values());
方案对比
- 聚合管道方案:所有逻辑在数据库端完成,适合需要直接返回完整结果的场景,无需额外JS处理。
- JavaScript补全方案:代码更简洁易懂,调试方便,适合日期区间较短的场景。
内容的提问来源于stack exchange,提问作者Mohsin
相关产品推荐
相关产品推荐

