定义
- $graphLookup
- 5.1 版本中进行了更改。 - 对集合执行递归搜索,并提供按照递归深度和查询筛选器限制搜索的选项。 - $graphLookup搜索过程总结如下:- 输入文档流入聚合操作的 - $graphLookup阶段。
- $graphLookup将搜索定位到由- from参数指定的集合(有关搜索参数的完整列表,请参见下文)。
- 对于每份输入文档,搜索从 - startWith指定的值开始。
- $graphLookup将- startWith值与- from集合中其他文档的- connectToField指定字段进行匹配。
- 对于每份匹配文档, - $graphLookup获取- connectFromField的值,并检查- from集合中的每份文档是否有匹配的- connectToField值。对于每次匹配,- $graphLookup将- from集合中的匹配文档添加到由- as参数命名的数组字段中。- 此步骤以递归方式继续,直到找不到更多匹配文档,或者直到操作达到 - maxDepth参数指定的递归深度。然后,- $graphLookup将数组字段追加到输入文档。- $graphLookup在完成对所有输入文档的搜索后返回结果。
 - $graphLookup具有以下原型形式:- { - $graphLookup: { - from: <collection>, - startWith: <expression>, - connectFromField: <string>, - connectToField: <string>, - as: <string>, - maxDepth: <number>, - depthField: <string>, - restrictSearchWithMatch: <document> - } - } - $graphLookup接受包含以下字段的文档:字段说明- from- $graphLookup操作要搜索的目标集合,以递归方式将- connectFromField- connectToField与 进行匹配。- from集合必须与此操作中使用的任何其他集合位于同一数据库中。- 从 MongoDB 5.1 开始,可以对 - from参数中指定的集合进行分片。- startWith- connectFromField- $graphLookup- connectToField字段名称,其值 用于以递归方式匹配集合中其他文档的 。如果该值为大量,则每个元素都会单独完成遍历进程。- connectToField- 其他文档中的字段名称,用于与 - connectFromField参数指定的字段值相匹配。- as- 添加到每个输出文档的大量字段的名称。 包含在 - $graphLookup阶段为访问文档而遍历的文档。- 不保证 - as字段中返回的文档按任何顺序排列。- maxDepth- 可选。指定最大递归深度的非负整数。 - depthField- 可选。要添加到搜索路径中每个已遍历文档的字段的名称。该字段的值为文档的递归深度,并用 - NumberLong表示。递归深度值从零开始,因此第一次查找对应于零深度。- restrictSearchWithMatch
Considerations
分片集合
从 MongoDB 5.1 开始,可以在 $graphLookup 阶段的 from 参数中指定分片集合。
当以分片集合为目标时,您无法在事务中使用 $graphLookup 阶段。
最大深度
将 maxDepth 字段设置为 0 相当于一个非递归的 $graphLookup 搜索阶段。
内存
如果 $graphLookup 阶段消耗的内存超过 100 MB,它会自动将临时文件写入磁盘。您可以通过serverStatus命令查看$graphLookup何时使用磁盘,并在executionStats详细模式下通过explain()命令查看$graphLookup磁盘使用情况的说明。
如果 $graphLookup 阶段超过 100 兆字节内存且 allowDiskUse 选项设立为 false,$graphLookup 将返回错误。
请参阅聚合管道限制,获取更多信息。
未排序的结果
$graphLookup 阶段不返回排序结果。要对结果进行排序,请使用$sortArray 操作符。
视图和排序规则
如果执行的聚合涉及多个视图(如使用 $lookup 或 $graphLookup),则这些视图必须具有相同的排序规则。
示例
单个集合内
名为 employees 的集合包含以下文档:
db.employees.insertMany( [    { _id: 1, name: "Dev" },    { _id: 2, name: "Eliot", reportsTo: "Dev" },    { _id: 3, name: "Ron", reportsTo: "Eliot" },    { _id: 4, name: "Andrew", reportsTo: "Eliot" },    { _id: 5, name: "Asya", reportsTo: "Ron" },    { _id: 6, name: "Dan", reportsTo: "Andrew" } ] ) 
以下 $graphLookup 操作递归匹配 employees 集合中的 reportsTo 和 name 字段,返回每个人员的报告层次结构:
db.employees.aggregate( [    {       $graphLookup: {          from: "employees",          startWith: "$reportsTo",          connectFromField: "reportsTo",          connectToField: "name",          as: "reportingHierarchy"       }    } ] ) 
输出结果如下:
{    _id: 1,    name: "Dev",    reportingHierarchy: [ ] } {    _id: 2,    name: "Eliot",    reportsTo: "Dev",    reportingHierarchy : [       { _id: 1, name: "Dev" }    ] } {    _id: 3,    name: "Ron",    reportsTo: "Eliot",    reportingHierarchy: [       { _id: 2, name: "Eliot", reportsTo: "Dev" },       { _id: 1, name: "Dev" }    ] } {    _id: 4,    name: "Andrew",    reportsTo: "Eliot",    reportingHierarchy: [       { _id: 2, name: "Eliot", reportsTo: "Dev" },       { _id: 1, name: "Dev" }    ] } {    _id: 5,    name: "Asya",    reportsTo: "Ron",    reportingHierarchy: [       { _id: 2, name: "Eliot", reportsTo: "Dev" },       { _id: 3, name: "Ron", reportsTo: "Eliot" },       { _id: 1, name: "Dev" }    ] } {    "_id" : 6,    "name" : "Dan",    "reportsTo" : "Andrew",    "reportingHierarchy" : [       { _id: 4, name: "Andrew", reportsTo: "Eliot" },       { _id: 2, name: "Eliot", reportsTo: "Dev" },       { _id: 1, name: "Dev" }    ] } 
下表提供文档 { "_id" : 5, "name" : "Asya", "reportsTo" : "Ron" } 的遍历路径:
| 起始值 | 文档的   | |
| 深度 0 |  | |
| 深度 1 |  | |
| 深度 2 |  | 
输出生成层次结构Asya -> Ron -> Eliot -> Dev。
跨多个集合
与 $lookup 一样,$graphLookup 可以访问同一数据库中的另一个集合。
例如,创建一个包含两个集合的数据库:
- 包含以下文档的 - airports集合:- db.airports.insertMany( [ - { _id: 0, airport: "JFK", connects: [ "BOS", "ORD" ] }, - { _id: 1, airport: "BOS", connects: [ "JFK", "PWM" ] }, - { _id: 2, airport: "ORD", connects: [ "JFK" ] }, - { _id: 3, airport: "PWM", connects: [ "BOS", "LHR" ] }, - { _id: 4, airport: "LHR", connects: [ "PWM" ] } - ] ) 
- 包含以下文档的 - travelers集合:- db.travelers.insertMany( [ - { _id: 1, name: "Dev", nearestAirport: "JFK" }, - { _id: 2, name: "Eliot", nearestAirport: "JFK" }, - { _id: 3, name: "Jeff", nearestAirport: "BOS" } - ] ) 
对于travelers集合中的每个文档,以下聚合操作会在airports集合中查找nearestAirport值,并以递归方式将connects字段与airport字段进行匹配。该操作指定最大递归深度为2。
db.travelers.aggregate( [    {       $graphLookup: {          from: "airports",          startWith: "$nearestAirport",          connectFromField: "connects",          connectToField: "airport",          maxDepth: 2,          depthField: "numConnections",          as: "destinations"       }    } ] ) 
输出结果如下:
{    _id: 1,    name: "Dev",    nearestAirport: "JFK",    destinations: [       { _id: 3,         airport: "PWM",         connects: [ "BOS", "LHR" ],         numConnections: Long(2) },       { _id: 2,         airport: "ORD",         connects: [ "JFK" ],         numConnections: Long(1) },       { _id: 1,         airport: "BOS",         connects: [ "JFK", "PWM" ],         numConnections: Long(1) },       { _id: 0,         airport: "JFK",         connects: [ "BOS", "ORD" ],         numConnections: Long(0) }    ] } {    _id: 2,    name: "Eliot",    nearestAirport: "JFK",    destinations: [       { _id: 3,         airport: "PWM",         connects: [ "BOS", "LHR" ],         numConnections: Long(2) },       { _id: 2,         airport: "ORD",         connects: [ "JFK" ],         numConnections: Long(1) },       { _id: 1,         airport: "BOS",         connects: [ "JFK", "PWM" ],         numConnections: Long(1) },       { _id: 0,         airport: "JFK",         connects: [ "BOS", "ORD" ],         numConnections: Long(0) } ] } {    "_id" : 3,    name: "Jeff",    nearestAirport: "BOS",    destinations: [       { _id: 2,         airport: "ORD",         connects: [ "JFK" ],         numConnections: Long(2) },       { _id: 3,         airport: "PWM",         connects: [ "BOS", "LHR" ],         numConnections: Long(1) },       { _id: 4,         airport: "LHR",         connects: [ "PWM" ],         numConnections: Long(2) },       { _id:: 0,         airport: "JFK",         connects: [ "BOS", "ORD" ],         numConnections: Long(1) },       { _id:: 1,         airport: "BOS",         connects: [ "JFK", "PWM" ],         numConnections: Long(0) }    ] } 
下表提供了递归搜索的遍历路径,深度为 2,其中起始 airport 为 JFK:
| 起始值 | 
  | ||
| 深度 0 |  | ||
| 深度 1 |  | ||
| 深度 2 |  | 
使用查询过滤器
以下示例使用一个包含一组文档的集合,其中包含人员姓名及其朋友和爱好的数组。聚合操作找到一个特定的人,并遍历她的人际网络,以找到在其爱好中列出golf的人。
一个名为 people 的集合包含以下文档:
db.people.insertMany( [    {       _id: 1,       name: "Tanya Jordan",       friends: [ "Shirley Soto", "Terry Hawkins", "Carole Hale" ],       hobbies: [ "tennis", "unicycling", "golf" ]    },    {       _id: 2,       name: "Carole Hale",       friends: [ "Joseph Dennis", "Tanya Jordan", "Terry Hawkins" ],       hobbies: [ "archery", "golf", "woodworking" ]    },    {       _id: 3,       name: "Terry Hawkins",       friends: [ "Tanya Jordan", "Carole Hale", "Angelo Ward" ],       hobbies: [ "knitting", "frisbee" ]    },    {       _id: 4,       name: "Joseph Dennis",       friends: [ "Angelo Ward", "Carole Hale" ],       hobbies: [ "tennis", "golf", "topiary" ]    },    {       _id: 5,       name: "Angelo Ward",       friends: [ "Terry Hawkins", "Shirley Soto", "Joseph Dennis" ],       hobbies: [ "travel", "ceramics", "golf" ]       },       {          _id: 6,          name: "Shirley Soto",          friends: [ "Angelo Ward", "Tanya Jordan", "Carole Hale" ],          hobbies: [ "frisbee", "set theory" ]    } ] ) 
以下聚合操作使用三个阶段:
- $match会对- name字段包含字符串- "Tanya Jordan"的文档进行匹配。返回一个输出文档。
- $graphLookup将输出文档的- friends字段与集合中其他文档的- name字段连接,以遍历- Tanya Jordan's连接网络。该阶段使用- restrictSearchWithMatch参数,只查找- hobbies数组包含- golf的文档。返回一个输出文档。
- $project会确定输出文档的形状。- connections who play golf中列出的名称取自输入文档的- golfers数组中所列文档的- name字段。
db.people.aggregate( [   { $match: { "name": "Tanya Jordan" } },   { $graphLookup: {       from: "people",       startWith: "$friends",       connectFromField: "friends",       connectToField: "name",       as: "golfers",       restrictSearchWithMatch: { "hobbies" : "golf" }     }   },   { $project: {       "name": 1,       "friends": 1,       "connections who play golf": "$golfers.name"     }   } ] ) 
该操作将返回以下文档:
{    _id: 1,    name: "Tanya Jordan",    friends: [       "Shirley Soto",       "Terry Hawkins",       "Carole Hale"    ],    'connections who play golf': [       "Joseph Dennis",       "Tanya Jordan",       "Angelo Ward",       "Carole Hale"    ] } 
名为 employees 的集合包含以下文档:
{ _id: 1, name: "Dev" }, { _id: 2, name: "Eliot", reportsTo: "Dev" }, { _id: 3, name: "Ron", reportsTo: "Eliot" }, { _id: 4, name: "Andrew", reportsTo: "Eliot" }, { _id: 5, name: "Asya", reportsTo: "Ron" }, { _id: 6, name: "Dan", reportsTo: "Andrew" } 
以下 Employee 类对 employees集合中的文档进行建模:
public class Employee {     public ObjectId Id { get; set; }     public string Name { get; set; }     public Employee ReportsTo { get; set; }     public List<Employee> ReportingHierarchy { get; set; }          public List<string> Hobbies { get; set; } } 
要使用MongoDB .NET/ C#驾驶员将 $graphLookup 阶段添加到聚合管道,请对 PipelineDefinition对象调用 GraphLookup() 方法。
以下示例创建了一个管道阶段,该阶段以递归方式匹配 employees集合中的 ReportsTo 和 Name 字段,从而返回每个人员的报告层次结构:
var pipeline = new EmptyPipelineDefinition<Employee>()     .GraphLookup<Employee, Employee, Employee, Employee, string, Employee, List<Employee>, Employee>(         from: employeeCollection,         connectFromField: e => e.ReportsTo,         connectToField: e => e.Name,         startWith: e => e.ReportsTo,         @as: e => e.ReportingHierarchy); 
您可以使用 AggregateGraphLookupOptions 对象来指定要递归的深度和深度字段的名称。以下代码示例执行与上一示例相同的 $graphLookup 操作,但指定最大递归深度为 1:
var employeeCollection = client.GetDatabase("aggregation_examples").GetCollection<Employee>("employees"); var pipeline = new EmptyPipelineDefinition<Employee>()     .GraphLookup<Employee, Employee, Employee, Employee, string, Employee, List<Employee>, Employee>(         from: employeeCollection,         connectFromField: e => e.ReportsTo,         connectToField: e => e.Name,         startWith: e => e.ReportsTo,         @as: e => e.ReportingHierarchy,         new AggregateGraphLookupOptions<Employee, Employee, Employee>         {             MaxDepth = 1         }); 
您还可以使用 AggregateGraphLookupOptions对象指定文档必须与筛选器匹配的过滤, MongoDB才能将其包含在搜索中。以下代码示例执行与前面示例相同的 $graphLookup 操作,但仅包括 Hobbies字段包含 "golf" 的 Employee 文档:
var employeeCollection = client.GetDatabase("aggregation_examples").GetCollection<Employee>("employees"); var pipeline = new EmptyPipelineDefinition<Employee>()     .GraphLookup<Employee, Employee, Employee, Employee, string, Employee, List<Employee>, Employee>(         from: employeeCollection,         connectFromField: e => e.ReportsTo,         connectToField: e => e.Name,         startWith: e => e.ReportsTo,         @as: e => e.ReportingHierarchy,         new AggregateGraphLookupOptions<Employee, Employee, Employee>         {             MaxDepth = 1,             RestrictSearchWithMatch = Builders<Employee>.Filter.AnyEq(e => e.Hobbies, "golf")          }); 
名为 employees 的集合包含以下文档:
db.employees.insertMany([   { _id: 1, name: "Dev" },   { _id: 2, name: "Eliot", reportsTo: "Dev" },   { _id: 3, name: "Ron", reportsTo: "Eliot" },   { _id: 4, name: "Andrew", reportsTo: "Eliot" },   { _id: 5, name: "Asya", reportsTo: "Ron" },   { _id: 6, name: "Dan", reportsTo: "Andrew" } ]); 
要使用MongoDB Node.js驾驶员将 $graphLookup 阶段添加到聚合管道,请在管道对象中使用 $graphLookup操作符。
以下示例创建了一个管道阶段,将 reportsTo 字段递归匹配到 employees 集合中的 name 字段,从而在名为 reportingHierarchy 的新字段中返回每个人的报告层次结构。然后,示例运行聚合管道:
const pipeline = [   {     $graphLookup: {       from: "employees",       connectFromField: "reportsTo",       connectToField: "name",       startWith: "$reportsTo",       as: "reportingHierarchy"     }   } ]; const cursor = collection.aggregate(pipeline); return cursor; 
要指定递归的深度,请使用 maxDepth 字段。以下代码示例执行与上一个示例相同的 $graphLookup 操作,但指定最大递归深度为 1:
const pipeline = [   {     $graphLookup: {       from: "employees",       connectFromField: "reportsTo",       connectToField: "name",       startWith: "$reportsTo",       as: "reportingHierarchy",       maxDepth: 1     }   } ]; const cursor = collection.aggregate(pipeline); return cursor; 
要指定文档必须匹配的过滤器以便操作将其包含在搜索结果中,请使用 restrictSearchWithMatch 字段。以下代码示例执行与上一个示例相同的 $graphLookup 操作,但仅包括 employee 文档,其中 hobbies 字段包含 "golf":
const pipeline = [   {     $graphLookup: {       from: "employees",       connectFromField: "reportsTo",       connectToField: "name",       startWith: "$reportsTo",       as: "reportingHierarchy",       maxDepth: 1,       restrictSearchWithMatch: { hobbies: "golf" }     }   } ]; const cursor = collection.aggregate(pipeline); return cursor; 
了解详情
要了解如何使用 $graphLookup,请参阅网络研讨会:在 MongoDB 中使用图形数据。