MongoDB聚合查询返回空数组:月度订单总价统计接口异常
问题描述
我编写了用于统计年度内各月份订单totalPrice总和的控制器getMonthlyOrderTotal,对应的Order模型已配置timestamps自动生成createdAt字段。但调用接口http://localhost:5001/orders/orderstotal/2022时,尽管MongoDB的orders集合存在对应数据,却始终返回空数组。
控制器代码
const getMonthlyOrderTotal = async (req, res) => { try { const year = req.params.year; const aggregatePipeline = [ { "$match": { "createdAt": { "$gte": new Date(`${year}-01-01T00:00:00.000`), "$lt": new Date(`${year}-12-31T23:59:59.999`) } } }, { "$group": { "_id": { "$month": "$createdAt" }, "total": { "$sum": "$totalPrice" } } }, { "$sort": { "_id": 1 } } ]; const orderTotals = await Order.aggregate(aggregatePipeline); res.json(orderTotals); } catch (err) { res.status(500).json({ "message": err.message }); } };
Order模型代码
import mongoose from "mongoose"; const orderSchema = mongoose.Schema( { "user": { "type": mongoose.Schema.Types.ObjectId, "required": true, "ref": "User", }, "client": { "type": mongoose.Schema.Types.ObjectId, "required": true, "ref": "Client", }, "orderItems": [ { "name": { "type": String, "required": true }, "qty": { "type": Number, "required": true }, "image": { "type": String }, "price": { "type": Number, "required": true }, "product": { "type": mongoose.Schema.Types.ObjectId, "required": true, "ref": "Product", }, }, ], "totalPrice": { "type": Number, "required": true, "default": 0.0, }, "taxPrice": { "type": Number, "required": true, "default": 0.0, }, "isPaid": { "type": Boolean, "required": true, "default": false, }, "paidAt": { "type": Date, }, "isDelivered": { "type": Boolean, "required": true, "default": false, }, "deliveredAt": { "type": Date, }, }, { "timestamps": true, } ); const Order = mongoose.model("Order", orderSchema); export default Order;
数据库数据截图


解决方案及排查
核心问题分析
- 日期范围边界错误:原查询用
${year}-12-31T23:59:59.999作为$lt边界,MongoDB的Date类型精度高于毫秒,可能导致部分接近年底的日期被排除。更严谨的做法是用下一年的1月1日0点作为$lt的边界,确保包含当年所有日期。 - 时区差异:MongoDB默认存储UTC时间,若订单
createdAt是本地时间生成,转换为UTC后可能跨到下一年,导致查询漏数据。需用UTC时间构造查询条件。 - 参数类型问题:
req.params.year是字符串类型,直接拼接可能引发意外,建议转为数字类型。
修正后的控制器代码
const getMonthlyOrderTotal = async (req, res) => { try { // 将年份参数转为数字,避免类型问题 const year = parseInt(req.params.year, 10); const aggregatePipeline = [ { "$match": { "createdAt": { // 用UTC时间构造当年1月1日0点 "$gte": new Date(Date.UTC(year, 0, 1)), // 用UTC时间构造下一年1月1日0点,确保包含全年数据 "$lt": new Date(Date.UTC(year + 1, 0, 1)) } } }, { "$group": { "_id": { "$month": "$createdAt" }, "total": { "$sum": "$totalPrice" } } }, { "$sort": { "_id": 1 } } ]; const orderTotals = await Order.aggregate(aggregatePipeline); res.json(orderTotals); } catch (err) { res.status(500).json({ "message": err.message }); } };
额外排查步骤
- 单独测试
$match阶段:用Order.find({ createdAt: { $gte: ..., $lt: ... }})直接查询,验证是否能返回目标数据。 - 核对数据库中
createdAt的UTC值:确认订单的创建时间确实落在目标年份的UTC时间范围内。
内容的提问来源于stack exchange,提问作者Joseph
相关产品推荐
相关产品推荐

