Definition 定义

对同一数据库中的未分片集合(unsharded collection)执行左外连接(left outer join),从 “连接(joined)” 的 collection 中过滤出文档进行处理。

对于每个输入文档,$lookup stage 添加一个新的数组字段,其元素(element)是 “连接(joined)” collection 中的匹配文档。$lookup stage 将这些重新调整的文档传递到下一个 stage。

Syntax 语法

$lookup stage 有以下语法:

Equality Match with a Single Join Condition 单一连接条件下的相等匹配

为了在输入文档中的 field 和 “joined” collection 中的 field 进行相等匹配,$lookup stage 有这样的语法:

  1. {
  2. $lookup:
  3. {
  4. from: <collection to join>,
  5. localField: <field from the input documents>,
  6. foreignField: <field from the documents of the "from" collection>,
  7. as: <output array field>
  8. }
  9. }

$lookup采用具有以下字段的文档:

Field Description
from 指定同一数据库中的 collection,以执行 join。from collection 不能是分片(sharded)的。详情请参阅分片集合的限制(Sharded Collection Restrictions)
localField 指定输入到 $lookup stage 的文档中的字段(输入文档中的字段)。$lookupfrom collection 的文档中对 localFieldforeignField 进行相等匹配。如果输入的文档不包含localField$lookup将该字段视为有一个空值(null)用于达到匹配的目的。
foreignField 指定来自 from collection 中文档的字段。$lookup对输入文档中的 foreignFieldlocalField 进行相等匹配。如果 from collection 中的文档不包含 foreignField$lookup 将该值视为空值(null)用于达到匹配的目的。
as 指定新的数组字段的名称,以添加到输入文档中。新的数组字段包含来自 from collection 的匹配文档。如果指定的名称已经存在于输入文档中,那么现有的字段将被覆盖(overwritten)

该 operation 对应以下的伪 SQL 语句(pseudo-SQL statement) :

  1. select *, <output array field>
  2. from collection
  3. where <output array field> in (
  4. select *
  5. from <collection to join>
  6. where <foreignField> = <collection.localField>
  7. )

请参阅以下示例:

  • Perform a Single Equality Join with $lookup$lookup 执行单一相等连接
  • Use $lookup with an Array 在数组中使用 $lookup
  • Use $lookup with $mergeObjects 使用 $lookup$mergeObjects

    Join Conditions and Subqueries on a Joined Collection 连接集合上的连接条件和子查询

    MongoDB 3.6 增加了对以下内容的支持:

  • Executing a pipeline on a joined collection. 在连接的集合上执行管道

  • Multiple join conditions. 多个连接条件
  • Correlated and uncorrelated subqueries. 关联和不关联的子查询

在 MongoDB 中,Correlated subquery(关联子查询)是 $lookup stage 的一个 pipeline,它引用了来自 joined collection 的文档字段。一个 uncorrelated subquery(不关联的子查询)不引用 joined fields。

NOTE 从 MongoDB 5.0 开始,对于包含 [$sample](https://docs.mongodb.com/manual/reference/operator/aggregation/sample/#mongodb-pipeline-pipe.-sample) stage、[$sampleRate](https://docs.mongodb.com/manual/reference/operator/aggregation/sampleRate/#mongodb-expression-exp.-sampleRate) operator 或 [$rand](https://docs.mongodb.com/manual/reference/operator/aggregation/rand/#mongodb-expression-exp.-rand) operator 的 $lookup pipeline stage 中的 uncorrelated subquery,如果重复,子查询总是被再次运行。以前,根据子查询的输出大小,要么 subquery 的输出被缓存,要么 subquery 被再次运行。

MongoDB correlated subquery 类似于 SQL correlated subquery,其中内部查询(inner query)引用外部查询值(outer query values)。一个 SQL uncorrelated subquery 不引用外部查询值(outer query values)。

MongoDB 5.0 还支持 concise correlated subqueries(简洁的关联子查询)

若要对两个 collection 执行 correlated 和 uncorrelated subqueries,并执行除单个相等匹配之外的其他连接条件,请使用以下 $lookup 语法:

  1. {
  2. $lookup:
  3. {
  4. from: <joined collection>,
  5. let: { <var_1>: <expression>, ..., <var_n>: <expression> },
  6. pipeline: [ <pipeline to run on joined collection> ],
  7. as: <output array field>
  8. }
  9. }

$lookupstage 接受包含以下字段的文档:

Field Description
from 指定同一数据库中的 collection 来执行 join operation。joined collection 不能是分片(sharded)的(参阅 Sharded Collection Restrictions 分片集合限制)。
let 可选的。指定在 pipeline stage 使用的变量。使用 variable expressions 来访问输入到 pipeline 的 joined collection 文档中的字段。> NOTE

要在 pipeline stage 引用变量,使用语法 “$$“。

let 变量可以被 pipeline 中的 stage 访问,包括嵌套(nested)在 pipeline 中的额外(additional) $lookup stages。

  • 一个 $match stage 需要使用 $expr operator 来访问变量。$expr operator 允许在 $match syntax 内部使用 aggregation expressions。
  • 从 MongoDB 5.0 开始,放置在 $expr operator 中的 $eq$lt$lte$gt$gte comparison operators 可以使用 $lookup stage 中引用的 fromcollection 上的索引(index)。限制条件:
    • 不使用 Multikey indexes(多键索引)
    • 运算对象为数组运算对象类型为 undefined 的情况下,不使用索引进行比较(comparison)。
    • 索引不用于与多个 field path 运算对象的比较(comparison)。
  • pipeline 中的其他(非 $match)stage 不需要 $expr operator 来访问这些变量。

| | pipeline | 指定在 joined collection 上运行的 pipelinepipeline 决定了从 joined collection 中得到的结果文档。要返回所有的文档,请指定一个 empty pipeline``[]

pipeline 不能包含 $out stage 或者 $merge stage。

pipeline 不能直接访问 joined document fields。取而代之的是,使用 let 选项为 joined document fields 定义变量,然后在 pipeline stage 引用这些变量。> NOTE

要在 pipeline stage 引用变量,使用语法 “$$“。

let 变量可以被 pipeline 中的 stage 访问,包括嵌套(nested)在 pipeline 中的额外(additional) $lookup stages。

  • 一个 $match stage 需要使用 $expr operator 来访问变量。$expr operator 允许在 $match syntax 内部使用 aggregation expressions。
  • 从 MongoDB 5.0 开始,放置在 $expr operator 中的 $eq$lt$lte$gt$gte comparison operators 可以使用 $lookup stage 中引用的 fromcollection 上的索引(index)。限制条件:
    • 不使用 Multikey indexes(多键索引)
    • 运算对象为数组运算对象类型为 undefined 的情况下,不使用索引进行比较(comparison)。
    • 索引不用于与多个 field path 运算对象的比较(comparison)。
  • pipeline 中的其他(非 $match)stage 不需要 $expr operator 来访问这些变量。

| | as | 指定要添加到 joined documents 的新数组字段的名称。 新的数组字段包含来自 joined collection 的匹配文档。 如果指定的名称已存在于 joined document 中,则覆盖(overwritten)现有字段。 |

该 operation 对应以下的 pseudo-SQL statement(伪 SQL 语句):

  1. select *, <output array field>
  2. from collection
  3. where <output array field> in (
  4. select <documents as determined from the pipeline(从管道中确定的文档)>
  5. from <collection to join>
  6. where <pipeline>
  7. )

请参阅以下示例:

Correlated subqueries 引用了来自 a joined “foreign” collection(一个连接的外部/外键集合) 和运行 aggregate() 方法的 “local” collection(本地集合)的文档字段。

以下新的 concise syntax 消除了对 $expr operator 内的外部(foreign)和本地(local)字段进行相等匹配的要求:

  1. {
  2. $lookup:
  3. {
  4. from: <foreign collection>,
  5. localField: <field from local collection's documents>,
  6. foreignField: <field from foreign collection's documents>,
  7. let: { <var_1>: <expression>, ..., <var_n>: <expression> },
  8. pipeline: [ <pipeline to run> ],
  9. as: <output array field>
  10. }
  11. }

$lookup 接受一个包含以下字段的文档:

Field Description
from 指定同一数据库中的 foreign collection,以 join 到 local collection 中。foreign collection 不能被分片(sharded)(参阅 Sharded Collction Resctrictions 分片集合限制)。
localField 指定 local document 的 localField 与 foreign document 的 foreignField 进行相等匹配。

如果一个 local document 不包含 localField 的值,$lookup 会使用一个空(null)值进行匹配。
foreignField 指定 foreign document 的 foreignField 与 local document 的 localField 进行相等匹配。

如果一个 foreign document 不包含 foreignField 的值,$lookup 会使用一个空(null)值进行匹配。
let 可选的。指定在 pipeline stage 使用的变量。使用 variable expressions 来访问输入到 pipeline 中的文档字段。> NOTE

要在 pipeline stage 引用变量,使用语法 “$$“。

let 变量可以被 pipeline 中的 stage 访问,包括嵌套(nested)在 pipeline 中的额外(additional) $lookup stages。

  • 一个 $match stage 需要使用 $expr operator 来访问变量。$expr operator 允许在 $match syntax 内部使用 aggregation expressions。
  • 从 MongoDB 5.0 开始,放置在 $expr operator 中的 $eq$lt$lte$gt$gte comparison operators 可以使用 $lookup stage 中引用的fromcollection 上的索引(index)。限制条件:
    • 不使用 Multikey indexes(多键索引)
    • 运算对象为数组运算对象类型为 undefined 的情况下,不使用索引进行比较(comparison)。
    • 索引不用于与多个 field path 运算对象的比较(comparison)。
  • pipeline 中的其他(非 $match)stage 不需要 $expr operator 来访问这些变量。

| | pipeline | 指定在 foreign collection 上运行的 pipeline。该 pipeline 从 foreign collection 中返回文档。要返回所有的文档,请指定一个 empty pipeline``[]

pipeline 不能包括 $out$merge stages。

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

要在 pipeline stage 引用变量,使用语法 “$$“。

let 变量可以被 pipeline 中的 stage 访问,包括嵌套(nested)在 pipeline 中的额外(additional) $lookup stages。

  • 一个 $match stage 需要使用 $expr operator 来访问变量。$expr operator 允许在 $match syntax 内部使用 aggregation expressions。
  • 从 MongoDB 5.0 开始,放置在 $expr operator 中的 $eq$lt$lte$gt$gte comparison operators 可以使用 $lookup stage 中引用的fromcollection 上的索引(index)。限制条件:
    • 不使用 Multikey indexes(多键索引)
    • 运算对象为数组运算对象类型为 undefined 的情况下,不使用索引进行比较(comparison)。
    • 索引不用于与多个 field path 运算对象的比较(comparison)。
  • pipeline 中的其他(非 $match)stage 不需要 $expr operator 来访问这些变量。

| | as | 指定新的数组字段的名称,以添加到 foreign document 中。新的数组字段包含了 foreign collection 中的匹配文档。如果指定的名称已经存在于 foreign document 中,那么现有的字段将被覆盖(overwritten)。 |

该 operation 对应以下 pseudo-SQL statement(伪 SQL 语句):

  1. select *, <output array field>
  2. from localCollection
  3. where <output array field> in (
  4. select <documents as determined from the pipeline(从管道中确定的文档)>
  5. from <foreignCollection>
  6. where <foreignCollection.foreignField> = <localCollection.localField>
  7. and <pipeline match condition(管道匹配条件)>
  8. )

请参阅以下示例:

  • Perform a Concise Correlated Subquery with $lookup$lookup执行一个简洁的关联子查询

    Considerations 注意事项

    Views and Collation 视图和排序规则

    如果执行涉及 multiple views 的 aggregation,比如用 $lookup$graphLookup,views 必须有相同的 collation

    Restrictions 限制

  • 在 4.2 版本中更改: 您不能在 $lookup stage 中包含 $out$merge stage。 也就是说,当指定一个 pipeline for the joined collection(连接集合的管道),您不能在 pipeline 字段中包含这两个 stage。

    1. {
    2. $lookup:
    3. {
    4. from: <collection to join>,
    5. let: { <var_1>: <expression>, …, <var_n>: <expression> },
    6. pipeline: [ <pipeline to execute on the joined collection> ], // Cannot include $out or $merge
    7. as: <output array field>
    8. }
    9. }

    Sharded Collection Restrictions 分片集合限制

    $lookup stage,from collection 不能被分片(sharded)。然而,你运行 aggregate() 方法的 collection 可以是分片(sharded)的。具体来说,在这个例子中:

    1. db.collection.aggregate([
    2. { $lookup: { from: "fromCollection", ... } }
    3. ])
  • collection可以分片(can be sharded)。

  • fromCollection不能分片(cannot be sharded)。

要将一个 sharded collection 与一个 unsharded collection 连接起来,在 sharded collection 上运行 aggregation,然后 lookup unsharded collection。比如说:

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

或者要 join multiple sharded collections(加入多个分片集合),可以考虑:

  • 修改客户端应用程序以执行手动查找(manual lookups),而不是使用 $lookup aggregation stage。
  • 如果可能的话,使用一个嵌入式(embedded)的数据模型,消除 join collection 的需要。
  • 如果可能的话,使用 Atlas Data Lake$lookup pipeline stage 来查询(lookup)一个分片的集合(a sharded collection)。

    Example 例子

    Perform a Single Equality Join with $lookup$lookup 执行单一相等连接

    用这些文档创建一个 orders collection:
    1. db.orders.insertMany( [
    2. { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
    3. { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
    4. { "_id" : 3 }
    5. ] )
    再用这些文档创建另外一个 inventory collection:
    1. db.inventory.insertMany( [
    2. { "_id" : 1, "sku" : "almonds", "description": "product 1", "instock" : 120 },
    3. { "_id" : 2, "sku" : "bread", "description": "product 2", "instock" : 80 },
    4. { "_id" : 3, "sku" : "cashews", "description": "product 3", "instock" : 60 },
    5. { "_id" : 4, "sku" : "pecans", "description": "product 4", "instock" : 70 },
    6. { "_id" : 5, "sku": null, "description": "Incomplete" },
    7. { "_id" : 6 }
    8. ] )
    orders collection 上的以下 aggregation operation,使用 orders collection 中的 item 字段和 inventory collection 中的 sku 字段,将 orders 的文档和 inventory collection 的文档连接在一起:
    1. db.orders.aggregate( [
    2. {
    3. from: "inventory",
    4. localField: "item",
    5. foreignField: "sku",
    6. as: "inventory_docs"
    7. }
    8. ] )
    该 operation 返回以下文档:
    1. {
    2. "_id" : 1,
    3. "item" : "almonds",
    4. "price" : 12,
    5. "quantity" : 2,
    6. "inventory_docs" : [
    7. { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
    8. ]
    9. }
    10. {
    11. "_id" : 2,
    12. "item" : "pecans",
    13. "price" : 20,
    14. "quantity" : 1,
    15. "inventory_docs" : [
    16. { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
    17. ]
    18. }
    19. {
    20. "_id" : 3,
    21. "inventory_docs" : [
    22. { "_id" : 5, "sku" : null, "description" : "Incomplete" },
    23. { "_id" : 6 }
    24. ]
    25. }
    该 operation 对应以下的 pseudo-SQL statement(伪 SQL 语句):
    1. select *, inventory_docs
    2. from orders
    3. where inventory_docs in (
    4. select *
    5. from inventory
    6. where sku = orders.item
    7. )

    Use $lookup with an Array 在数组中使用 $lookup

    如果localField是一个数组(array),你可以将数组元素(array elements)与标量(scalar)的 foreignField 进行匹配,而不需要 $unwind stage。

例如,用以下文档创建一个 classes collection:

  1. db.classes.insertMany( [
  2. { _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] },
  3. { _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] }
  4. ] )

再用这些文档创建另外一个 members collection:

  1. db.members.insertMany( [
  2. { _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" },
  3. { _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" },
  4. { _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" },
  5. { _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" },
  6. { _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" },
  7. { _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" }
  8. ] )

以下 aggregation operation 将 classescollection 中的文档与 members colleciton 连接起来,在 members字段上用 name字段进行匹配:

  1. db.classes.aggregate( [
  2. {
  3. from: "members",
  4. localField: "enrollmentList",
  5. foreignField: "name",
  6. as: "enrollee_info"
  7. }
  8. ] )

该 operation 返回以下内容:

  1. {
  2. "_id" : 1,
  3. "title" : "Reading is ...",
  4. "enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
  5. "days" : [ "M", "W", "F" ],
  6. "enrollee_info" : [
  7. { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
  8. { "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" },
  9. { "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" }
  10. ]
  11. }
  12. {
  13. "_id" : 2,
  14. "title" : "But Writing ...",
  15. "enrollmentlist" : [ "giraffe1", "artie" ],
  16. "days" : [ "T", "F" ],
  17. "enrollee_info" : [
  18. { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
  19. { "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" }
  20. ]
  21. }

Use $lookup with $mergeObjects$lookup$mergeObjects 一起使用

$mergeObjects operator 将多个文档合并成一个文档。

用以下文档创建一个 orders collection:

  1. db.orders.insertMany( [
  2. { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
  3. { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 }
  4. ] )

再用这些文档创建另一个 items collection:

  1. db.items.insertMany( [
  2. { "_id" : 1, "item" : "almonds", description: "almond clusters", "instock" : 120 },
  3. { "_id" : 2, "item" : "bread", description: "raisin and nut bread", "instock" : 80 },
  4. { "_id" : 3, "item" : "pecans", description: "candied pecans", "instock" : 60 }
  5. ] )

下面的 operation 首先使用 $lookup stage,通过 item 字段连接两个 collection,然后在 $replaceRoot 中使用 $mergeObjects 合并来自 itemsorders 的连接文档:

  1. db.orders.aggregate( [
  2. {
  3. $lookup: {
  4. from: "items",
  5. localField: "item",
  6. foreignField: "item",
  7. as: "fromItems"
  8. }
  9. },
  10. {
  11. $replaceRoot: {
  12. newRoot: {
  13. $mergeObjects: [
  14. {
  15. $arrayElemAt: [ "$fromItems", 0 ]
  16. },
  17. "$$ROOT"
  18. ]
  19. }
  20. }
  21. },
  22. {
  23. $project: { fromItems: 0 }
  24. }
  25. ] )

该 operation 返回以下文档:

  1. {
  2. _id: 1,
  3. item: 'almonds',
  4. description: 'almond clusters',
  5. instock: 120,
  6. price: 12,
  7. quantity: 2
  8. },
  9. {
  10. _id: 2,
  11. item: 'pecans',
  12. description: 'candied pecans',
  13. instock: 60,
  14. price: 20,
  15. quantity: 1
  16. }

Perform Multiple Joins and a Correlated Subquery with $lookup$lookup 执行多个连接和一个关联的子查询

MongoDB 3.6 增加了对在一个 joined collection 上执行 pipeline 的支持,并允许多个 join condition。

join condition 可以引用运行 aggregate() 方法的 local collection 中的字段,并引用 joined collection 中的字段。 这允许两个 collection 之间的 correlated subquery(关联子查询)。

MongoDB 5.0 还支持 consise correlated subqueries(简洁的关联子查询)

用以下文档创建一个 orders collection:

  1. db.orders.insertMany( [
  2. { "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 },
  3. { "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1 },
  4. { "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60 }
  5. ] )

再用这些文档创建另一个 warehouses collection:

  1. db.warehouses.insertMany( [
  2. { "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 },
  3. { "_id" : 2, "stock_item" : "pecans", warehouse: "A", "instock" : 80 },
  4. { "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 },
  5. { "_id" : 4, "stock_item" : "cookies", warehouse: "B", "instock" : 40 },
  6. { "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 }
  7. ] )

下面的例子:

  • orders.itemwarehouses.stock_item 字段上使用 correlated subquery(关联子查询)进行 join。
  • 确保库存商品的数量(quantity of the item in stock)可以满足订购的数量(ordered quantity)。

    1. db.orders.aggregate( [
    2. {
    3. $lookup:
    4. {
    5. from: "warehouses",
    6. let: { order_item: "$item", order_qty: "$ordered" },
    7. pipeline: [
    8. { $match:
    9. { $expr:
    10. { $and:
    11. [
    12. { $eq: [ "$stock_item", "$$order_item" ] },
    13. { $gte: [ "$instock", "$$order_qty" ] }
    14. ]
    15. }
    16. }
    17. },
    18. { $project: { stock_item: 0, _id: 0 } }
    19. ],
    20. as: "stockdata"
    21. }
    22. }
    23. ] )

    该 operation 返回以下文档:

    1. {
    2. _id: 1,
    3. item: 'almonds',
    4. price: 12,
    5. ordered: 2,
    6. stockdata: [
    7. { warehouse: 'A', instock: 120 },
    8. { warehouse: 'B', instock: 60 }
    9. ]
    10. },
    11. {
    12. _id: 2,
    13. item: 'pecans',
    14. price: 20,
    15. ordered: 1,
    16. stockdata: [ { warehouse: 'A', instock: 80 } ]
    17. },
    18. {
    19. _id: 3,
    20. item: 'cookies',
    21. price: 10,
    22. ordered: 60,
    23. stockdata: [ { warehouse: 'A', instock: 80 } ]
    24. }

    该 operation 对应以下 pseudo-SQL statement(伪 SQL 语句):

    1. select *, stockdata
    2. from orders
    3. where stockdata in (
    4. select warehouse, instock
    5. from warehouses
    6. where stock_item = orders.item
    7. and instock >= orders.ordered
    8. )

    从 MongoDB 5.0 开始,放置在 $expr operator 中的 $eq$lt$lte$gt$gte comparison operators 可以使用 $lookup stage 中引用的 from collection 上的索引(index)。 限制条件:

  • 不使用 Multikey indexes(多键索引)

  • 运算对象为数组运算对象类型为 undefined 的情况下,不使用索引进行比较(comparison)。
  • 索引不用于与多个 field path 运算对象的比较(comparison)。

例如,如果索引 { stock_item: 1, stock: 1 } 存在于 warehouses collection 上:

  • warehouses.stock_item 字段上的相等匹配使用索引。
  • warehouses.instock 字段的范围查询部分也使用复合索引中的索引字段。

TIP
参阅:

Perform an Uncorrelated Subquery with $lookup$lookup 执行一个不关联的子查询

一个 aggregation pipeline $lookup stage 可以在 joined collection 上执行 pipeline,它允许 uncorrelated subquery。 一个 uncorrelated subquery 不引用 joined document 字段。

NOTE 从 MongoDB 5.0 开始,对于包含 $sample stage、$sampleRate operator 或 $rand operator 的 $lookup pipeline stage 中的 uncorrelated subquery,如果重复,subquery 总是被再次运行。 以前,根据 subquery 的输出大小,要么缓存 subquery 的输出,要么再次运行 subquery。

用以下文档创建一个 absences collection:

  1. db.absences.insertMany( [
  2. { "_id" : 1, "student" : "Ann Aardvark", sickdays: [ new Date ("2018-05-01"),new Date ("2018-08-23") ] },
  3. { "_id" : 2, "student" : "Zoe Zebra", sickdays: [ new Date ("2018-02-01"),new Date ("2018-05-23") ] },
  4. ] )

再用这些文档创建另一个 holidays collection:

  1. db.holidays.insertMany( [
  2. { "_id" : 1, year: 2018, name: "New Years", date: new Date("2018-01-01") },
  3. { "_id" : 2, year: 2018, name: "Pi Day", date: new Date("2018-03-14") },
  4. { "_id" : 3, year: 2018, name: "Ice Cream Day", date: new Date("2018-07-15") },
  5. { "_id" : 4, year: 2017, name: "New Years", date: new Date("2017-01-01") },
  6. { "_id" : 5, year: 2017, name: "Ice Cream Day", date: new Date("2017-07-16") }
  7. ] )

以下 operation 将 absences collection 与 holidays collection 中的 2018 年假期的信息连接在一起:

  1. db.absences.aggregate( [
  2. {
  3. $lookup:
  4. {
  5. from: "holidays",
  6. pipeline: [
  7. { $match: { year: 2018 } },
  8. { $project: { _id: 0, date: { name: "$name", date: "$date" } } },
  9. { $replaceRoot: { newRoot: "$date" } }
  10. ],
  11. as: "holidays"
  12. }
  13. }
  14. ] )

该 operation 返回以下内容:

  1. {
  2. _id: 1,
  3. student: 'Ann Aardvark',
  4. sickdays: [
  5. ISODate("2018-05-01T00:00:00.000Z"),
  6. ISODate("2018-08-23T00:00:00.000Z")
  7. ],
  8. holidays: [
  9. { name: 'New Years', date: ISODate("2018-01-01T00:00:00.000Z") },
  10. { name: 'Pi Day', date: ISODate("2018-03-14T00:00:00.000Z") },
  11. { name: 'Ice Cream Day', date: ISODate("2018-07-15T00:00:00.000Z")
  12. }
  13. ]
  14. },
  15. {
  16. _id: 2,
  17. student: 'Zoe Zebra',
  18. sickdays: [
  19. ISODate("2018-02-01T00:00:00.000Z"),
  20. ISODate("2018-05-23T00:00:00.000Z")
  21. ],
  22. holidays: [
  23. { name: 'New Years', date: ISODate("2018-01-01T00:00:00.000Z") },
  24. { name: 'Pi Day', date: ISODate("2018-03-14T00:00:00.000Z") },
  25. { name: 'Ice Cream Day', date: ISODate("2018-07-15T00:00:00.000Z")
  26. }
  27. ]
  28. }

该 operation 对象以下 pseudo-SQL statement(伪 SQL 语句):

  1. select *, holidays
  2. from absences
  3. where holidays in (
  4. select name, date
  5. from holidays
  6. where year = 2018
  7. )

Perform a Concise Correlated Subquery with $lookup$lookup 执行一个简洁的关联子查询

从 MongoDB 5.0 开始,aggregation pipeline $lookup stage 支持 concise correlated subquery syntax(简洁的关联子查询语法),可改进 collection 之间的 join。 新的 concise syntax 消除了对 $match stage 中对 $expr operator 内部的 foreign 和 local 字段进行相等匹配的要求。

创建一个 restaurants collection:

  1. db.restaurants.insertMany( [
  2. {
  3. _id: 1,
  4. name: "American Steak House",
  5. food: [ "filet", "sirloin" ],
  6. beverages: [ "beer", "wine" ]
  7. },
  8. {
  9. _id: 2,
  10. name: "Honest John Pizza",
  11. food: [ "cheese pizza", "pepperoni pizza" ],
  12. beverages: [ "soda" ]
  13. }
  14. ] )

创建另一个带有食物和可选饮料的 orders collection:

  1. db.orders.insertMany( [
  2. {
  3. _id: 1,
  4. item: "filet",
  5. restaurant_name: "American Steak House"
  6. },
  7. {
  8. _id: 2,
  9. item: "cheese pizza",
  10. restaurant_name: "Honest John Pizza",
  11. drink: "lemonade"
  12. },
  13. {
  14. _id: 3,
  15. item: "cheese pizza",
  16. restaurant_name: "Honest John Pizza",
  17. drink: "soda"
  18. }
  19. ] )

下面例子的含义:

  • 通过匹配 localField orders.restaurant_nameforeignField restaurant.name 来 join ordersrestaurants collection。 匹配在 pipeline 运行之前执行。
  • orders.drinkrestaurants.beverages 字段之间执行一个 $in 数组匹配,这些字段分别、依次使用 $$orders_drink$beverages 访问。
    1. db.orders.aggregate( [
    2. {
    3. $lookup:
    4. {
    5. from: "restaurants",
    6. localField: "restaurant_name",
    7. foreignField: "name",
    8. let: { orders_drink: "$drink" },
    9. pipeline: [
    10. {
    11. $match: {
    12. $expr: { $in: [ "$$orders_drink", "$beverages" ] }
    13. }
    14. }
    15. ],
    16. as: "matches"
    17. }
    18. }
    19. ] )
    orders.drinkrestaurant.beverages 字段中的 soda 值匹配。 这个输出显示了匹配(matches)数组,包含了所有来自 restaurants collection 的匹配字段:
    1. {
    2. "_id" : 1, "item" : "filet",
    3. "restaurant_name" : "American Steak House",
    4. "matches" : [ ]
    5. }
    6. {
    7. "_id" : 2, "item" : "cheese pizza",
    8. "restaurant_name" : "Honest John Pizza",
    9. "drink" : "lemonade",
    10. "matches" : [ ]
    11. }
    12. {
    13. "_id" : 3, "item" : "cheese pizza",
    14. "restaurant_name" : "Honest John Pizza",
    15. "drink" : "soda",
    16. "matches" : [ {
    17. "_id" : 2, "name" : "Honest John Pizza",
    18. "food" : [ "cheese pizza", "pepperoni pizza" ],
    19. "beverages" : [ "soda" ]
    20. } ]
    21. }
    在引入 concise correlated subqueries 之前,您必须在 pipeline $lookup stage 的 $expr operator 中使用 local field 和 joined field 之间的 $eq 相等匹配,如 Perform Multiple Joins and a Correlated Subquery with $lookup(使用 $lookup 执行多个连接和关联子查询中所示)

    此示例使用 5.0 之前的 MongoDB 版本中较旧的详细语法,并返回与前面的 concise 示例相同的结果:
    1. db.orders.aggregate( [
    2. {
    3. $lookup: {
    4. from: "restaurants",
    5. let: { orders_restaurant_name: "$restaurant_name",
    6. orders_drink: "$drink" },
    7. pipeline: [ {
    8. $match: {
    9. $expr: {
    10. $and: [
    11. { $eq: [ "$$orders_restaurant_name", "$name" ] },
    12. { $in: [ "$$orders_drink", "$beverages" ] }
    13. ]
    14. }
    15. }
    16. } ],
    17. as: "matches"
    18. }
    19. }
    20. ] )
    前面的例子对应这个 pseudo-SQL statement(伪 SQL 语句):
    1. SELECT *, matches
    2. FROM orders
    3. WHERE matches IN (
    4. SELECT *
    5. FROM restaurants
    6. WHERE restaurants.name = orders.restaurant_name
    7. // 这里应该是官方写错了,因为 beverages 是一个数组,而 drink 是一个字符串,应该是 orders.drink in restaurants.beverages
    8. AND restaurants.beverages = orders.drink
    9. );

    参考

    https://docs.mongodb.com/manual/reference/operator/aggregation/lookup