$lookup (aggregation)

On this page本页内容

Definition定义

$lookup

New in version 3.2.版本3.2中的新功能。

Performs a left outer join to an unsharded collection in the same database to filter in documents from the “joined” collection for processing. 对同一数据库中的未分片集合执行左外部联接,以从“联接”集合中筛选文档进行处理。To each input document, the $lookup stage adds a new array field whose elements are the matching documents from the “joined” collection. $lookup阶段向每个输入文档添加一个新的数组字段,其元素是来自“joined”集合的匹配文档。The $lookup stage passes these reshaped documents to the next stage.$lookup阶段将这些重新成形的文档传递到下一阶段。

Syntax语法

The $lookup stage has the following syntaxes:$lookup阶段具有以下语法:

Equality Match相等匹配

To perform an equality match between a field from the input documents with a field from the documents of the “joined” collection, the $lookup stage has the following syntax:要在输入文档中的字段与“联接”集合中的文档中的字段之间执行相等匹配,loopup阶段语法如下所示:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

The $lookup takes a document with the following fields:$lookup获取具有以下字段的文档:

Field字段Description描述
from

Specifies the collection in the same database to perform the join with. 指定同一数据库中要执行联接的集合。The from collection cannot be sharded. 无法分割from集合的数据。For details, see Sharded Collection Restrictions.有关详细信息,请参阅分片集合局限性

localField

Specifies the field from the documents input to the $lookup stage. 指定从文档输入到$lookup阶段的字段。$lookup performs an equality match on the localField to the foreignField from the documents of the from collection. $lookuplocalFieldfrom集合文档中的foreignField上执行相等匹配。If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes.如果输入文档不包含localField$lookup将该字段视为具有null值以进行匹配。

foreignField

Specifies the field from the documents in the from collection. from集合中的文档中指定字段。$lookup performs an equality match on the foreignField to the localField from the input documents. $lookup对输入文档中的foreignFieldlocalField执行相等匹配。If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes.如果from集合中的文档不包含foreignField,则$lookup会将该值视为null以进行匹配。

as

Specifies the name of the new array field to add to the input documents. 指定要添加到输入文档的新数组字段的名称。The new array field contains the matching documents from the from collection. 新数组字段包含from集合的匹配文档。If the specified name already exists in the input document, the existing field is overwritten.如果输入文档中已存在指定的名称,则覆盖现有字段。

The operation would correspond to the following pseudo-SQL statement:该操作将对应于以下伪SQL语句:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
                               FROM <collection to join>
                               WHERE <foreignField>= <collection.localField>);

See the following examples:请参见以下示例:

Join Conditions and Uncorrelated Sub-queries连接条件和不相关子查询

New in version 3.6.版本3.6中的新功能。

To perform uncorrelated subqueries between two collections as well as allow other join conditions besides a single equality match, the $lookup stage has the following syntax:要在两个集合之间执行不相关的子查询,以及允许除单个相等匹配之外的其他联接条件,$lookup阶段语法如下所示:

{
   $lookup:
     {
       from: <collection to join>,
       let: { <var_1>: <expression>, …, <var_n>: <expression> },
       pipeline: [ <pipeline to execute on the collection to join> ],
       as: <output array field>
     }
}

The $lookup takes a document with the following fields:$lookup获取具有以下字段的文档:

Field字段Description描述
from

Specifies the collection in the same database to perform the join with. The from collection cannot be sharded. 指定同一数据库中要执行联接的集合。无法分割来自集合的数据。For details, see Sharded Collection Restrictions.有关详细信息,请参阅分片集合局限性。

let

Optional. 可选。Specifies variables to use in the pipeline field stages. 指定要在管道字段阶段中使用的变量。Use the variable expressions to access the fields from the documents input to the $lookup stage.使用变量表达式访问从文档输入到$lookup阶段的字段。

The pipeline cannot directly access the input document fields. 管道无法直接访问输入文档字段。Instead, first define the variables for the input document fields, and then reference the variables in the stages in the pipeline.相反,首先为输入文档字段定义变量,然后在pipeline中的阶段中引用变量。

Note

To reference variables in pipeline stages, use the "$$<variable>" syntax.要引用管道阶段中的变量,请使用"$$<variable>"语法。

The let variables can be accessed by the stages in the pipeline, including additional $lookup stages nested in the pipeline.let变量可以由管道中的阶段访问,包括pipeline中嵌套的其他$lookup阶段。

  • A $match stage requires the use of an $expr operator to access the variables. $match阶段需要使用$expr运算符来访问变量。$expr allows the use of aggregation expressions inside of the $match syntax.$expr允许在$match语法中使用聚合表达式。

    Without the use of the $expr operator, $match can refer to fields in a document but cannot access variables defined by a $lookup let clause.如果不使用$expr运算符,$match可以引用文档中的字段,但不能访问由$lookup let子句定义的变量。

    The $expr operator only uses indexes on the from collection for equality matches. $expr运算符仅使用from集合上的索引进行相等匹配。Non-equality match queries, such as range queries, cannot use indexes on the from collection.非相等匹配查询(如范围查询)不能在from集合上使用索引。

  • Other (non-$match) stages in the pipeline do not require an $expr operator to access the variables.管道中的其他(非$match)阶段不需要$expr运算符来访问变量。
pipeline

Specifies the pipeline to run on the joined collection. 指定要在联接的集合上运行的管道。The pipeline determines the resulting documents from the joined collection. pipeline确定从关联集合生成的文档。To return all documents, specify an empty pipeline [].要返回所有文档,请指定空管道[]

The pipeline cannot include the $out stage or the $merge stage.pipeline不能包括$out阶段或$merge阶段。

The pipeline cannot directly access the input document fields. pipeline无法直接访问输入文档字段。Instead, first define the variables for the input document fields, and then reference the variables in the stages in the pipeline.相反,首先为输入文档字段定义变量,然后在pipeline中的阶段中引用变量。

Note

To reference variables in pipeline stages, use the "$$<variable>" syntax.要引用管道阶段中的变量,请使用"$$<variable>"语法。

The let variables can be accessed by the stages in the pipeline, including additional $lookup stages nested in the pipeline.let变量可以由管道中的阶段访问,包括pipeline中嵌套的其他$lookup阶段。

  • A $match stage requires the use of an $expr operator to access the variables. $match阶段需要使用$expr运算符来访问变量。$expr allows the use of aggregation expressions inside of the $match syntax.$expr允许在$match语法中使用聚合表达式。

    Without the use of the $expr operator, $match can refer to fields in a document but cannot access variables defined by a $lookup let clause.如果不使用$expr运算符,$match可以引用文档中的字段,但不能访问由$lookup let子句定义的变量。

    The $expr operator only uses indexes on the from collection for equality matches. $expr运算符仅使用from集合上的索引进行相等匹配。Non-equality match queries, such as range queries, cannot use indexes on the from collection.非相等匹配查询(如范围查询)不能在from集合上使用索引。

  • Other (non-$match) stages in the pipeline do not require an $expr operator to access the variables.管道中的其他(非$match)阶段不需要$expr运算符来访问变量。
as

Specifies the name of the new array field to add to the input documents. 指定要添加到输入文档的新数组字段的名称。The new array field contains the matching documents from the from collection. 新数组字段包含from集合的匹配文档。If the specified name already exists in the input document, the existing field is overwritten.如果输入文档中已存在指定的名称,则覆盖现有字段。

The operation would correspond to the following pseudo-SQL statement:该操作将对应于以下伪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> );

See the following examples:请参见以下示例:

Consideration考虑事项

Views and Collation视图和排序规则

If performing an aggregation that involves multiple views, such as with $lookup or $graphLookup, the views must have the same collation.如果执行涉及多个视图的聚合,例如使用$lookup$graphLookup,则这些视图必须具有相同的排序规则。

Restrictions局限性

  • Changed in version 4.2.在版本4.2中更改。You cannot include the $out or the $merge stage in the $lookup stage. $lookup阶段中不能包含$out$merge阶段。That is, when specifying a pipeline for the joined collection, you cannot include either stage in the pipeline field.也就是说,在为联接的集合指定管道时,不能在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>
        }
    }

Sharded Collection Restrictions分片收集局限性

In the $lookup stage, the from collection cannot be sharded. $lookup阶段中,无法分割from集合。However, the collection on which you run the aggregate() method can be sharded. 但是,可以对运行aggregate()方法的集合进行切分。That is, in the following:也就是说,在以下方面:

db.collection.aggregate([
   { $lookup: { from: "fromCollection", ... } }
])
  • The collection can be sharded.collection可以分片。
  • The fromCollection cannot be sharded.fromCollection不能分片。

As such, to join a sharded collection with an unsharded collection, you can run the aggregation on the sharded collection and lookup the unsharded collection; e.g.:因此,要将分片集合与非分片集合联接,可以在分片集合上运行聚合并查找非分片集合;例如。:

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

Alternatively, or to join multiple sharded collections, consider:或者,要加入多个分片集合,请考虑:

  • Modifying client applications to perform manual lookups instead of using the $lookup aggregation stage.修改客户端应用程序以执行手动查找,而不是使用$lookup聚合阶段。
  • If possible, using an embedded data model that removes the need to join collections.如果可能,请使用嵌入式数据模型,以消除加入集合的需要。

Examples示例

Perform a Single Equality Join with $lookup使用$lookup执行单个相等联接

Create a collection orders with the following documents:使用以下文档创建orders集合:

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

Create another collection inventory with the following documents:使用以下文档创建另一个inventory集合:

db.inventory.insert([
   { "_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 }
])

The following aggregation operation on the orders collection joins the documents from orders with the documents from the inventory collection using the fields item from the orders collection and the sku field from the inventory collection:orders集合上的以下聚合操作使用订单集合中的字段项和库存集合中的sku字段,将订单中的文档与库存集合中的文档关联起来:

db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }
])

The operation returns the following documents:该操作将返回以下文档:

{
   "_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 }
   ]
}

The operation would correspond to the following pseudo-SQL statement:该操作将对应于以下伪SQL语句:

SELECT *, inventory_docs
FROM orders
WHERE inventory_docs IN (SELECT *
FROM inventory
WHERE sku= orders.item);

Use $lookup with an Array对数组使用$lookup

Starting MongoDB 3.4, if the localField is an array, you can match the array elements against a scalar foreignField without needing an $unwind stage.从MongoDB 3.4开始,如果localField是一个数组,则可以将数组元素与标量foreignField进行匹配,而无需$unwind阶段。

For example, create an example collection classes with the following document:例如,使用以下文档创建示例集合classes

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

Create another collection members with the following documents:使用以下文档创建另一个集合members

db.members.insert( [
   { _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" }
])

The following aggregation operation joins documents in the classes collection with the members collection, matching on the members field to the name field:以下聚合操作将classes集合中的文档与members集合联接,并在members字段与name字段上进行匹配:

db.classes.aggregate([
   {
      $lookup:
         {
            from: "members",
            localField: "enrollmentlist",
            foreignField: "name",
            as: "enrollee_info"
        }
   }
])

The operation returns the following:该操作返回以下内容:

{
   "_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" }
   ]
}

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

Changed in version 3.6:在版本3.6中更改:MongoDB 3.6 adds the $mergeObjects operator to combine multiple documents into a single documentMongoDB 3.6添加了$mergeObjects运算符以将多个文档合并到一个文档中

Create a collection orders with the following documents:使用以下文档创建集合orders

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

Create another collection items with the following documents:使用以下文档创建另一个集合items

db.items.insert([
  { "_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 }
])

The following operation first uses the $lookup stage to join the two collections by the item fields and then uses $mergeObjects in the $replaceRoot to merge the joined documents from items and orders:以下操作首先使用$lookup阶段通过item字段连接两个集合,然后使用$replaceRoot中的$mergeObjects合并来自项和订单的连接文档:

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

The operation returns the following documents:该操作将返回以下文档:

{ "_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 }

Specify Multiple Join Conditions with $lookup使用$lookup指定多个联接条件

Changed in version 3.6:在版本3.6中更改:MongoDB 3.6 adds support for executing a pipeline on the joined collection, which allows for specifying multiple join conditions as well as uncorrelated sub-queries.MongoDB 3.6增加了对在连接的集合上执行管道的支持,允许指定多个连接条件以及不相关的子查询。

Create a collection orders with the following documents:使用以下文档创建集合orders

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

Create another collection warehouses with the following documents:使用以下文档创建另一个集合warehouses

db.warehouses.insert([
  { "_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 }
])

The following operation joins the orders collection with the warehouse collection by the item and whether the quantity in stock is sufficient to cover the ordered quantity:以下操作按物料和库存量是否足以覆盖订单数量将orders集合与warehouse集合关联起来:

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

The operation returns the following documents:该操作将返回以下文档:

{ "_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 } ] }

The operation corresponds to the following pseudo-SQL statement:该操作对应于以下伪SQL语句:

SELECT *, stockdata
FROM orders
WHERE stockdata IN ( SELECT warehouse, instock
                     FROM warehouses
                     WHERE stock_item = orders.item
                     AND instock >= orders.ordered );

The $expr operator only uses indexes on the from collection for equality matches. $expr运算符仅使用from集合上的索引进行相等匹配。For example, if the index { stock_item: 1, instock: 1 } exists on the warehouses collection:例如,如果warehouses集合上存在索引{ stock_item: 1, instock: 1 }

  • The equality match on the warehouses.stock_item field uses the index.warehouses.stock_item字段上的相等匹配使用索引。
  • The range part of the query on the warehouses.instock field does not use the indexed field in the compound index.warehouses.instock字段查询的范围部分不使用复合索引中的索引字段。

Uncorrelated Subquery不相关子查询

Changed in version 3.6:在版本3.6中更改:MongoDB 3.6 adds support for executing a pipeline on the joined collection, which allows for specifying multiple join conditions as well as uncorrelated sub-queries.MongoDB 3.6增加了对在连接的集合上执行管道的支持,允许指定多个连接条件以及不相关的子查询。

Create a collection absences with the following documents:使用以下文档创建集合absences

db.absences.insert([
   { "_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") ] },
])

Create another collection holidays with the following documents:使用以下文档创建另一个集合holidays

db.holidays.insert([
   { "_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") }
])

The following operation joins the absences collection with 2018 holiday information from the holidays collection:以下操作将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"
         }
    }
])

The operation returns the following:该操作返回以下内容:

{ "_id" : 1, "student" : "Ann Aardvark", "sickdays" : [ ISODate("2018-05-01T00:00:00Z"), ISODate("2018-08-23T00:00:00Z") ],
    "holidays" : [ { "name" : "New Years", "date" : ISODate("2018-01-01T00:00:00Z") }, { "name" : "Pi Day", "date" : ISODate("2018-03-14T00:00:00Z") }, { "name" : "Ice Cream Day", "date" : ISODate("2018-07-15T00:00:00Z") } ] }
{ "_id" : 2, "student" : "Zoe Zebra", "sickdays" : [ ISODate("2018-02-01T00:00:00Z"), ISODate("2018-05-23T00:00:00Z") ],
    "holidays" : [ { "name" : "New Years", "date" : ISODate("2018-01-01T00:00:00Z") }, { "name" : "Pi Day", "date" : ISODate("2018-03-14T00:00:00Z") }, { "name" : "Ice Cream Day", "date" : ISODate("2018-07-15T00:00:00Z") } ] }

The operation would correspond to the following pseudo-SQL statement:该操作将对应于以下伪SQL语句:

SELECT *, holidays
FROM absences
WHERE holidays IN (SELECT name, date
                    FROM holidays
                    WHERE year = 2018);