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

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;

数据库数据截图

数据库订单数据
订单数据详情


解决方案及排查

核心问题分析

  1. 日期范围边界错误:原查询用${year}-12-31T23:59:59.999作为$lt边界,MongoDB的Date类型精度高于毫秒,可能导致部分接近年底的日期被排除。更严谨的做法是用下一年的1月1日0点作为$lt的边界,确保包含当年所有日期。
  2. 时区差异:MongoDB默认存储UTC时间,若订单createdAt是本地时间生成,转换为UTC后可能跨到下一年,导致查询漏数据。需用UTC时间构造查询条件。
  3. 参数类型问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:55:19