导读:本期聚焦于Amelis创作的《MongoDB聚合管道中$push操作符如何使用?推入数组的完整用法解析》,敬请观看详情。$push是MongoDB聚合管道中常用的数组累积操作符,它能把分组内的文档字段值收集到一个数组里,配合$group使用时可以快速实现数据汇总。本文详细讲解$push的基本语法和实际用法,包括如何在分组统计中推入字段值、推入完整文档、结合$match做条件筛选,以及与$addToSet的区别对比。文中还给出聚合查询的完整示例代码,覆盖订单按用户分组、嵌套文档收集等常见场景,并分析使用$push时容易踩到的坑,比如数组无限增长导致的内存问题。掌握这些技巧后,你可以在报表统计和数据聚合场景中更灵活地运用MongoDB。

做数据统计的时候,经常需要把某一组文档里的多个值收集到一个数组中。比如统计每个用户的订单列表、每个班级的学生名单,这类需求用普通的find查询很难一步到位,而MongoDB聚合管道里的$push操作符正是为此设计的。它属于累积操作符的一种,专门在$group阶段使用,能够把分组内所有文档的指定字段值依次推入一个数组,最终返回一个完整的数组结果。这篇文章就来详细拆解$push的用法,包括基本语法、推入完整文档、结合其他管道阶段的组合技巧,以及和$addToSet的区别。

MongoDB聚合管道中$push操作符如何使用?推入数组的完整用法解析

$push的基本语法与工作原理

$push在聚合管道中只能出现在$group阶段的累积器位置,语法结构非常简单:{ $push: <表达式> }。表达式可以是一个字段引用,比如"$productId",也可以是一个对象字面量,甚至可以是嵌套的表达式组合。MongoDB在执行$group时,会遍历输入文档流,遇到相同分组键的文档,就把表达式的计算结果追加到对应的数组里,顺序与文档进入管道的顺序一致。

先准备一份测试数据,模拟一个订单集合:

db.orders.insertMany([
  { user: "张三", product: "键盘", amount: 299 },
  { user: "张三", product: "鼠标", amount: 99 },
  { user: "李四", product: "显示器", amount: 1899 },
  { user: "李四", product: "耳机", amount: 499 },
  { user: "王五", product: "摄像头", amount: 399 }
]);

如果想知道每个用户买了哪些商品,用$push配合$group就能一次查出来:

db.orders.aggregate([
  {
    $group: {
      _id: "$user",
      products: { $push: "$product" },
      totalAmount: { $sum: "$amount" }
    }
  }
]);

返回结果中,每个用户对应一个products数组,同时可以用$sum并行统计消费总额。这种写法的好处是一条聚合语句同时完成分组、收集和求和,避免了在应用层再写循环逻辑。

推入完整文档与自定义结构

$push后面接的表达式不局限于单个字段,还可以推入整个文档或者手工构造的对象。把表达式写成$$ROOT就能把原始文档完整收进数组:

db.orders.aggregate([
  {
    $group: {
      _id: "$user",
      orderList: { $push: "$$ROOT" }
    }
  }
]);

这种方式在需要保留文档全部字段时很方便,但要注意内存占用,后面会专门讨论。更多时候我们会推入一个自定义结构的对象,只保留关心的字段,这样结果更干净,传输量也更小:

db.orders.aggregate([
  {
    $group: {
      _id: "$user",
      orders: {
        $push: {
          item: "$product",
          price: "$amount"
        }
      }
    }
  }
]);

这个查询的结果里,每个用户的orders数组元素都是{ item, price }结构。还可以进一步配合$addFields在推入时动态计算新字段,例如给每笔订单加上购买时的折扣价,写法上和普通表达式没有区别。

配合$match和$project实现条件收集

实际业务里往往不需要收集全部文档,只想收集满足条件的部分。常见做法是把$match放在$group之前先过滤,比如只统计金额大于200的订单:

db.orders.aggregate([
  { $match: { amount: { $gt: 200 } } },
  {
    $group: {
      _id: "$user",
      bigOrders: { $push: "$product" }
    }
  },
  { $project: { _id: 0, user: "$_id", bigOrders: 1 } }
]);

如果过滤条件依赖于分组之后的结果,比如只要消费总额超过1000的用户的订单数组,就需要把$match放到$group之后,对统计字段做筛选。管道的执行顺序是从上到下,理解每一步的输入输出是写好聚合的关键。

还有一种更灵活的场景:在同一个分组内,根据文档自身的条件决定是否推入。这可以通过$cond构造条件表达式,让不满足条件时推入一个缺省值,再在后续阶段用$filter清理掉。虽然写法绕一点,但在无法提前过滤分组的情况下是个可行的兜底方案。

$push与$addToSet的区别

很多人容易把$push$addToSet搞混。两者的核心区别在于:$push保留重复值,数组里出现什么完全取决于输入;而$addToSet会自动去重,只有数组中不存在该值时才会添加。举个例子,如果张三买了两个相同的键盘,用$push的结果是["键盘", "键盘"],用$addToSet的结果则只有一个"键盘"

选择哪个操作符要看业务语义。统计购买明细时重复记录是有意义的,必须用$push;统计用户接触过哪些品类时只关心去重集合,用$addToSet更合适。另外要注意,$addToSet对元素的相等判断是基于值比较的,数组、文档类型的元素也能正确去重,但元素顺序不同的数组会被视为不相等。

使用$push的注意事项与性能问题

最需要警惕的一点是聚合管道中的内存限制。MongoDB单个聚合阶段的默认内存上限是100MB,$push累积的数组全部驻留在内存中,一旦分组内的文档数量非常大,比如几十万条记录被推入同一个数组,很容易触发内存超限报错。解决办法有几种:一是先用$match$limit减少进入管道的数据量;二是利用$group阶段允许磁盘溢写的特性,在执行时加上allowDiskUse: true参数;三是从设计上重新审视,是否真的需要一次性取出这么大的数组,分页读取可能是更合理的方案。

db.orders.aggregate(
  [
    { $group: { _id: "$user", orderList: { $push: "$$ROOT" } } }
  ],
  { allowDiskUse: true }
);

另外要区分聚合操作符$push和更新操作符$push。两者名字相同但完全不是一个东西:更新时的$push用于往已有文档的数组字段里追加元素,配合$each$slice等修饰符使用;聚合时的$push只能在$group里做累积。刚接触MongoDB的同学在查文档时一定要确认自己看的是哪一章,避免混用导致语法报错。

最后提一点实践经验:推入完整文档$$ROOT虽然省事,但会让结果集膨胀得很快,尤其是原文档字段多、体积大的时候。建议在$group之前先用$project裁剪掉不需要的字段,或者在$push表达式里明确列出需要的字段。养成这个习惯后,聚合查询的执行效率和结果的可读性都会有明显提升。

MongoDB聚合管道$push操作符数组操作符修改时间:2026-09-10 17:25:05

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260910/54159.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。