Docs 菜单
Docs 主页
/
MongoDB Manual
/ / /

$lookup(聚合)

在此页面上

  • 定义
  • 兼容性
  • 语法
  • Considerations
  • 示例
$lookup

5.0 版本中的更改

同一数据库中的未分片集合执行左外连接,以筛选“已连接”集合中的文档进行处理。 对于每个输入文档, $lookup阶段添加一个新的数组字段,其元素是“已连接”集合中的匹配文档。 $lookup阶段将这些重塑后的文档传递到下一阶段。

可以使用 $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 获取一份包含以下字段的文档:

字段
说明

指定同一数据库中用于执行联接的集合。 无法对from集合进行分片。 有关详细信息,请参阅分片集合限制。

指定输入到 $lookup 阶段的文档中的字段。$lookupfrom 集合文档中的 localFieldforeignField 执行等值匹配。如果输入文档不包含 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 阶段、$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 阶段接受包含这些字段的文档:

字段
说明

指定同一数据库中执行联接操作的集合。 无法对联接的集合进行分片(请参阅分片集合限制)。

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

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

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

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

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

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

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

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

    • 不使用多键索引

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

指定要在已联接集合上运行的 pipelinepipeline 确定来自已联接集合的结果文档。要返回所有文档,请指定一个空的 pipeline []

pipeline不能包括$out阶段或$merge阶段。

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 操作符来访问变量。

指定要添加到已连接文档的新数量字段的名称。新的大量字段包含来自加入的收集的匹配文档。如果指定的名称已存在于所连接的文档中,则现有字段将被覆盖。

该操作对应于如下伪 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 接受包含如下字段的文档:

字段
说明

指定同一数据库中要联接到本地集合的外部集合。 外部集合无法进行分片(请参阅分片集合限制)。

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

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

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

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

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

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

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

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

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

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

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

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

    • 不使用多键索引

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

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

pipeline不能包括$out$merge阶段。

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 操作符来访问变量。

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

该操作对应于如下伪 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),则这些视图必须具有相同的排序规则

不能在 $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>
}
}

$lookup阶段,无法对from集合进行分。 但是,运行aggregate()方法的集合可以进行分片。 具体来说,在此示例中:

db.collection.aggregate([
{ $lookup: { from: "fromCollection", ... } }
])
  • collection可以进行分片。

  • 无法对fromCollection进行分片。

要将分片集合与非分片集合连接,请对分片集合运行聚合并查找非分片集合。 例如:

db.shardedCollection.aggregate([
{ $lookup: { from: "unshardedCollection", ... } }
])

或者要连接多个分片集合,请考虑:

$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" }
] )

以下聚合操作将 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" }
]
}

$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
}

管道可以在连接的集合上执行并包含多个连接条件。

联接条件可以引用运行了 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
);

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

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

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

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

  • 不使用多键索引

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

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

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

提示

另请参阅:

聚合管道 $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
);

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

版本 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 localFieldrestaurants.name foreignField 匹配,联接 ordersrestaurants 集合。在运行 pipeline 之前执行匹配。

  • 在分别使用 $$orders_drink$beverages 访问的 orders.drinkrestaurants.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$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
);

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

后退

$listSessions