Docs 主页 → 开发应用程序 → MongoDB Manual
$lookup(聚合)
在此页面上
定义
-
$lookup
-
5.1 版本中进行了更改 。
对 同一 数据库中的一个集合执行左外连接,以筛选“已连接”集合中的文档以便进行处理。
$lookup
阶段为每个输入文档添加一个新的数组字段。这新的数组字段包含来自“已连接”集合的匹配文档。$lookup
阶段将这些重塑后的文档传递给下一阶段。从 MongoDB 5.1 开始,可以将
$lookup
与分片集合一起使用。要组合来自两个不同集合的元素,请使用
$unionWith
管道阶段。
兼容性
可以使用
$lookup
查找托管在以下环境中的部署:
-
MongoDB Atlas :用于云中 MongoDB 部署的完全托管服务
-
MongoDB Enterprise :基于订阅、自我管理的 MongoDB 版本
-
MongoDB Community :源代码可用、免费使用且可自行管理的 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
获得一份包含以下字段的文档:
字段
|
说明
|
---|---|
在 同一个 数据库中指定待执行联接操作的集合。
从 MongoDB 5.1 开始,可以对
|
|
指定要添加到输入文档中的新数组字段的名称。新数组字段包含来自
|
该操作对应于如下伪 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
阶段、
$sampleRate
操作符或
$rand
操作符的
$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
阶段接受包含这些字段的文档:
字段
|
说明
|
---|---|
在 同一个 数据库中指定待执行联接操作的集合。
从 MongoDB 5.1 开始,
|
|
指定要在已联接集合上运行的
注意
要在各个
管道
阶段引用变量,请使用
|
|
指定要添加到已连接文档的新数量字段的名称。新的大量字段包含来自加入的收集的匹配文档。如果指定的名称已存在于所连接的文档中,则现有字段将被覆盖。 |
该操作对应于如下伪 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
接受包含如下字段的文档:
字段
|
说明
|
---|---|
|
|
|
|
|
|
|
|
|
|
|
该操作对应于如下伪 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
或
{7
,则这些视图必须具有相同的
排序规则
。
限制
版本 4.2 中进行了更改 。
不能在
$lookup
阶段中包含
$out
或
$merge
阶段。换言之,在
为联接集合指定管道
时,不能在
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> } }
Atlas Search 支持
从 MongoDB 6.0 开始,您可以在
$lookup
管道中指定
Atlas Search
$search
或
$searchMeta
阶段来搜索 Atlas 集群上的集合。
$search
或
$searchMeta
阶段必须是
$lookup
管道内的第一阶段。
例如,当您
对已联接集合上联接条件和子查询
或
使用简洁事务语法运行关联子查询
时,可以在管道内指定
$search
或
$searchMeta
,如下所示:
分片集合
从 MongoDB 5.1 开始,可以在
$lookup
阶段的
from
参数中指定
分片集合
。
当以分片集合为目标时,您
无法
在事务中使用
$graphLookup
阶段。
请参阅:基于插槽的查询执行引擎
从版本 6.0 开始,如果管道中的
所有
前面的阶段也可以由基于槽的执行查询引擎执行,并且以下条件都不成立,则
MongoDB
$lookup
可以使用 基于槽的执行查询引擎 来执行 阶段:
-
$lookup
操作在联接集合上执行管道。要查看此类操作的示例,请参阅 联接集合上的连接条件和子查询。 -
$lookup
的localField
或foreignField
指定数字成分。例如:{ localField: "restaurant.0.review" }
。 -
管道中任何
$lookup
的from
字段指定视图或分片集合。
有关更多信息,请参阅
$lookup
优化
。
性能考虑因素
$lookup
性能取决于执行的操作类型。请参阅下表了解不同
$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 );
更多信息,请参阅 等值匹配性能注意事项 。
结合数组使用
$lookup
如果
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" } ] )
以下聚合操作将
classes
集合中的文档与
members
集合联接起来,将
enrollmentlist
字段与
name
字段进行匹配:
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" } ] }
将
$lookup
与
$mergeObjects
结合使用
$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
合并来自
items
和
orders
中的联接文档:
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 }
执行多个联接和关联子查询
$lookup
管道可以在连接的集合上执行并包含多个连接条件。
联接条件可以引用运行了
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.item
和warehouse.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 );
放置在
$expr
操作符上的
$eq
、
$lt
、
$lte
、
$gt
和
$gte
比较操作符可以使用
$lookup
阶段引用的
from
集合上的索引。限制:
例如,如果索引
{ stock_item: 1, instock: 1 }
存在于
warehouses
集合上:
-
warehouses.stock_item
字段上的等值匹配使用索引。 -
对
warehouses.instock
字段的查询范围部分也使用复合索引中的索引字段。
执行非关联子查询
$lookup
聚合管道
$lookup
阶段可以在联接集合上执行管道,这样即可执行非关联子查询。非关联子查询不会引用已联接的文档字段。
注意
从 MongoDB 5.0 开始,对于包含
$sample
阶段、
$sampleRate
操作符或
$rand
操作符的
$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 );
更多信息,请参阅 非关联子查询性能考量 。
执行简洁关联子查询
$lookup
版本 5.0 中的新增功能 。
从 MongoDB 5.0 开始,聚合管道
$lookup
阶段支持
简洁关联子查询事务语法
,该语法改进了集合之间的联接。新的简洁事务语法删除了对于
$match
阶段对
$expr
操作符内的外部和本地字段进行等值匹配的要求。
创建集合
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
localField 与restaurants.name
foreignField 匹配,联接orders
和restaurants
集合。在运行pipeline
之前执行匹配。 -
在分别使用
$$orders_drink
和$beverages
访问的orders.drink
和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.drink
和
restaurants.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
$lookup
阶段的
$expr
操作符中,对本地字段和已连接字段之间使用
$eq
等值匹配,如
使用
$lookup
执行多个联接和一个关联子查询
。
此示例使用 5.0 之前的 MongoDB 版本中较旧的详细事务语法,并返回与上一个简洁示例相同的结果:
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 );
更多信息,请参阅 关联子查询性能考量 。