View a markdown version of this page

$graphLookup - Amazon DocumentDB

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

$graphLookup

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)

以下示例用于$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)

以下示例遍历人与人之间的联系网络,用于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,因此在递归搜索期间起始文档不匹配。

代码示例

要查看使用 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()