必须将 $match 写入 $lookup 的 pipeline 内才能真正过滤关联数据;外层 $match 仅判断主文档是否存在匹配项,无法剔除 as 数组中不满足条件的元素。

$lookup 的 pipeline 参数里加 $match 才能真正过滤关联数据
直接在 $lookup 后面接一个全局 $match 阶段,只能过滤主文档(比如 products 文档),无法剔除 as 字段里那些不满足条件的关联项。真正要“只保留匹配的 inventory 记录”,必须把 $match 写进 $lookup 的 pipeline 里——这是唯一能让关联结果本身被筛选的方式。
为什么不能只靠外层 $match?
外层 $match 看的是整个文档是否存在某个嵌套路径值,比如 "inventory_docs.state": "OK"。它不会删掉 inventory_docs 数组里其他不满足条件的元素,只会把整个文档留下(只要数组里至少有一个匹配项)。这常导致返回冗余数据、后续 $unwind 膨胀出大量无效行。
-
$match在$lookup外:作用于主文档,对as数组是“存在性判断” -
$match在$lookup.pipeline内:作用于被关联集合(from)的文档流,直接控制哪些文档进入数组 - 不写
pipeline,$lookup默认等价于pipeline: [],即全量拉取
let + $expr 是访问主文档字段的唯一方式
要在 $lookup.pipeline 里按主文档字段(如 product)做匹配,必须用 let 声明变量,并在 $match 中通过 $expr + $eq 引用。写错变量名或漏掉 $expr,查询会静默失败(返回空数组)。
- 正确写法:
let: { prod: "$product" }+$match: { $expr: { $eq: ["$product", "$$prod"] } } - 错误写法:
$match: { product: "$$prod" }(字段名匹配,不是表达式匹配) -
$$prod中的$$不可省略,这是变量引用语法 -
$expr内部支持索引,但仅限单字段、非数组、非 undefined 类型
实际聚合语句长什么样?
以 product 主表关联 inventory 表,并只取 state: "OK" 的库存记录为例:
db.product.aggregate([
{
$lookup: {
from: "inventory",
let: { prod_name: "$product" },
pipeline: [
{ $match: { $expr: { $eq: ["$product", "$$prod_name"] } } },
{ $match: { state: "OK" } }
],
as: "inventory_docs"
}
}
])
注意:两个 $match 可合并,但拆开更清晰;pipeline 中不能用 $out 或 $merge;as 字段若已存在会被覆盖。
最易忽略的一点:pipeline 里的 $match 对 from 集合生效,所以 state: "OK" 是查 inventory 的字段,不是 product 的——这个上下文切换,调试时经常看反。











