$project 做什么
$project 是聚合管道里控制输出形状的阶段:可以保留字段、排除字段、重命名字段,也可以用表达式计算新字段。它决定后续阶段能拿到哪些数据;把它放在管道前段还能提前裁掉不需要的字段,减少后续阶段的计算量。
基本信息
| 保留 | { sku: 1 } |
|---|---|
| 排除 | { internal: 0 } |
| 重命名 | { name: "$title" } |
| 计算 | { total: { $multiply: ["$qty", "$price"] } } |
| 混用限制 | 保留式与排除式不能混用,_id 是唯一例外 |
典型使用场景
- 裁剪输出字段,减小返回体积
- 重命名字段,统一对外接口的字段名
- 计算派生字段:总价、比例、条件标记
- 把嵌套字段提到顶层,便于下游处理
- 放在管道前段裁剪,减少后续阶段的数据量
常用写法
常用写法
// 计算总价并标记大额订单
db.orders.aggregate([
{ $project: {
_id: 0,
sku: 1,
total: { $multiply: ["$qty", "$price"] },
isBig: { $gt: ["$qty", 10] }
} }
])
// 重命名并提取嵌套字段
db.users.aggregate([
{ $project: { _id: 0, nick: "$profile.nickname", city: "$addr.city" } }
])
// 输出常量必须用 $literal,否则会被当成字段路径
db.users.aggregate([
{ $project: { source: { $literal: "import" }, name: 1 } }
])
// 条件标记
db.orders.aggregate([
{ $project: { level: { $cond: [ { $gte: ["$amount", 1000] }, "high", "normal" ] } } }
])写法与语义
| 写法 | 语义 | 注意 |
|---|---|---|
| { sku: 1 } | 保留该字段 | 与其他排除式写法不能混用 |
| { internal: 0 } | 排除该字段 | _id 可以单独排除 |
| { name: "$title" } | 重命名或引用字段 | 不带 $ 时会被当作常量字符串 |
| { total: { $multiply: [...] } } | 表达式计算 | 除 _id 外字段不能重复输出 |
| { level: { $cond: [...] } } | 条件表达式 | 两个分支的返回值类型要一致 |
$literal 用于输出常量值:直接写字符串会被当成字段路径解析,需要固定文本时一定要加上它。
保留式与排除式写法不能混用(_id 是唯一例外);字段较多时统一采用保留式写法,输出结构一眼就能看清。
$project 与 $addFields/$set 的取舍:后者保留原有字段、只做增补,$project 则是白名单式的精确控制;要同时保留原字段又加计算字段时,用 $addFields/$set 能少写一屏字段清单。
$project 里可以直接嵌表达式:算术 $add/$multiply、比较 $gt/$eq、条件 $cond、字符串 $concat/$substr、日期 $year/$dateToString,组合起来就能把内部结构整成对外接口需要的形状。
字段一旦被 $project 排除,后面的阶段就再也取不到它;管道中段确实需要临时字段时,先算出来用着,最后再收口裁剪。