文档菜单
文档首页
/
MongoDB 手册
/ / /

$lookup (聚合)

本页内容

  • 定义
  • 兼容性
  • 语法
  • 使用单个连接条件进行等值匹配
  • 连接条件和对连接集合上的子查询
  • 使用简洁语法进行相关子查询
  • 行为
  • 视图和排序规则
  • 限制
  • Atlas 搜索支持
  • 分片集合
  • 基于槽位的查询执行引擎
  • 性能考虑
  • 示例
  • 使用以下方法执行单个等值连接$lookup
  • 使用 $lookup 与数组一起使用
  • 使用 $lookup$mergeObjects
  • 使用多个连接条件和关联子查询
  • 使用 $lookup 执行非关联子查询
  • 使用 $lookup 执行简洁的关联子查询
  • 子管道中的命名空间
$lookup

变更在版本8.0.

在相同数据库的集合中执行左外连接,以过滤来自“连接”集合的文档进行处理。此$lookup 阶段为每个输入文档添加一个新数组字段。新数组字段包含来自“连接”集合的匹配文档。该 $lookup 阶段将这些重塑后的文档传递到下一阶段。

从 MongoDB 5.1 开始,您可以使用 $lookup 与分片集合一起使用。

要合并来自两个不同集合的元素,请使用$unionWith 管道阶段。

您可以使用 $lookup 在以下环境中部署

  • MongoDB Atlas:MongoDB 在云中的完全托管服务

$lookup 阶段具有以下各节中显示的语法变体。

要执行输入文档中的一个字段与“连接”集合中的字段之间的等值匹配,$lookup 阶段具有以下语法

{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

$lookup 读取包含以下字段的文档

字段
描述

指定与连接操作一起执行的数据库中的集合。

from 是可选的,您可以在 $documents 阶段中使用一个 $lookup 阶段代替。例如,请参阅 $lookup 阶段中使用 $documents 阶段。

从 MongoDB 5.1 开始,from 参数中指定的集合可以进行分片。

指定输入到 $lookup 阶段的文档的字段。 $lookuplocalField 和来自 from 集合的文档的 foreignField 执行相等匹配。如果输入文档不包含 localField,则 $lookup 将该字段视为具有 null 值进行匹配。

指定来自 from 集合的文档的字段。 $lookupforeignField 和来自输入文档的 localField 执行相等匹配。如果 from 集合中的文档不包含 foreignField,则 $lookup 将该值视为 null 进行匹配。

指定要添加到输入文档中的新数组字段的名称。该新数组字段包含来自 from 集合的匹配文档。如果指定的名称已在输入文档中存在,则现有的字段将被 覆盖

该操作对应于以下伪 SQL 语句

SELECT *, (
SELECT ARRAY_AGG(*)
FROM <collection to join>
WHERE <foreignField> = <collection.localField>
) AS <output array field>
FROM collection;

注意

此页面上的 SQL 语句包含用于与 MongoDB 聚合管道语法进行比较。这些 SQL 语句不可执行。

有关 MongoDB 示例,请参阅这些页面

MongoDB 支持

  • 在连接集合上执行管道。

  • 多个连接条件。

  • 关联和非关联子查询。

在MongoDB中,相关子查询是在管道中的一个$lookup阶段,它引用了已连接集合中的文档字段。非相关子查询不引用已连接字段。

注意

从MongoDB 5.0开始,在一个包含$sample阶段的$lookup管道阶段中,对于非相关子查询,如果重复执行,则子查询总是重新运行。之前,根据子查询输出的大小,要么缓存子查询输出,要么重新运行子查询。

MongoDB的相关子查询类似于SQL的相关子查询,其中内部查询引用外部查询的值。一个SQL的非相关子查询不引用外部查询的值。

MongoDB 5.0还支持简洁的相关子查询。

要使用两个集合执行相关和非相关子查询,并执行除了单个等值匹配之外的其它连接条件,请使用这个$lookup语法

{
$lookup:
{
from: <joined collection>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to run on joined collection> ],
as: <output array field>
}
}

$lookup阶段接受包含这些字段的文档

字段
描述

指定进行连接操作的同一数据库中的集合。

from 是可选的,您可以在 $documents 阶段中使用一个 $lookup 阶段代替。例如,请参阅 $lookup 阶段中使用 $documents 阶段。

从MongoDB 5.1开始,from集合可以是分片。

可选。指定在管道阶段中使用的变量。使用变量表达式来访问输入到pipeline的已连接集合文档的字段。

要在管道阶段中引用变量,请使用"$$<variable>"语法。

let变量可以被管道阶段访问,包括嵌套在pipeline中的额外的$lookup阶段。

  • $match阶段需要使用$expr运算符来访问变量。该运算符允许在$match语法中使用聚合表达式。

    将比较运算符 $eq$lt$lte$gt$gte 放置在 $expr 运算符中,可以用于在 $lookup 阶段引用的 from 集合上的索引。限制条件

    • 索引只能用于字段和常量之间的比较,因此 let 操作数必须解析为常量。

      例如,比较 $a 和常量值可以使用索引,但比较 $a$b 则不能。

    • let 操作数解析为空或缺失值时,不会使用索引进行比较。

    • 多键索引 不被使用。

  • pipeline 中的其他(非 $match)阶段不需要 $expr 运算符来访问变量。

指定在连接集合上运行的 pipeline。该 pipeline 确定连接集合的结果文档。要返回所有文档,指定空 pipeline []

pipeline 不能包含 $out 阶段或 $merge 阶段。从 v6.0 开始,pipeline 可以包含作为 pipeline 中第一个阶段的 Atlas Search $search 阶段。有关更多信息,请参阅 Atlas Search Support.

pipeline 不能直接访问连接的文档字段。相反,使用 let 选项为连接文档字段定义变量,然后在 pipeline 阶段中引用这些变量。

要在管道阶段中引用变量,请使用"$$<variable>"语法。

let变量可以被管道阶段访问,包括嵌套在pipeline中的额外的$lookup阶段。

  • $match阶段需要使用$expr运算符来访问变量。该运算符允许在$match语法中使用聚合表达式。

    将比较运算符 $eq$lt$lte$gt$gte 放置在 $expr 运算符中,可以用于在 $lookup 阶段引用的 from 集合上的索引。限制条件

    • 索引只能用于字段和常量之间的比较,因此 let 操作数必须解析为常量。

      例如,比较 $a 和常量值可以使用索引,但比较 $a$b 则不能。

    • let 操作数解析为空或缺失值时,不会使用索引进行比较。

    • 多键索引 不被使用。

  • pipeline 中的其他(非 $match)阶段不需要 $expr 运算符来访问变量。

指定要添加到连接文档的新数组字段的名称。该新数组字段包含来自连接集合的匹配文档。如果指定的名称已存在于连接文档中,则覆盖现有字段。

该操作对应于以下伪 SQL 语句

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (
SELECT <documents as determined from the pipeline>
FROM <collection to join>
WHERE <pipeline>
);

请参阅以下示例

新功能在版本5.0.

从MongoDB 5.0版本开始,您可以使用简洁语法来使用相关子查询。相关子查询引用了与在本地集合上运行的aggregate()方法相关的“外部”集合的文档字段。

以下新的简洁语法去除了在$expr运算符内部对外部和本地字段进行相等匹配的要求

{
$lookup:
{
from: <foreign collection>,
localField: <field from local collection's documents>,
foreignField: <field from foreign collection's documents>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to run> ],
as: <output array field>
}
}

$lookup运算符接受包含以下字段的文档

字段
描述

指定要连接到本地集合的同一数据库中的外部集合。

from 是可选的,您可以在 $documents 阶段中使用一个 $lookup 阶段代替。例如,请参阅 $lookup 阶段中使用 $documents 阶段。

从MongoDB 5.1开始,from集合可以是分片。

指定本地文档的localField,以与外部文档的foreignField执行相等匹配。

如果本地文档不包含localField值,则$lookup使用null值进行匹配。

指定外部文档的foreignField,以与本地文档的localField执行相等匹配。

如果外部文档不包含foreignField值,则$lookup使用null值进行匹配。

可选。指定在管道阶段中使用的变量。使用变量表达式来访问输入到pipeline的文档字段。

要在管道阶段中引用变量,请使用"$$<variable>"语法。

let变量可以被管道阶段访问,包括嵌套在pipeline中的额外的$lookup阶段。

  • $match阶段需要使用$expr运算符来访问变量。该运算符允许在$match语法中使用聚合表达式。

    将比较运算符 $eq$lt$lte$gt$gte 放置在 $expr 运算符中,可以用于在 $lookup 阶段引用的 from 集合上的索引。限制条件

    • 索引只能用于字段和常量之间的比较,因此 let 操作数必须解析为常量。

      例如,比较 $a 和常量值可以使用索引,但比较 $a$b 则不能。

    • let 操作数解析为空或缺失值时,不会使用索引进行比较。

    • 多键索引 不被使用。

  • pipeline 中的其他(非 $match)阶段不需要 $expr 运算符来访问变量。

指定在外国集合上运行的pipeline。该pipeline返回外部集合的文档。要返回所有文档,指定一个空的pipeline []

管道不能包含 pipeline$out$merge 阶段。从 v6.0 版本开始,pipeline 可以包含作为管道内部第一个阶段的 Atlas Search $search 阶段。了解更多信息,请参阅 Atlas Search 支持。

管道不能直接访问文档字段。相反,使用 let 选项为文档字段定义变量,然后在管道阶段中引用这些变量。

要在管道阶段中引用变量,请使用"$$<variable>"语法。

let变量可以被管道阶段访问,包括嵌套在pipeline中的额外的$lookup阶段。

  • $match阶段需要使用$expr运算符来访问变量。该运算符允许在$match语法中使用聚合表达式。

    将比较运算符 $eq$lt$lte$gt$gte 放置在 $expr 运算符中,可以用于在 $lookup 阶段引用的 from 集合上的索引。限制条件

    • 索引只能用于字段和常量之间的比较,因此 let 操作数必须解析为常量。

      例如,比较 $a 和常量值可以使用索引,但比较 $a$b 则不能。

    • let 操作数解析为空或缺失值时,不会使用索引进行比较。

    • 多键索引 不被使用。

  • pipeline 中的其他(非 $match)阶段不需要 $expr 运算符来访问变量。

指定要添加到外部文档的新数组字段的名称。新数组字段包含来自外部集合的匹配文档。如果指定的名称已存在于外部文档中,则覆盖现有字段。

该操作对应于以下伪 SQL 语句

SELECT *, <output array field>
FROM localCollection
WHERE <output array field> IN (
SELECT <documents as determined from the pipeline>
FROM <foreignCollection>
WHERE <foreignCollection.foreignField> = <localCollection.localField>
AND <pipeline match condition>
);

查看此示例

如果执行涉及多个视图的聚合操作,例如使用 $lookup$graphLookup,则这些视图必须具有相同的 排序规则。

您不能在 $out$merge 阶段中包含 $lookup 阶段。也就是说,当指定 连接集合的管道 时,您不能在 pipeline 字段中包含这些阶段。

{
$lookup:
{
from: <collection to join>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to execute on the joined collection> ], // Cannot include $out or $merge
as: <output array field>
}
}

从 MongoDB 6.0 开始,您可以在 Atlas Search $search$searchMeta 管道中指定 $lookup,以在 Atlas 集群上搜索集合。$search$searchMeta 阶段必须是 $lookup 管道中的第一个阶段。

例如,当您在连接集合上使用连接条件和子查询或运行使用简洁语法的相关子查询时,您可以在如下所示的管道中指定$search$searchMeta

[{
"$lookup": {
"from": <joined collection>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
"as": <output array field>,
"pipeline": [{
"$search": {
"<operator>": {
<operator-specification>
}
},
...
}]
}
}]
[{
"$lookup": {
"from": <joined collection>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
"as": <output array field>,
"pipeline": [{
"$searchMeta": {
"<collector>": {
<collector-specification>
}
},
...
}]
}
}]

要查看使用$search$lookup示例,请参阅Atlas Search教程使用$lookup运行Atlas Search $search查询。

从MongoDB 5.1开始,您可以在$lookup阶段的from参数中指定分片集合

从MongoDB 8.0开始,您可以在针对分片集合时使用事务在$lookup阶段。

从版本6.0开始,MongoDB可以使用基于槽位的执行查询引擎来执行$lookup阶段,前提是管道中的所有先前的阶段也可以由该基于槽位的执行引擎执行并且以下条件均不成立

  • $lookup操作在连接集合上执行管道。要查看此类操作的示例,请参阅连接集合上的连接条件和子查询。

  • 《$lookup》操作的localFieldforeignField指定数字组件。例如:{ localField: "restaurant.0.review" }

  • 管道中任何《$lookup》操作的from字段指定了一个视图或分片集合。

有关更多信息,请参阅$lookup 优化。

《$lookup》的性能取决于执行的操作类型。请参阅以下表格了解不同《$lookup》操作的性能考虑因素。

《$lookup》操作
性能考虑
  • 执行单联合等性匹配的《$lookup》操作在外国集合包含对foreignField的索引时表现更好。

    重要:如果foreignField上不存在支持索引,那么执行单联合等性匹配的《$lookup》操作可能性能较差。

  • 包含非相关子查询的《$lookup》操作在内部管道可以引用外国集合的索引时表现更好。

  • MongoDB只需要在缓存查询之前运行一次$lookup子查询,因为源集合和外国集合之间没有关系。子查询不基于源集合中的任何值。这种行为提高了《$lookup》操作的后续执行性能。

  • 包含相关子查询的《$lookup》操作在以下条件下表现更好

    • 外国集合包含对foreignField的索引。

    • 外国集合包含引用内部管道的索引。

  • 如果您要将大量文档传递给《$lookup》查询,以下策略可能会提高性能

    • 减少MongoDB传递给《$lookup》查询的文档数量。例如,在$match阶段设置更严格的过滤器。

    • 将《$lookup》子查询的内部管道作为一个单独的查询运行,并使用$out创建一个临时集合。然后,运行一个单联合等性匹配。

    • 重新考虑数据的模式,以确保它对于用例是最优的。

有关一般性能策略,请参阅索引策略查询优化。

重要

在查询中过度使用 $lookup 可能会降低性能。为了避免多个 $lookup 阶段,考虑使用 嵌入式数据模型 来优化查询性能。

创建一个包含以下文档的集合 orders

db.orders.insertMany( [
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
{ "_id" : 3 }
] )

创建另一个包含以下文档的集合 inventory

db.inventory.insertMany( [
{ "_id" : 1, "sku" : "almonds", "description": "product 1", "instock" : 120 },
{ "_id" : 2, "sku" : "bread", "description": "product 2", "instock" : 80 },
{ "_id" : 3, "sku" : "cashews", "description": "product 3", "instock" : 60 },
{ "_id" : 4, "sku" : "pecans", "description": "product 4", "instock" : 70 },
{ "_id" : 5, "sku": null, "description": "Incomplete" },
{ "_id" : 6 }
] )

以下对 orders 集合的聚合操作使用 orders 集合中的 item 字段和 inventory 集合中的 sku 字段将来自 orders 的文档与来自 inventory 的文档进行连接

db.orders.aggregate( [
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
] )

操作返回以下文档

{
"_id" : 1,
"item" : "almonds",
"price" : 12,
"quantity" : 2,
"inventory_docs" : [
{ "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "pecans",
"price" : 20,
"quantity" : 1,
"inventory_docs" : [
{ "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"inventory_docs" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}

该操作对应于以下伪 SQL 语句

SELECT *, inventory_docs
FROM orders
WHERE inventory_docs IN (
SELECT *
FROM inventory
WHERE sku = orders.item
);

有关更多信息,请参阅 等值匹配性能考虑因素。

如果localField是一个数组,您可以在不进行$unwind阶段的情况下,将数组元素与标量foreignField进行匹配。

例如,创建一个包含以下文档的示例集合classes

db.classes.insertMany( [
{ _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] },
{ _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] }
] )

创建另一个包含以下文档的集合members

db.members.insertMany( [
{ _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" },
{ _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" },
{ _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" },
{ _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" },
{ _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" },
{ _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" }
] )

以下聚合操作通过enrollmentlist字段与name字段匹配,将classes集合中的文档与members集合中的文档进行连接

db.classes.aggregate( [
{
$lookup:
{
from: "members",
localField: "enrollmentlist",
foreignField: "name",
as: "enrollee_info"
}
}
] )

操作返回以下结果

{
"_id" : 1,
"title" : "Reading is ...",
"enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
"days" : [ "M", "W", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" },
{ "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" }
]
}
{
"_id" : 2,
"title" : "But Writing ...",
"enrollmentlist" : [ "giraffe1", "artie" ],
"days" : [ "T", "F" ],
"enrollee_info" : [
{ "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
{ "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" }
]
}

$mergeObjects运算符将多个文档合并为单个文档。

创建一个包含以下文档的集合 orders

db.orders.insertMany( [
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 }
] )

创建另一个包含以下文档的集合items

db.items.insertMany( [
{ "_id" : 1, "item" : "almonds", description: "almond clusters", "instock" : 120 },
{ "_id" : 2, "item" : "bread", description: "raisin and nut bread", "instock" : 80 },
{ "_id" : 3, "item" : "pecans", description: "candied pecans", "instock" : 60 }
] )

以下操作首先使用$lookup阶段通过item字段将两个集合连接起来,然后在$replaceRoot中使用$mergeObjects来合并来自itemsorders的连接文档

db.orders.aggregate( [
{
$lookup: {
from: "items",
localField: "item", // field in the orders collection
foreignField: "item", // field in the items collection
as: "fromItems"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$fromItems", 0 ] }, "$$ROOT" ] } }
},
{ $project: { fromItems: 0 } }
] )

操作返回以下文档

{
_id: 1,
item: 'almonds',
description: 'almond clusters',
instock: 120,
price: 12,
quantity: 2
},
{
_id: 2,
item: 'pecans',
description: 'candied pecans',
instock: 60,
price: 20,
quantity: 1
}

管道可以在连接的集合上执行,并包含多个连接条件。$expr运算符允许更复杂的连接条件,包括合取和非相等匹配。

连接条件可以引用本地集合中的字段,该字段是在执行aggregate()方法时使用的,并且可以引用连接集合中的字段。这允许两个集合之间进行相关子查询。

MongoDB 5.0 支持简明相关子查询。

创建一个包含以下文档的集合 orders

db.orders.insertMany( [
{ "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1 },
{ "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60 }
] )

创建另一个包含以下文档的集合warehouses

db.warehouses.insertMany( [
{ "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 },
{ "_id" : 2, "stock_item" : "pecans", warehouse: "A", "instock" : 80 },
{ "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 },
{ "_id" : 4, "stock_item" : "cookies", warehouse: "B", "instock" : 40 },
{ "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 }
] )

以下示例

  • 使用相关子查询与在orders.itemwarehouse.stock_item字段上进行的连接。

  • 确保库存中物品的数量可以满足订单数量。

db.orders.aggregate( [
{
$lookup:
{
from: "warehouses",
let: { order_item: "$item", order_qty: "$ordered" },
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$stock_item", "$$order_item" ] },
{ $gte: [ "$instock", "$$order_qty" ] }
]
}
}
},
{ $project: { stock_item: 0, _id: 0 } }
],
as: "stockdata"
}
}
] )

操作返回以下文档

{
_id: 1,
item: 'almonds',
price: 12,
ordered: 2,
stockdata: [
{ warehouse: 'A', instock: 120 },
{ warehouse: 'B', instock: 60 }
]
},
{
_id: 2,
item: 'pecans',
price: 20,
ordered: 1,
stockdata: [ { warehouse: 'A', instock: 80 } ]
},
{
_id: 3,
item: 'cookies',
price: 10,
ordered: 60,
stockdata: [ { warehouse: 'A', instock: 80 } ]
}

该操作对应于以下伪 SQL 语句

SELECT *, stockdata
FROM orders
WHERE stockdata IN (
SELECT warehouse, instock
FROM warehouses
WHERE stock_item = orders.item
AND instock >= orders.ordered
);

将比较运算符 $eq$lt$lte$gt$gte 放置在 $expr 运算符中,可以用于在 $lookup 阶段引用的 from 集合上的索引。限制条件

  • 索引只能用于字段和常量之间的比较,因此 let 操作数必须解析为常量。

    例如,比较 $a 和常量值可以使用索引,但比较 $a$b 则不能。

  • let 操作数解析为空或缺失值时,不会使用索引进行比较。

  • 多键索引 不被使用。

例如,如果warehouses集合上存在索引{ stock_item: 1, instock: 1 }

  • warehouses.stock_item字段上的等值匹配使用了索引。

  • warehouses.instock字段上的查询范围也使用了复合索引中的索引字段。

提示

另请参阅

聚合管道中的$lookup阶段可以在连接的集合上执行管道,这允许执行无相关子查询。无相关子查询不引用连接的文档字段。

注意

从MongoDB 5.0开始,在一个包含$sample阶段的$lookup管道阶段中,对于非相关子查询,如果重复执行,则子查询总是重新运行。之前,根据子查询输出的大小,要么缓存子查询输出,要么重新运行子查询。

创建一个包含以下文档的集合absences

db.absences.insertMany( [
{ "_id" : 1, "student" : "Ann Aardvark", sickdays: [ new Date ("2018-05-01"),new Date ("2018-08-23") ] },
{ "_id" : 2, "student" : "Zoe Zebra", sickdays: [ new Date ("2018-02-01"),new Date ("2018-05-23") ] },
] )

创建另一个包含以下文档的集合holidays

db.holidays.insertMany( [
{ "_id" : 1, year: 2018, name: "New Years", date: new Date("2018-01-01") },
{ "_id" : 2, year: 2018, name: "Pi Day", date: new Date("2018-03-14") },
{ "_id" : 3, year: 2018, name: "Ice Cream Day", date: new Date("2018-07-15") },
{ "_id" : 4, year: 2017, name: "New Years", date: new Date("2017-01-01") },
{ "_id" : 5, year: 2017, name: "Ice Cream Day", date: new Date("2017-07-16") }
] )

以下操作将absences集合与来自holidays集合的2018年假期信息进行连接

db.absences.aggregate( [
{
$lookup:
{
from: "holidays",
pipeline: [
{ $match: { year: 2018 } },
{ $project: { _id: 0, date: { name: "$name", date: "$date" } } },
{ $replaceRoot: { newRoot: "$date" } }
],
as: "holidays"
}
}
] )

操作返回以下结果

{
_id: 1,
student: 'Ann Aardvark',
sickdays: [
ISODate("2018-05-01T00:00:00.000Z"),
ISODate("2018-08-23T00:00:00.000Z")
],
holidays: [
{ name: 'New Years', date: ISODate("2018-01-01T00:00:00.000Z") },
{ name: 'Pi Day', date: ISODate("2018-03-14T00:00:00.000Z") },
{ name: 'Ice Cream Day', date: ISODate("2018-07-15T00:00:00.000Z")
}
]
},
{
_id: 2,
student: 'Zoe Zebra',
sickdays: [
ISODate("2018-02-01T00:00:00.000Z"),
ISODate("2018-05-23T00:00:00.000Z")
],
holidays: [
{ name: 'New Years', date: ISODate("2018-01-01T00:00:00.000Z") },
{ name: 'Pi Day', date: ISODate("2018-03-14T00:00:00.000Z") },
{ name: 'Ice Cream Day', date: ISODate("2018-07-15T00:00:00.000Z")
}
]
}

该操作对应于以下伪 SQL 语句

SELECT *, holidays
FROM absences
WHERE holidays IN (
SELECT name, date
FROM holidays
WHERE year = 2018
);

有关更多信息,请参阅无相关子查询性能考虑因素。

新功能在版本5.0.

从MongoDB 5.0版本开始,聚合管道中的 $lookup 阶段支持了一种简明的相关子查询语法,这提高了集合之间的连接操作。新的简明语法取消了在 $expr 操作符中进行的 $match 阶段内对外键和本地字段的等值匹配的要求。

创建一个集合 restaurants

db.restaurants.insertMany( [
{
_id: 1,
name: "American Steak House",
food: [ "filet", "sirloin" ],
beverages: [ "beer", "wine" ]
},
{
_id: 2,
name: "Honest John Pizza",
food: [ "cheese pizza", "pepperoni pizza" ],
beverages: [ "soda" ]
}
] )

创建另一个集合 orders,包含食物和可选的饮料订单

db.orders.insertMany( [
{
_id: 1,
item: "filet",
restaurant_name: "American Steak House"
},
{
_id: 2,
item: "cheese pizza",
restaurant_name: "Honest John Pizza",
drink: "lemonade"
},
{
_id: 3,
item: "cheese pizza",
restaurant_name: "Honest John Pizza",
drink: "soda"
}
] )

以下示例

  • 通过将 orders.restaurant_name 的本地字段与 restaurants.name 的外键字段进行匹配来连接 ordersrestaurants 集合。匹配操作在执行 pipeline 之前进行。

  • orders.drink 和使用 $$orders_drink$beverages 分别访问的 restaurants.beverages 字段之间执行一个 $in 数组匹配。

db.orders.aggregate( [
{
$lookup: {
from: "restaurants",
localField: "restaurant_name",
foreignField: "name",
let: { orders_drink: "$drink" },
pipeline: [ {
$match: {
$expr: { $in: [ "$$orders_drink", "$beverages" ] }
}
} ],
as: "matches"
}
}
] )

orders.drinkrestaurants.beverages 字段中有一个与 soda 值的匹配。此输出显示了 matches 数组,包含所有匹配的 restaurants 集合的联合字段。

{
"_id" : 1, "item" : "filet",
"restaurant_name" : "American Steak House",
"matches" : [ ]
}
{
"_id" : 2, "item" : "cheese pizza",
"restaurant_name" : "Honest John Pizza",
"drink" : "lemonade",
"matches" : [ ]
}
{
"_id" : 3, "item" : "cheese pizza",
"restaurant_name" : "Honest John Pizza",
"drink" : "soda",
"matches" : [ {
"_id" : 2, "name" : "Honest John Pizza",
"food" : [ "cheese pizza", "pepperoni pizza" ],
"beverages" : [ "soda" ]
} ]
}

在简明相关子查询引入之前,您必须在 pipeline 中的 $eq 等值匹配中使用本地字段和连接字段,如 使用多个连接条件和相关子查询。 所示。

此示例使用MongoDB 5.0之前的旧式详细语法,并返回与上一个简明示例相同的输出

db.orders.aggregate( [
{
$lookup: {
from: "restaurants",
let: { orders_restaurant_name: "$restaurant_name",
orders_drink: "$drink" },
pipeline: [ {
$match: {
$expr: {
$and: [
{ $eq: [ "$$orders_restaurant_name", "$name" ] },
{ $in: [ "$$orders_drink", "$beverages" ] }
]
}
}
} ],
as: "matches"
}
}
] )

前面的示例对应于以下伪SQL语句

SELECT *, matches
FROM orders
WHERE matches IN (
SELECT *
FROM restaurants
WHERE restaurants.name = orders.restaurant_name
AND restaurants.beverages = orders.drink
);

有关更多信息,请参阅 相关子查询性能考虑。

从MongoDB 8.0版本开始,对 $lookup$unionWith 中的子管道中的命名空间进行验证,以确保正确使用 fromcoll 字段。

  • 对于 $lookup 操作符,如果您使用一个不需要指定集合的子管道,可以省略 from 字段。例如,一个 $documents 阶段。

  • 同样,对于 $unionWith 操作符,可以省略 coll 字段。

行为不变

  • 对于以集合阶段开始的 $lookup 操作符,例如一个 $match$collStats 子管道,您必须包含 from 字段并指定集合。

  • 同样,对于 $unionWith 操作符,包含 coll 字段并指定集合。

以下场景展示了示例。

创建一个名为 cakeFlavors 的集合

db.cakeFlavors.insertMany( [
{ _id: 1, flavor: "chocolate" },
{ _id: 2, flavor: "strawberry" },
{ _id: 3, flavor: "cherry" }
] )

从 MongoDB 8.0 版本开始,以下示例返回错误,因为它包含一个无效的 from 字段

db.cakeFlavors.aggregate( [ {
$lookup: {
from: "cakeFlavors",
pipeline: [ { $documents: [ {} ] } ],
as: "test"
}
} ] )

在 MongoDB 8.0 版本之前的版本中,上述示例可以正常运行。

有关包含有效 from 字段的示例,请参阅 使用 $lookup 执行单等性连接。

返回

$listSessions