管道里加工数组
数组型字段在文档库里很常见(标签、明细行、分数列表),聚合阶段提供了一组数组表达式直接在数据库侧加工:$filter 按条件筛选元素,$map 逐个转换,$reduce 折叠成单值,$slice 与 $arrayElemAt 取片段或元素。把这些计算放在管道里,能显著减少应用程序侧的遍历与拼接代码。
基本信息
| 筛选 | { $filter: { input: "$scores", as: "s", cond: { $gt: ["$$s", 60] } } } |
|---|---|
| 映射 | { $map: { input: "$items", as: "i", in: { $multiply: ["$$i.qty", "$$i.price"] } } } |
| 折叠 | { $reduce: { input: "$items", initialValue: 0, in: { $add: ["$$value", "$$this.qty"] } } } |
| 取元素与长度 | { $arrayElemAt: ["$items", 0] }、{ $size: "$items" } |
| 变量引用 | 自定义变量用 $$名称 引用,系统变量为 $$this 与 $$value |
| 适用版本 | 多数数组表达式在 3.2 至 3.6 之间陆续提供 |
典型使用场景
- 统计达标记数:筛选出及格元素后取长度
- 计算明细合计:把每行数量乘单价再累加成金额
- 取首末元素:列表页只展示首条与末条概要
- 数组字段瘦身:只保留需要的元素,减少返回体积
- 结构转换:把元素数组映射成新的嵌套结构供前端直接使用
示例
示例
db.students.aggregate([
{ $project: {
name: 1,
// 筛选及格成绩后计数
passCount: { $size: { $filter: { input: "$scores", as: "s", cond: { $gte: ["$$s", 60] } } } },
// 数组内每行数量乘单价,得到金额数组
amounts: { $map: { input: "$items", as: "i", in: { $multiply: ["$$i.qty", "$$i.price"] } } },
// 折叠成总额
total: { $reduce: { input: "$items", initialValue: 0,
in: { $add: ["$$value", { $multiply: ["$$this.qty", "$$this.price"] }] } } },
first: { $arrayElemAt: ["$scores", 0] },
// 取前三个元素,负值表示从末尾开始
top3: { $slice: ["$scores", 3] }
} }
])运算符对照
| 运算符 | 作用 | 注意点 |
|---|---|---|
| $filter | 按条件筛选元素 | input 为 null 时结果为 null 而非空数组 |
| $map | 对每个元素求值生成新数组 | 元素结构不符预期会得到 null 或缺字段 |
| $reduce | 把数组折叠成单值 | initialValue 决定累加类型,别漏设 |
| $arrayElemAt | 按下标取元素 | 下标越界返回缺失,不会报错 |
| $size | 数组长度 | 对非数组字段使用会报错 |
| $slice | 截取片段 | 正数从头取,负数从末尾取 |
自定义变量用 $$名称 引用,$reduce 里则是 $$value 与 $$this:写错前缀会得到空结果或报错,条件计数前建议先用 $ifNull 把缺失数组兜底为空数组。
数组运算发生在文档内部:元素很多时单文档体积与计算量都会上升,超大数组应考虑拆分建模而不是靠表达式硬算。