“文档” 菜单
文档首页
/
MongoDB Manual
/ / /

$lookup(聚合)

在此页面上

  • 定义
  • 兼容性
  • 语法
  • 涉及单个条件联接的等值匹配
  • 已联接集合上的联接条件和子查询
  • 采用简洁语法的关联子查询
  • 行为
  • 视图和排序规则
  • 限制
  • Atlas Search 支持
  • 分片集合
  • 请参阅:基于插槽的查询执行引擎
  • 性能考虑因素
  • 举例
  • 执行单一等值联接 $lookup
  • 结合数组使用 $lookup
  • $lookup$mergeObjects 结合使用
  • 执行多个联接和关联子查询 $lookup
  • 执行非关联子查询 $lookup
  • 执行简洁关联子查询 $lookup
$lookup

5.1 版本中进行了更改

同一数据库中的一个集合执行左外连接,以过滤“已连接”集合中的文档以便进行处理。$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

同一个数据库中指定待执行联接操作的集合。

from 可选,可以改为在 $documents 阶段中使用 $lookup 阶段。有关示例,请参阅$documents 阶段中使用 $lookup 阶段

从 MongoDB 5.1 开始,可以对 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 阶段接受包含这些字段的文档:

字段
说明

同一个数据库中指定待执行联接操作的集合。

from 可选,可以改为在 $documents 阶段中使用 $lookup 阶段。有关示例,请参阅$documents 阶段中使用 $lookup 阶段

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

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

注意

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

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

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

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

    • 不使用多键索引

    • 当操作数为大量或操作数类型未定义时,不使用索引进行比较。

    • 对于多个字段路径操作数的比较,不使用索引。

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

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

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

pipeline 无法直接访问联接的文档字段。可以使用 let 选项为联接的文档字段定义变量,然后在 pipeline 阶段引用变量。

注意

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

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

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

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

    • 不使用多键索引

    • 当操作数为大量或操作数类型未定义时,不使用索引进行比较。

    • 对于多个字段路径操作数的比较,不使用索引。

  • 管道中的其他(非 $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 阶段。有关示例,请参阅$documents 阶段中使用 $lookup 阶段

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

指定本地文档的 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 集合上的索引。限制:

    • 不使用多键索引

    • 当操作数为大量或操作数类型未定义时,不使用索引进行比较。

    • 对于多个字段路径操作数的比较,不使用索引。

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

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

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

pipeline 不能直接访问文档字段。取而代之的是,使用 let 选项定义文档字段的变量,然后在 pipeline 阶段引用这些变量。

注意

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

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

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

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

    • 不使用多键索引

    • 当操作数为大量或操作数类型未定义时,不使用索引进行比较。

    • 对于多个字段路径操作数的比较,不使用索引。

  • 管道中的其他(非 $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>
}
}

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

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

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

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

当以分片集合为目标时,您无法在事务中使用 $lookup 阶段。

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

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

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

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

有关更多信息,请参阅 $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" }
] )

以下聚合操作将 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 集合上的索引。限制:

  • 不使用多键索引

  • 当操作数为大量或操作数类型未定义时,不使用索引进行比较。

  • 对于多个字段路径操作数的比较,不使用索引。

例如,如果索引 { 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