对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。

$lookup(聚合阶段)

$lookup

在版本8.0中进行了更改。

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

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

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

重要

$lookup 与任何其他查询性能一样,性能取决于操作类型以及外部字段是否已建立索引。大型集合上未索引或相关的子查询 $lookup 操作可能会降低查询性能。为了减少对 $lookup 的依赖,请考虑使用 嵌入式数据模型 将相关数据存储在单个集合中。

有关按操作类型划分的 $lookup 性能的详细信息,请参阅性能注意事项。

可以使用 $lookup 查找托管在以下环境中的部署:

  • MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务

$lookup 阶段语法:

{ $lookup: { from: <collection to join>, localField: <field from the input documents>, foreignField: <field from the documents of the "from" collection>, let: { <var_1>: <expression>, …, <var_n>: <expression> }, pipeline: [ <pipeline to run> ], as: <output array field> }}

$lookup 接受包含如下字段的文档:

字段
必要性
说明

必需

在同一个数据库中指定待联接到本地集合的外部集合。

在某些边缘情况下,可以使用 pipeline 替换 from,并将 $documents 作为第一阶段。有关示例,请参阅在 $lookup 阶段中使用 $documents 阶段。

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

如果指定了 pipeline,则为可选

指定输入到 $lookup 阶段的文档中的字段。$lookup 对 from 集合文档中的 localField 和 foreignField 执行等值匹配。如果输入文档不包含 localField,则出于匹配目的,$lookup 会将此字段视为具有值 null。

如果指定了 pipeline,则为可选

指定外部文档的 foreignField 对本地文档的 localField 执行等值匹配。

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

Optional

指定在 管道 阶段中使用的变量。使用变量表达式访问输入到 pipeline的本地集合文档中的字段。

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

管道中的各个阶段可以访问 let 变量,包括其他嵌套在 pipeline 中的其他 $lookup 阶段。

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

    放置在 $expr 操作符上的 $eq、$lt、$lte、$gt 和 $gte 比较操作符可以使用 $lookup 阶段引用的 from 集合上的索引。限制:

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

      示例,$a 和常量值之间的比较可以使用索引,但 $a 和 $b 之间的比较不能使用索引。

    • 当 let 操作数解析为空值或缺失值时,索引不用于比较。

    • 不使用 多键、部分 或 稀疏 索引。

  • 管道中的其他(非 $match)阶段不需要使用 $expr 操作符来访问变量。

如果指定了 localField 和 foreignField,则为可选

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

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

pipeline 无法访问输入文档中的字段。取而代之的是,使用 let 选项定义文档字段的变量,然后在 pipeline 阶段引用这些变量。

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

管道中的各个阶段可以访问 let 变量,包括其他嵌套在 pipeline 中的其他 $lookup 阶段。

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

    放置在 $expr 操作符上的 $eq、$lt、$lte、$gt 和 $gte 比较操作符可以使用 $lookup 阶段引用的 from 集合上的索引。限制:

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

      示例,$a 和常量值之间的比较可以使用索引,但 $a 和 $b 之间的比较不能使用索引。

    • 当 let 操作数解析为空值或缺失值时,索引不用于比较。

    • 不使用 多键、部分 或 稀疏 索引。

  • 管道中的其他(非 $match)阶段不需要使用 $expr 操作符来访问变量。

必需

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

如要在输入文档中的字段与外部集合的文档中的字段之间执行等值匹配,$lookup 阶段具有以下语法:

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

注意

在此示例中,pipeline 是可选的,在本地和外部相等阶段之后运行。

该操作对应于如下伪 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,它使用本地或input 集合的字段返回与每个传入文档相关的结果。

注意

从 MongoDB 5.0 开始,对于包含 $sample 阶段、$sampleRate 操作符或 $rand 操作符的 $lookup 管道阶段中的非关联子查询,如果重复此子查询,此子查询总是会再次运行。以前,根据子查询输出大小,要么缓存子查询输出,要么再次运行子查询。

MongoDB 相关子查询与 SQL 相关子查询类似,其中内部查询引用外部查询值。SQL 不相关子查询不引用外部查询值。

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

要对两个集合执行关联和非关联子查询,并执行除单一等值匹配外的其他联接条件,请使用此 $lookup 语法:

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

该操作对应于如下伪 SQL 语句:

SELECT *, <output array field>FROM collectionWHERE <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> }}

该操作对应于如下伪 SQL 语句:

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

请参阅如下示例:

从MongoDB 8.1 开始,您可以在 $lookup 阶段引用多个加密集合。但是,$lookup 不支持:

  • 使用加密字段作为 localField 或 foreignField 中的联接字段。

    注意

    对于使用 客户端字段级加密的驱动程序,只有在执行自连接操作时才能使用加密字段作为连接字段。

  • 使用加密大量中的任何字段。如果大量包含任何加密元素,则视为加密。

    • 示例,除非使用客户端字段级加密和 $lookup字段,否则不能使用 操作生成的 as$unwind as数组中的任何字段。

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

您不能将 $out 和 $merge 阶段包含到 $lookup 阶段中。换言之,在指定外部集合的管道时,您不能在 pipeline 字段中包含任何阶段。

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

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

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

要查看 $lookup 和 $search 的示例,请参阅MongoDB Search 教程 使用 $lookup 运行MongoDB Search $ 搜索查询。

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

从 MongoDB 8.0 开始,您可以在事务内使用 $lookup 阶段,并以分片集合为目标。

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

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

  • $lookup 的 localField 或 foreignField 指定数字成分。例如:{ localField: "restaurant.0.review" }。

  • 管道中任何 $lookup 的 from 字段指定视图或分片集合。

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

$lookup 性能取决于执行的操作类型。请参阅下表了解不同 $lookup 操作的性能注意事项。

$lookup 操作
性能考虑因素
  • $lookup 当外部集合在 foreignField 上包含索引时,使用单个联接执行等值匹配的操作通常具有较好的性能。

    重要提示:如果 foreignField 上的支持索引不存在,使用单个联接执行等值匹配的 $lookup 操作可能具有较差的性能。

  • $lookup 在内部管道可以引用外部集合的索引时,包含不相关子查询的操作具有较好的性能。

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

  • $lookup 当外部集合包含针对 foreignField 的索引时,包含相关子查询的操作性能更佳。

  • 如果管道将大量文档传递给 $lookup 查询,以下策略可能会提高性能:

    • 减少 MongoDB 传递给 $lookup 查询的文档数量。例如,在 $match 阶段设定更严格的筛选器。

    • 重新考虑数据的模式,以确保它对于使用案例来说是最佳的。

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

本页上的示例使用来自 sample_mflix 数据集的数据。有关如何将此数据集加载到您的自管理 MongoDB 部署中的详细信息,请参阅 加载示例数据集。如果您对示例数据库进行了任何修改,您可能需要删除并重新创建数据库,才能运行此页面上的示例。

以下聚合操作首先将 movies集合筛选为 runtime 大于 1000 的电影,然后在 _id 和 movie_id 字段上与 comments集合连接:

db.movies.aggregate( [ { $match: { runtime: { $gt: 1000 } } }, { $lookup: { from: "comments", localField: "_id", foreignField: "movie_id", as: "movie_comments" } }, { $project: { _id: 0, title: 1, year: 1, "movie_comments.name": 1, "movie_comments.text": 1, "movie_comments.date": 1 } }] )
[ { title: 'Centennial', year: 1978, movie_comments: [ { name: 'Ellaria Sand', text: 'Excepturi nam nam eum possimus aspernatur autem. Quis nulla optio praesentium ut distinctio explicabo.', date: ISODate('1995-08-18T03:01:50.000Z') } ] }, { title: 'Baseball', year: 1994, movie_comments: [] }]

该操作对应于如下伪 SQL 语句:

SELECT *, movie_commentsFROM moviesWHERE movie_comments IN ( SELECT * FROM comments WHERE movie_id = movies._id);

有关更多信息,请参阅等值匹配性能注意事项。

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

以下聚合操作将 movies集合与 users集合连接起来,将 movies 中的 cast数组字段与 users 中的标量 name字段进行匹配:

db.movies.aggregate( [ { $match: { title: { $in: [ "Roger & Me", "The Sum of Us", "Centennial" ] } } }, { $lookup: { from: "users", localField: "cast", foreignField: "name", as: "cast_users" } }, { $project: { _id: 0, title: 1, year: 1, cast: 1, "cast_users.name": 1, "cast_users.email": 1 } }, { $sort: { year: 1 } }] )
[ { cast: [ 'Raymond Burr', 'Barbara Carrera', 'Richard Chamberlain', 'Robert Conrad' ], title: 'Centennial', year: 1978, cast_users: [] }, { cast: [ 'Michael Moore', 'Roger B. Smith', 'Rhonda Britton', 'Fred Ross' ], title: 'Roger & Me', year: 1989, cast_users: [ { name: 'Michael Moore', email: 'michael_moore@fakegmail.com' } ] }, { cast: [ 'Jack Thompson', 'Russell Crowe', 'John Polson', 'Deborah Kennedy' ], title: 'The Sum of Us', year: 1994, cast_users: [ { name: 'Deborah Kennedy', email: 'deborah_kennedy@fakegmail.com' } ] }]

$mergeObjects 操作符将多个文档合并成一个文档。

以下操作使用$lookup 连接movies 集合与comments 集合,然后使用$mergeObjects 中的$replaceRoot 将第一个评论 document 与电影 document 合并:

db.movies.aggregate( [ { $match: { runtime: { $gt: 1000 } } }, { $lookup: { from: "comments", localField: "_id", foreignField: "movie_id", as: "movie_comments" } }, { $replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ "$movie_comments", 0 ] }, "$$ROOT" ] } } }, { $project: { _id: 0, title: 1, year: 1, genres: 1, name: 1, email: 1, text: 1, date: 1 } }] )
[ { name: 'Ellaria Sand', email: 'indira_varma@gameofthron.es', text: 'Excepturi nam nam eum possimus aspernatur autem. Quis nulla optio praesentium ut distinctio explicabo.', date: ISODate('1995-08-18T03:01:50.000Z'), genres: [ 'Action', 'Adventure', 'Drama' ], title: 'Centennial', year: 1978 }, { genres: [ 'Documentary', 'History', 'Sport' ], title: 'Baseball', year: 1994 }]

管道可以在外部集合上执行并包含多个连接条件。$expr 操作符可以实现更复杂的连接条件,包括搭配和非等值匹配。

联接条件可以引用运行了 aggregate() 方法的本地集合中的字段,并引用外部集合中的字段。这样即可在两个集合之间执行一个关联子查询。

MongoDB 5.0 支持简洁关联子查询。

如下示例:

  • 使用 _id 和 movie_id 字段连接 movies 和 comments 集合。

  • 筛选评论以仅包含在电影发布年份之后发布的评论。

db.movies.aggregate( [ { $match: { title: { $in: [ "Class Action", "Kafka", "Corpse Bride" ] } } }, { $lookup: { from: "comments", localField: "_id", foreignField: "movie_id", let: { movie_year: "$year" }, pipeline: [ { $match: { $expr: { $gt: [ { $year: "$date" }, "$$movie_year" ] } } }, { $project: { _id: 0, name: 1, date: 1 } } ], as: "post_release_comments" } }, { $project: { _id: 0, title: 1, year: 1, post_release_comments: 1 } }] )
[ { year: 1991, title: 'Class Action', post_release_comments: [ { name: 'Khal Drogo', date: ISODate('2016-12-06T07:17:03.000Z') } ] }, { year: 1991, title: 'Kafka', post_release_comments: [ { name: 'Khal Drogo', date: ISODate('1998-05-10T03:10:20.000Z') } ] }, { year: 2005, title: 'Corpse Bride', post_release_comments: [] }]

该操作对应于如下伪 SQL 语句:

SELECT *, post_release_commentsFROM moviesWHERE post_release_comments IN ( SELECT name, date FROM comments WHERE movie_id = movies._id AND YEAR(date) > movies.year);

放置在 $expr 操作符上的 $eq、$lt、$lte、$gt 和 $gte 比较操作符可以使用 $lookup 阶段引用的 from 集合上的索引。限制:

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

    示例,$a 和常量值之间的比较可以使用索引,但 $a 和 $b 之间的比较不能使用索引。

  • 当 let 操作数解析为空值或缺失值时,索引不用于比较。

  • 不使用 多键、部分 或 稀疏 索引。

例如,如果索引 { movie_id: 1 } 存在于 comments 集合上:

  • comments.movie_id 字段上的等值匹配使用索引。

聚合管道 $lookup 阶段可以在外部集合上执行管道,这样即可执行非关联子查询。非关联子查询不引用本地文档字段。

注意

从 MongoDB 5.0 开始,对于包含 $sample 阶段、$sampleRate 操作符或 $rand 操作符的 $lookup 管道阶段中的非关联子查询,如果重复此子查询,此子查询总是会再次运行。以前,根据子查询输出大小,要么缓存子查询输出,要么再次运行子查询。

以下操作将 users集合与 movies集合中运行时间大于 1000 分钟的电影连接起来:

db.users.aggregate( [ { $match: { email: { $in: [ "mark_addy@gameofthron.es", "lena_headey@gameofthron.es" ] } } }, { $lookup: { from: "movies", pipeline: [ { $match: { runtime: { $gt: 1000 } } }, { $project: { _id: 0, title: 1, year: 1 } } ], as: "long_movies" } }, { $project: { _id: 0, name: 1, email: 1, long_movies: 1 } }] )
[ { name: 'Robert Baratheon', email: 'mark_addy@gameofthron.es', long_movies: [ { title: 'Centennial', year: 1978 }, { title: 'Baseball', year: 1994 } ] }, { name: 'Cersei Lannister', email: 'lena_headey@gameofthron.es', long_movies: [ { title: 'Centennial', year: 1978 }, { title: 'Baseball', year: 1994 } ] }]

该操作对应于如下伪 SQL 语句:

SELECT *, long_moviesFROM usersWHERE long_movies IN ( SELECT title, year FROM movies WHERE runtime > 1000);

如需详细了解,请参阅非关联子查询性能注意事项。

版本 5.0 中的新增功能。

从 MongoDB 5.0 开始,聚合管道 $lookup 阶段支持简洁关联子查询语法,该语法改进了集合之间的联接。新的简洁语法取消了在 $match 阶段对 $expr 操作符内的外部和本地字段进行等值匹配的要求。

如下示例:

  • 通过将本地字段 _id 与外部字段 movie_id 进行匹配来连接 movies 和 comments 集合。在运行pipeline 之前执行匹配。

  • 筛选评论,仅包含在电影发布年份之后发布的评论,可分别使用 $$movie_year 和 $date 进行访问。

db.movies.aggregate( [ { $match: { title: { $in: [ "I Don't Kiss", "Lucky Luke", "Mississippi Masala" ] } } }, { $lookup: { from: "comments", localField: "_id", foreignField: "movie_id", let: { movie_year: "$year" }, pipeline: [ { $match: { $expr: { $gt: [ { $year: "$date" }, "$$movie_year" ] } } }, { $project: { _id: 0, name: 1, date: 1 } } ], as: "post_release_comments" } }, { $project: { _id: 0, title: 1, year: 1, post_release_comments: 1 } }] )
[ { title: "I Don't Kiss", year: 1991, post_release_comments: [ { name: 'Brandon Hardy', date: ISODate('2016-09-18T11:11:34.000Z') } ] }, { title: 'Lucky Luke', year: 1991, post_release_comments: [ { name: 'Kelsey Smith', date: ISODate('2010-01-13T17:55:01.000Z') } ] }, { title: 'Mississippi Masala', year: 1991, post_release_comments: [ { name: 'Phillip Collins', date: ISODate('2010-05-13T08:04:22.000Z') } ] }]

此示例使用 5.0 之前的 MongoDB 版本中较旧的详细事务语法,并返回与上一个简洁示例相同的结果:

db.movies.aggregate( [ { $match: { title: { $in: [ "I Don't Kiss", "Lucky Luke", "Mississippi Masala" ] } } }, { $lookup: { from: "comments", let: { movie_id: "$_id", movie_year: "$year" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: [ "$movie_id", "$$movie_id" ] }, { $gt: [ { $year: "$date" }, "$$movie_year" ] } ] } } }, { $project: { _id: 0, name: 1, date: 1 } } ], as: "post_release_comments" } }, { $project: { _id: 0, title: 1, year: 1, post_release_comments: 1 } }] )
[ { title: "I Don't Kiss", year: 1991, post_release_comments: [ { name: 'Brandon Hardy', date: ISODate('2016-09-18T11:11:34.000Z') } ] }, { title: 'Lucky Luke', year: 1991, post_release_comments: [ { name: 'Kelsey Smith', date: ISODate('2010-01-13T17:55:01.000Z') } ] }, { title: 'Mississippi Masala', year: 1991, post_release_comments: [ { name: 'Phillip Collins', date: ISODate('2010-05-13T08:04:22.000Z') } ] }]

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

SELECT *, post_release_commentsFROM moviesWHERE post_release_comments IN ( SELECT * FROM comments WHERE comments.movie_id = movies._id AND YEAR(comments.date) > movies.year);

更多信息,请参阅关联子查询性能考量。

从 MongoDB 8.0 开始,会验证 $lookup 和 $unionWith 内子管道中的命名空间,以确保正确使用 from 和 coll 字段:

  • 对于 $lookup,如果您使用的子管道具有不需要指定集合的阶段,请省略 from 字段。例如,$documents 阶段。

  • 同样,对于 $unionWith,省略 coll 字段。

保持不变的行为:

  • 对于以集合阶段开头的 $lookup,例如 $match 或 $collStats 子管道,必须包含 from 字段并指定集合。

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