定义
$lookup在版本8.0中进行了更改。
对同一数据库中的一个集合执行左外连接,以便从外部集合中过滤文档进行处理。
$lookup阶段会为每个输入文档添加一个新的数组字段。新数组字段包含来自外部集合的匹配文档。$lookup阶段会将这些重塑后的文档传递给下一阶段。从 MongoDB 5.1 开始,可以将
$lookup与分片集合一起使用。要组合来自两个不同集合的元素,请使用
$unionWith管道阶段。
兼容性
可以使用 $lookup 查找托管在以下环境中的部署:
MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 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 接受包含如下字段的文档:
字段 | 必要性 | 说明 |
|---|---|---|
必需 | 在同一个数据库中指定待联接到本地集合的外部集合。 在某些边缘情况下,可以使用 从 MongoDB 5.1 开始, | |
如果指定了 | ||
如果指定了 | 指定外部文档的 如果外部文档不包含 | |
Optional | ||
如果指定了 | 指定在外部集合上运行的
要引用管道阶段中的变量,请使用 | |
必需 | 指定要添加到输入文档中的新数组字段的名称。新数组字段包含来自 |
涉及单个条件联接的等值匹配
如要在输入文档中的字段与外部集合的文档中的字段之间执行等值匹配,$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 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> } }
该操作对应于如下伪 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> );
请参阅如下示例:
行为
加密collection
从MongoDB 8.1 开始,您可以在 $lookup 阶段引用多个加密集合。但是,$lookup 不支持:
使用加密字段作为
localField或foreignField中的联接字段。注意
对于使用 客户端字段级加密的驱动程序,只有在执行自连接操作时才能使用加密字段作为连接字段。
使用加密大量中的任何字段。如果大量包含任何加密元素,则视为加密。
示例,除非使用客户端字段级加密和
$lookup字段,否则不能使用 操作生成的 as$unwindas数组中的任何字段。
视图和排序规则
如果执行的聚合涉及多个视图(如使用 $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搜索支持
从MongoDB6.0 开始,您可以在 管道中指定MongoDB搜索$search 或$searchMeta $lookup阶段来搜索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 操作 | 性能考虑因素 |
|---|---|
| |
| |
|
示例
本页上的示例使用 sample_mflix示例数据集中的数据。有关如何将此数据集加载到自管理MongoDB 部署中的详细信息,请参阅加载示例数据集。如果对示例数据库进行了任何修改,则可能需要删除并重新创建数据库才能运行本页上的示例。
执行单一等值联接 $lookup
以下聚合操作首先将 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_comments FROM movies WHERE movie_comments IN ( SELECT * FROM comments WHERE movie_id = movies._id );
有关更多信息,请参阅等值匹配性能注意事项。
将 $lookup 与数组一起使用
如果 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' } ] } ]
将 $lookup 与 $mergeObjects 结合使用
$mergeObjects 操作符将多个文档合并成一个文档。
以下操作使用$lookup 连接movies 集合与comments 集合,然后使用$mergeObjects 中的$replaceRoot 将第一个评论文档与电影文档合并:
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_comments FROM movies WHERE 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
聚合管道 $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_movies FROM users WHERE long_movies IN ( SELECT title, year FROM movies WHERE runtime > 1000 );
如需详细了解,请参阅非关联子查询性能注意事项。
执行简洁关联子查询 $lookup
版本 5.0 中的新增功能。
从 MongoDB 5.0 开始,聚合管道 $lookup 阶段支持简洁关联子查询语法,该语法改进了集合之间的联接。新的简洁语法取消了在 $match 阶段对 $expr 操作符内的外部和本地字段进行等值匹配的要求。
如下示例:
moviescomments通过将本地字段_id与外部字段 进行匹配来连接 和movie_id集合。在运行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_comments FROM movies WHERE 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字段并指定集合。
以下场景显示了一个示例。
创建集合 cakeFlavors:
db.cakeFlavors.insertMany( [ { _id: 1, flavor: "chocolate" }, { _id: 2, flavor: "strawberry" }, { _id: 3, flavor: "cherry" } ] )
本页上的C#示例使用Atlas示例数据集中的 sample_mflix数据库。要学习;了解如何创建免费的MongoDB Atlas 群集并加载示例数据集,请参阅MongoDB .NET/ C#驱动程序文档中的入门。
以下 Movie 类对 sample_mflix.movies 集合中的文档进行建模:
public class Movie { public ObjectId Id { get; set; } public int Runtime { get; set; } public string Title { get; set; } public string Rated { get; set; } public List<string> Genres { get; set; } public string Plot { get; set; } public ImdbData Imdb { get; set; } public int Year { get; set; } public int Index { get; set; } public string[] Comments { get; set; } [] public DateTime LastUpdated { get; set; } }
注意
用于 Pascal Case 的 ConventionPack
此页面上的 C# 类在其属性名称中使用 Pascal 命名法,而 MongoDB 集合中的字段名称则使用 camel 命名法。为了解决这种差异,可以在应用程序启动时使用以下代码注册一个 ConventionPack:
var camelCaseConvention = new ConventionPack { new CamelCaseElementNameConvention() }; ConventionRegistry.Register("CamelCase", camelCaseConvention, type => true);
以下 Comment 类对 sample_mflix.comments 集合中的文档进行建模:
public class Comment { public Guid Id { get; set; } [] public Guid MovieId { get; set; } public string Text { get; set; } }
要使用MongoDB .NET/C# 驱动程序将 $lookup 阶段添加到聚合管道,请对 PipelineDefinition 对象调用 Lookup() 方法。
以下示例创建了一个管道阶段,用于在 movies 和 comments 集合之间执行左外连接。该代码将每个 Movie文档中的 Id字段连接到 Comment 文档中的 MovieId字段。每部电影的评论都存储在每个 Movie文档中名为 Comments 的字段中。
var commentCollection = client .GetDatabase("aggregation_examples") .GetCollection<Comment>("comments"); var pipeline = new EmptyPipelineDefinition<Movie>() .Lookup<Movie, Movie, Comment, Movie>( foreignCollection: commentCollection, localField: m => m.Id, foreignField: c => c.MovieId, @as: m => m.Comments);
本页上的 Node.js 示例使用 Atlas 示例数据集中的 sample_mflix数据库。要学习如何创建免费的MongoDB Atlas 集群并加载示例数据集,请参阅MongoDB Node.js驱动程序文档中的入门。
要使用MongoDB Node.js驱动程序将 $lookup 阶段添加到聚合管道,请在管道对象中使用 $lookup操作符。
以下示例创建了一个管道阶段,该阶段在 movies 和 comments 集合之间执行左外连接。该代码将每个 movie 文档中的 _id 字段连接到 comment 文档中的 movie_id 字段。comments 字段存储每个 movie 文档中每部电影的评论。然后,示例运行聚合管道:
const pipeline = [ { $lookup: { from: "comments", localField: "_id", foreignField: "movie_id", as: "comments" } } ]; const cursor = collection.aggregate(pipeline); return cursor;