

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# $graphLookup
<a name="graphLookup"></a>

8.0.2 版中的新内容。

Amazon DocumentDB 中的`$graphLookup`聚合阶段对集合执行递归搜索。对于每个输入文档，从一个或多个值`$graphLookup`开始，重复将连接字段与目标字段进行匹配，沿着匹配文档链进行匹配，直到没有更多匹配项或达到可选的递归深度。这对于遍历类图形或分层数据（例如组织报告结构、类别树或网络关系）很有用。

**参数**
+ `from`：要递归搜索的目标集合。该`from`集合必须在同一个数据库中。
+ `startWith`：该表达式指定递归搜索开始时使用的一个或多个值。将该值与`from`集合中文档`connectToField`的值进行比较。如果`startWith`计算结果为数组，则搜索将同时对所有数组元素运行。
+ `connectFromField`：字段名称，其值用于连接递归搜索中的下一个文档。如果该值是一个数组，则将分别跟踪每个元素进行遍历。
+ `connectToField`：`from`集合中与的值匹配的字段名称`connectFromField`。
+ `as`：添加到每个输出文档的数组字段的名称。此字段包含在递归搜索期间找到的文档。有关这些文档顺序的信息，请参阅以下 “行为” 部分中的结果排序项目。
+ `maxDepth`：一个非负整数，它指定初始查找后要执行的最大递归迭代次数。此参数为可选的。值仅`0`执行初始匹配，不执行递归。如果省略`maxDepth`，搜索将以递归方式继续，直到没有更多匹配的文档。
+ `depthField`：要添加到`as`数组中每个文档的字段的名称，该字段包含找到该文档的递归深度。此参数为可选的。深度以 a 表示`NumberLong`并从零开始，因此初始查找对应的深度为`0`。
+ `restrictSearchWithMatch`：指定应用于搜索文档的附加匹配条件的文档。此参数为可选的。该条件使用与`$match`阶段相同的查询语法，不能包含聚合表达式。

**注意**  
该`$graphLookup`阶段受 100 MB 内存限制且不支持`allowDiskUse:true`。超过此限制的操作会返回错误。如果聚合包括其他阶段，则该`allowDiskUse:true`选项对这些阶段仍然有效。

**行为**
+ **Cross-collection 遍历**：设置为`from`要搜索的集合。这可以是管道运行的相同集合，也可以是同一个数据库中的不同集合。
+ **结果排序**：`as`数组中的文档不按任何保证顺序返回。

## 示例（MongoDB Shell）
<a name="graphLookup-examples"></a>

以下示例用于`$graphLookup`遍历员工报告层次结构，对于每位员工，返回其上方的完整经理链。

以下命令在 mongosh shell 中运行，用于插入建立报告层次结构的示例员工文档：

```
db.employees.insertMany([
  { _id: 1, name: "Dev", reportsTo: "Eliot" },
  { _id: 2, name: "Eliot", reportsTo: "Ron" },
  { _id: 3, name: "Ron", reportsTo: "Andrew" },
  { _id: 4, name: "Andrew", reportsTo: "Asya" },
  { _id: 5, name: "Asya" }
]);
```

以下聚合在新`reportingHierarchy`数组中返回每位员工的完整管理链：

```
db.employees.aggregate([
  {
    $graphLookup: {
      from: "employees",
      startWith: "$reportsTo",
      connectFromField: "reportsTo",
      connectToField: "name",
      as: "reportingHierarchy",
      depthField: "level"
    }
  }
]);
```

此操作返回以下输出：

```
[
  {
    _id: 1,
    name: 'Dev',
    reportsTo: 'Eliot',
    reportingHierarchy: [
      { _id: 2, name: 'Eliot', reportsTo: 'Ron', level: 0 },
      { _id: 3, name: 'Ron', reportsTo: 'Andrew', level: 1 },
      { _id: 4, name: 'Andrew', reportsTo: 'Asya', level: 2 },
      { _id: 5, name: 'Asya', level: 3 }
    ]
  },
  {
    _id: 2,
    name: 'Eliot',
    reportsTo: 'Ron',
    reportingHierarchy: [
      { _id: 3, name: 'Ron', reportsTo: 'Andrew', level: 0 },
      { _id: 4, name: 'Andrew', reportsTo: 'Asya', level: 1 },
      { _id: 5, name: 'Asya', level: 2 }
    ]
  },
  {
    _id: 3,
    name: 'Ron',
    reportsTo: 'Andrew',
    reportingHierarchy: [
      { _id: 4, name: 'Andrew', reportsTo: 'Asya', level: 0 },
      { _id: 5, name: 'Asya', level: 1 }
    ]
  },
  {
    _id: 4,
    name: 'Andrew',
    reportsTo: 'Asya',
    reportingHierarchy: [
      { _id: 5, name: 'Asya', level: 0 }
    ]
  },
  {
    _id: 5,
    name: 'Asya',
    reportingHierarchy: []
  }
]
```

每个员工文档都会获得一个包含递归匹配的经理文档的`reportingHierarchy`数组。该`level`字段记录了找到每个管理器的递归深度。Asya 没有`reportsTo`值，所以她的`reportingHierarchy`数组是空的。

## 使用 maxDepth 和限制的示例SearchWithMatch （MongoDB Shell）
<a name="graphLookup-filter-depth-example"></a>

以下示例遍历人与人之间的联系网络，用于`maxDepth`限制递归并`restrictSearchWithMatch`仅包含符合附加条件的文档。搜索会查找起始人物的人际关系，深度可达两层，只保留`hobbies`数组中包含的人`golf`。

以下命令在 mongosh shell 中运行，用于插入示例文档，其中每个人的`friends`指向通过单向连接链向前移动：

```
db.people.insertMany([
  { _id: 1, name: "Tanya", friends: [ "Carole", "Shirley" ], hobbies: [ "tennis", "golf" ] },
  { _id: 2, name: "Carole", friends: [ "Joseph" ], hobbies: [ "archery", "golf" ] },
  { _id: 3, name: "Joseph", friends: [ "Angelo" ], hobbies: [ "tennis", "golf" ] },
  { _id: 4, name: "Angelo", friends: [ "Miguel" ], hobbies: [ "travel", "golf" ] },
  { _id: 5, name: "Miguel", friends: [], hobbies: [ "golf" ] },
  { _id: 6, name: "Shirley", friends: [], hobbies: [ "frisbee" ] }
]);
```

以下聚合从 Tanya 开始，遍历她的人脉网络，深度可达两层，只保留那些列`golf`为业余爱好的人：

```
db.people.aggregate([
  { $match: { name: "Tanya" } },
  {
    $graphLookup: {
      from: "people",
      startWith: "$friends",
      connectFromField: "friends",
      connectToField: "name",
      as: "golfConnections",
      maxDepth: 2,
      restrictSearchWithMatch: { hobbies: "golf" }
    }
  },
  { $project: { name: 1, "golfConnections.name": 1 } }
]);
```

此操作返回以下输出：

```
[
  {
    _id: 1,
    name: 'Tanya',
    golfConnections: [
      { name: 'Carole' },
      { name: 'Joseph' },
      { name: 'Angelo' }
    ]
  }
]
```

仅包括`hobbies`数组包含`golf`的连接，并且遍历在两级递归后停止。雪莉之所以被排除在外，是因为她没有列出`golf`，而米格尔被排除在外，是因为他离Tanya还有三个等级，除此之外。`maxDepth` `2`由于连接形成单向链，从不指向 Tanya，因此在递归搜索期间起始文档不匹配。

## 代码示例
<a name="graphLookup-code"></a>

要查看使用 MongoDB 驱动程序从第一个示例中构建员工报告层次结构的代码示例，请选择要使用的语言选项卡：

------
#### [ Node.js ]

```
const { MongoClient } = require('mongodb');

async function example() {
  const client = new MongoClient('mongodb://<username>:<password>@<cluster-endpoint>:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false');

  try {
    await client.connect();
    const db = client.db('test');
    const employees = db.collection('employees');

    const result = await employees.aggregate([
      {
        $graphLookup: {
          from: 'employees',
          startWith: '$reportsTo',
          connectFromField: 'reportsTo',
          connectToField: 'name',
          as: 'reportingHierarchy',
          depthField: 'level'
        }
      }
    ]).toArray();

    console.log(JSON.stringify(result, null, 2));
  } catch (err) {
    console.error('Failed to run $graphLookup aggregation:', err.message);
  } finally {
    await client.close();
  }
}

example();
```

------
#### [ Python ]

```
from pymongo import MongoClient
from pymongo.errors import PyMongoError

def example():
    client = MongoClient('mongodb://<username>:<password>@<cluster-endpoint>:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false')

    try:
        db = client['test']
        employees = db['employees']

        pipeline = [
            {
                "$graphLookup": {
                    "from": "employees",
                    "startWith": "$reportsTo",
                    "connectFromField": "reportsTo",
                    "connectToField": "name",
                    "as": "reportingHierarchy",
                    "depthField": "level"
                }
            }
        ]

        result = list(employees.aggregate(pipeline))

        for doc in result:
            print(doc)
    except PyMongoError as error:
        print(f"Failed to run $graphLookup aggregation: {error}")
    finally:
        client.close()

example()
```

------