管道式关联的价值
$lookup 有两种写法:简单的 localField + foreignField 等值关联,以及用 let + pipeline 的管道式写法。后者可以在关联侧做过滤、排序与截断,适合「每个主文档只关联少量子文档」的场景,比如每张订单只取最近一条物流记录,避免先把子文档全部拉回来再截断。
基本信息
| 写法 | { $lookup: { from, let, pipeline: [...], as } } |
|---|---|
| 变量引用 | pipeline 内用 $$变量名 引用 let 定义的字段 |
| 优势 | 关联侧先过滤再返回,减少结果膨胀与内存占用 |
| 位置 | 可作为聚合管道中的阶段,通常放在已收敛数据之后 |
| 适用版本 | 3.6 及以上 |
| 配合写法 | 关联侧条件常用 $expr 表达字段与变量的比较 |
典型使用场景
- 每单带最近一条状态记录:只取最新一条,避免数组膨胀
- 关联侧聚合:在子文档上先分组汇总再返回结果
- 复杂关联条件:多字段或不等值条件用 $expr 表达
- 关联侧限量:限制返回条数控制结果体积
- 替代多次查询:把原本多轮查询合并到一次管道执行
每单带最近一条物流
每单带最近一条物流
db.orders.aggregate([
{ $match: { status: "paid" } }, // 先收敛主文档
{ $lookup: {
from: "shipments",
let: { orderNo: "$orderNo" },
pipeline: [
{ $match: { $expr: { $eq: ["$orderNo", "$$orderNo"] } } },
{ $sort: { ts: -1 } },
{ $limit: 1 } // 每个主文档只保留最新一条
],
as: "latestShipment"
} },
{ $project: { orderNo: 1, status: 1, latestShipment: { $arrayElemAt: ["$latestShipment", 0] } } }
])两种写法对照
| 写法 | 适用 | 注意点 |
|---|---|---|
| localField + foreignField | 简单等值关联 | 关联侧无索引时成本高,返回结果可能膨胀 |
| let + pipeline | 需要过滤、排序、限量或复杂条件 | 内部多用 $expr,优化器能力有限,先小范围验证 |
| 关联字段索引 | 两种写法都受益 | 关联集合上的匹配字段应建立索引 |
| 结果展开 | 用 $arrayElemAt 或 $unwind 处理数组 | 注意 $unwind 会成倍放大行数 |
管道式关联的收益来自「关联侧先收敛」:在子集合上过滤与排序后再返回,避免把大量子文档拉进内存再截断,大比例减少内存与耗时。
$$变量名拼写错误通常不报明确错误,而是静默返回空数组:排查时先核对 let 与 pipeline 里的变量名是否完全一致。