对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。

$graphLookup(聚合阶段)

$graphLookup

在版本5.1中进行了更改。

对集合执行递归搜索,并提供按照递归深度和查询筛选器限制搜索的选项。

$graphLookup 搜索过程总结如下:

  1. 输入文档流入聚合操作的 $graphLookup 阶段。

  2. $graphLookup 将搜索定位到由 from 参数指定的集合(有关搜索参数的完整列表,请参见下文)。

  3. 对于每份输入文档,搜索从 startWith 指定的值开始。

  4. $graphLookup 将 startWith 值与 from 集合中其他文档的 connectToField 指定字段进行匹配。

  5. 对于每份匹配文档,$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 集合必须与此操作中使用的所有其他集合位于同一数据库中。

有关分片支持,请参阅 分片集合。

startWith

connectFromField表达式,用于指定开始递归搜索的 值。如果 startWith 的计算结果为大量,则 $graphLookup 同时从所有大量元素中执行搜索。

connectFromField

字段名,其值 $graphLookup 用于以递归方式匹配集合中其他文档的 connectToField。如果值是数组,则每个元素都会单独完成遍历进程。

connectToField

其他文档中的字段名称,用于与 connectFromField 参数指定的字段值相匹配。

as

添加到每个输出文档的数组字段的名称。包含在 $graphLookup 阶段为访问文档而遍历的文档。

不保证 as 字段中返回的文档按任何顺序排列。

maxDepth

可选。指定最大递归深度的非负整数。

depthField

可选。要添加到搜索路径中每个已遍历文档的字段的名称。该字段的值为文档的递归深度,并用 NumberLong 表示。递归深度值从零开始,因此第一次查找对应于零深度。

restrictSearchWithMatch

可选。指定递归搜索附加条件的文档。语法与查询过滤器语法相同。

您无法在此过滤器中使用任何聚合表达式。例如,您不能使用以下文档来查找 lastName 值与输入文档的 lastName 值不同的文档:

{ lastName: { $ne: "$lastName" } }

您无法在这种情况下使用该文档,因为 "$lastName" 将充当字符串文字,而不是字段路径。

从 MongoDB 5.1 开始,可以在 $graphLookup 阶段的 from 参数中指定分片集合。

当以分片集合为目标时,您无法在事务中使用 $graphLookup 阶段。

将 maxDepth 字段设置为 0 相当于一个非递归的 $graphLookup 搜索阶段。

$graphLookup 阶段必须保持在 100 兆字节的内存限制之内。如果为 aggregate() 操作指定了 allowDiskUse: true,则 $graphLookup 阶段将忽略该选项。如果 aggregate() 操作中还有其他阶段,则 allowDiskUse: true 选项对这些其他阶段有效。

有关更多信息,请参阅聚合管道限制。

$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: 2, name: 'Eliot', reportsTo: 'Dev' }, { _id: 1, name: 'Dev' }, { _id: 4, name: 'Andrew', reportsTo: 'Eliot' } ] }]

下表提供文档 { "_id" : 5, "name" : "Asya", "reportsTo" : "Ron" } 的遍历路径:

起始值

文档的 reportsTo 值:

{ ... reportsTo: "Ron" }

深度 0

{ _id: 3, name: "Ron", reportsTo: "Eliot" }

深度 1

{ _id: 2, name: "Eliot", reportsTo: "Dev" }

深度 2

{ _id: 1, name: "Dev" }

输出生成层次结构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: 0, airport: 'JFK', connects: [ 'BOS', 'ORD' ], numConnections: Long('0') }, { _id: 1, airport: 'BOS', connects: [ 'JFK', 'PWM' ], numConnections: Long('1') }, { _id: 3, airport: 'PWM', connects: [ 'BOS', 'LHR' ], numConnections: Long('2') }, { _id: 2, airport: 'ORD', connects: [ 'JFK' ], numConnections: Long('1') } ] }, { _id: 2, name: 'Eliot', nearestAirport: 'JFK', destinations: [ { _id: 0, airport: 'JFK', connects: [ 'BOS', 'ORD' ], numConnections: Long('0') }, { _id: 1, airport: 'BOS', connects: [ 'JFK', 'PWM' ], numConnections: Long('1') }, { _id: 3, airport: 'PWM', connects: [ 'BOS', 'LHR' ], numConnections: Long('2') }, { _id: 2, airport: 'ORD', connects: [ 'JFK' ], numConnections: Long('1') } ] }, { _id: 3, name: 'Jeff', nearestAirport: 'BOS', destinations: [ { _id: 0, airport: 'JFK', connects: [ 'BOS', 'ORD' ], numConnections: Long('1') }, { _id: 1, airport: 'BOS', connects: [ 'JFK', 'PWM' ], numConnections: Long('0') }, { _id: 4, airport: 'LHR', connects: [ 'PWM' ], numConnections: Long('2') }, { _id: 3, airport: 'PWM', connects: [ 'BOS', 'LHR' ], numConnections: Long('1') }, { _id: 2, airport: 'ORD', connects: [ 'JFK' ], numConnections: Long('2') } ] }]

下表提供了递归搜索的遍历路径,深度为 2,其中起始 airport 为 JFK:

起始值

travelers 集合中的 nearestAirport 值:

{ ... nearestAirport: "JFK" }

深度 0

{ _id: 0, airport: "JFK", connects: [ "BOS", "ORD" ] }

深度 1

{ _id: 1, airport: "BOS", connects: [ "JFK", "PWM" ] }{ _id: 2, airport: "ORD", connects: [ "JFK" ] }

深度 2

{ _id: 3, airport: "PWM", connects: [ "BOS", "LHR" ] }

以下示例使用一个包含一组文档的集合,其中包含人员姓名及其朋友和爱好的数组。聚合操作找到一个特定的人,并遍历她的人际网络,以找到在其爱好中列出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字段包含 string "Tanya Jordan" 的 document。返回一个输出 document。

  • $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': [ 'Tanya Jordan', 'Joseph Dennis', 'Angelo Ward', 'Carole Hale' ] }]

要学习;了解有关如何使用$graphLookup的更多信息,请参阅在MongoDB中使用图表数据。

给本页内容打分