$lookup(聚合)

在本页面

  • 定义

  • 句法

  • 考虑

  • 例子

定义

  • $lookup

    • version 3.2 中的新内容。

对同一数据库中的未整数集合执行左外连接,以从“已连接”集合中过滤文档以进行处理。对于每个输入文档,$lookup阶段添加一个新的 array 字段,其元素是来自“已连接”集合的匹配文档。 $lookup阶段将这些重新整形的文档传递到下一阶段。

句法

$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集合无法分片。有关详细信息,请参阅Sharded Collection 限制。
localField指定输入到$lookup阶段的文档中的字段。 $lookup从from集合的文档中localFieldforeignField执行相等匹配。如果输入文档不包含localField,则$lookup将该字段视为具有null的值以进行匹配。
foreignField指定from集合中文档的字段。 $lookup对输入文档中的foreignFieldlocalField执行相等匹配。如果from集合中的文档不包含foreignField,则$lookup将 value 视为null以进行匹配。
as指定要添加到输入文档的新 array 字段的 name。新的 array 字段包含from集合中的匹配文档。如果输入文档中已存在指定的 name,则会覆盖现有字段。

该操作将对应于以下 pseudo-SQL 语句:

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

请参阅以下示例:

  • 使用$lookup 执行单一平等连接

  • 将$lookup 与 Array 一起使用

  • 使用$lookup 和$mergeObjects

加入条件和不相关 Sub-queries

version 3.6 中的新内容。

要在两个集合之间执行不相关的子查询以及允许除单个相等 match 之外的其他连接条件,$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>
     }
}

$lookup采用包含以下字段的文档:

领域描述
from指定同一数据库中的集合以执行连接。 from集合无法分片。有关详细信息,请参阅Sharded Collection 限制。
let可选的。指定要在pipeline字段阶段中使用的变量。使用变量表达式从输入到$lookup阶段的文档中访问字段。
pipeline无法直接访问输入文档字段。相反,首先为输入文档字段定义变量,然后引用pipeline中各阶段的变量。
要访问pipeline中的let变量,请使用$expr operator。
注意

变量可由pipeline中的各阶段访问,包括嵌套在pipeline中的其他$lookup阶段。
pipeline指定要在已连接集合上运行的管道。 pipeline确定已加入集合中的结果文档。要 return 所有文档,请指定一个空管道[]
pipeline无法直接访问输入文档字段。相反,首先为输入文档字段定义变量,然后引用pipeline中各阶段的变量。
要访问pipeline中的let变量,请使用$expr operator。
注意

变量可由pipeline中的各阶段访问,包括嵌套在pipeline中的其他$lookup阶段。
as指定要添加到输入文档的新 array 字段的 name。新的 array 字段包含from集合中的匹配文档。如果输入文档中已存在指定的 name,则会覆盖现有字段。

该操作将对应于以下 pseudo-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> );

请参阅以下示例:

  • 使用$lookup 指定多个连接条件

  • 不相关的子查询

考虑

观点和整理

如果执行涉及多个视图的聚合(例如$lookup或$graphLookup),则视图必须具有相同的整理。

Sharded Collection 限制

在$lookup阶段,from集合不能是分片。但是,可以对方法运行的集合进行分片。也就是说,在以下内容中:

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

  • fromCollection无法分片。

因此,要使用未整理的集合连接分片集合,您可以在分片集合上运行聚合并查找未整理的集合; e.g. :

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

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

  • 修改 client applications 以执行手动查找,而不是使用$lookup聚合阶段。

  • 如果可能,使用嵌入数据 model消除加入集合的需要。

例子

使用$lookup 执行单一平等连接

使用以下文档创建集合orders

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

使用以下文档创建另一个集合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 }
])

orders集合的以下聚合操作使用orders集合中的字段iteminventory集合中的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 }
   ]
}

该操作将对应于以下 pseudo-SQL 语句:

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

将$lookup 与 Array 一起使用

启动 MongoDB 3.4,如果localField是 array,则可以_对数据foreignField匹配 array 元素,而不需要$unwind阶段。

对于 example,使用以下文档创建 example 集合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"] }
])

使用以下文档创建另一个集合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" }
])

以下聚合操作将classes集合中的文档与members集合连接,在members字段上与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

更改 version 3.6:MongoDB 3.6 添加$mergeObjects operator 将多个文档合并为一个文档

使用以下文档创建集合orders

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

使用以下文档创建另一个集合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 }
])

以下操作首先使用$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 }

使用$lookup 指定多个连接条件

在 version 3.6 中更改:MongoDB 3.6 添加了对连接集合上执行管道的支持,这允许指定多个连接条件以及不相关的 sub-queries。

使用以下文档创建集合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 }
])

使用以下文档创建另一个集合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 }
])

以下操作将orders集合与 item 的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"
         }
    }
])

该操作返回以下文档:

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

该操作将对应于以下 pseudo-SQL 语句:

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

也可以看看
$expr

不相关的子查询

在 version 3.6 中更改:MongoDB 3.6 添加了对连接集合上执行管道的支持,这允许指定多个连接条件以及不相关的 sub-queries。

使用以下文档创建集合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") ] },
])

使用以下文档创建另一个集合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") }
])

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

该操作将对应于以下 pseudo-SQL 语句:

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