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

如何基于Brands集合阈值,用Mongoose聚合筛选Sales数据

问题

需要从Sales集合中获取按brand_id分组,且品牌总销售额超过对应阈值的所有文档。目前已实现固定阈值(1000美元)的Mongoose聚合方案,但阈值实际存储在Brands集合中(每个品牌对应不同阈值)。不想循环遍历Brands文档多次调用聚合,寻求更优实现方案。

Sales集合样本数据

[
  {
    "brand_id": "A",
    "price": 500
  },
  {
    "brand_id": "A",
    "price": 700
  },
  {
    "brand_id": "B",
    "price": 1500
  },
  {
    "brand_id": "C",
    "price": 100
  },
  {
    "brand_id": "D",
    "price": 400
  },
  {
    "brand_id": "D",
    "price": 600
  },
  {
    "brand_id": "D",
    "price": 200
  }
]

现有固定阈值实现代码

const data = await Sales.aggregate([
  {
    $group: {
      _id: "$brand_id",
      total_sales: {
        $sum: "$price"
      },
      records: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $match: {
      total_sales: {
        $gt: 1000
      }
    }
  },
  {
    $unwind: "$records"
  },
  {
    $replaceWith: "$records"
  }
])

Brands集合样本数据

[
  {
    "brand_id":"A",
    "brand_name": "abc",
    "threshold": 1000
  },
  {
    "brand_id": "B",
    "brand_name": "hef",
    "threshold": 600
  },
  {
    "brand_id": "C",
    "brand_name": "xyz",
    "threshold": 310
  }
]

期望输出格式

{
  "A": [
    {
      "_id": ObjectId("5a934e000102030405000000"),
      "brand_id": "A",
      "price": 500
    },
    {
      "_id": ObjectId("5a934e000102030405000001"),
      "brand_id": "A",
      "price": 700
    }
  ],
  "B": [
    {
      "_id": ObjectId("5a934e000102030405000002"),
      "brand_id": "B",
      "price": 1500
    }
  ],
  "D": [
    {
      "_id": ObjectId("5a934e000102030405000004"),
      "brand_id": "D",
      "price": 400
    },
    {
      "_id": ObjectId("5a934e000102030405000005"),
      "brand_id": "D",
      "price": 600
    },
    {
      "_id": ObjectId("5a934e000102030405000006"),
      "brand_id": "D",
      "price": 200
    }
  ]
}

解决方案

可以通过聚合管道关联两个集合,一次性完成计算、阈值匹配和结果格式化,无需循环调用。核心逻辑是先计算品牌总销售额,再关联Brands集合获取阈值,最后筛选并整理成目标格式。

完整Mongoose聚合代码如下:

const result = await Sales.aggregate([
  // 1. 按brand_id分组,计算总销售额并保留原始记录
  {
    $group: {
      _id: "$brand_id",
      total_sales: { $sum: "$price" },
      records: { $push: "$$ROOT" }
    }
  },
  // 2. 左连接Brands集合,获取对应品牌的阈值信息
  {
    $lookup: {
      from: "brands", // 注意是数据库中的集合名称,非Mongoose模型名
      localField: "_id",
      foreignField: "brand_id",
      as: "brand_info"
    }
  },
  // 3. 提取阈值,处理Brands中不存在的品牌(如D),默认阈值设为0
  {
    $addFields: {
      threshold: {
        $cond: {
          if: { $gt: [{ $size: "$brand_info" }, 0] },
          then: { $arrayElemAt: ["$brand_info.threshold", 0] },
          else: 0
        }
      }
    }
  },
  // 4. 筛选总销售额超过对应阈值的品牌
  {
    $match: {
      $expr: { $gt: ["$total_sales", "$threshold"] }
    }
  },
  // 5. 整理为key-value结构,方便后续转为对象
  {
    $project: {
      _id: 0,
      key: "$_id",
      value: "$records"
    }
  },
  // 6. 将所有key-value对合并成目标格式的对象
  {
    $group: {
      _id: null,
      data: { $push: { k: "$key", v: "$value" } }
    }
  },
  {
    $replaceWith: { $arrayToObject: "$data" }
  }
])

// 最终结果取数组中的第一个对象,为空则返回空对象
const finalData = result[0] || {}

步骤说明

  1. $group:按brand_id聚合,计算每个品牌的总销售额,同时收集该品牌的所有销售记录。
  2. $lookup:通过左连接关联Brands集合,获取对应品牌的阈值数据。
  3. $addFields:从连接结果中提取阈值,对Brands中未收录的品牌设置默认阈值0,确保这类品牌的销售额判断逻辑正常。
  4. $match:使用$expr实现动态字段比较,筛选出总销售额超过阈值的品牌。
  5. $project:将数据转换为key-value结构,为后续生成目标对象做准备。
  6. $group + $replaceWith:将所有key-value对合并成一个对象,得到期望的输出格式。

内容的提问来源于stack exchange,提问作者Akash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:54:58