MongoDB 聚合操作:实战演示 $graphLookup 查询方法
单个集合
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" }
下面的$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" : 1, "name" : "Dev" },
{ "_id" : 2, "name" : "Eliot", "reportsTo" : "Dev" }
]
}
{
"_id" : 4,
"name" : "Andrew",
"reportsTo" : "Eliot",
"reportingHierarchy" : [
{ "_id" : 1, "name" : "Dev" },
{ "_id" : 2, "name" : "Eliot", "reportsTo" : "Dev" }
]
}
{
"_id" : 5,
"name" : "Asya",
"reportsTo" : "Ron",
"reportingHierarchy" : [
{ "_id" : 1, "name" : "Dev" },
{ "_id" : 2, "name" : "Eliot", "reportsTo" : "Dev" },
{ "_id" : 3, "name" : "Ron", "reportsTo" : "Eliot" }
]
}
{
"_id" : 6,
"name" : "Dan",
"reportsTo" : "Andrew",
"reportingHierarchy" : [
{ "_id" : 1, "name" : "Dev" },
{ "_id" : 2, "name" : "Eliot", "reportsTo" : "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" : 3,
"airport" : "PWM",
"connects" : [ "BOS", "LHR" ],
"numConnections" : NumberLong(2) },
{ "_id" : 2,
"airport" : "ORD",
"connects" : [ "JFK" ],
"numConnections" : NumberLong(1) },
{ "_id" : 1,
"airport" : "BOS",
"connects" : [ "JFK", "PWM" ],
"numConnections" : NumberLong(1) },
{ "_id" : 0,
"airport" : "JFK",
"connects" : [ "BOS", "ORD" ],
"numConnections" : NumberLong(0) }
]
}
{
"_id" : 2,
"name" : "Eliot",
"nearestAirport" : "JFK",
"destinations" : [
{ "_id" : 3,
"airport" : "PWM",
"connects" : [ "BOS", "LHR" ],
"numConnections" : NumberLong(2) },
{ "_id" : 2,
"airport" : "ORD",
"connects" : [ "JFK" ],
"numConnections" : NumberLong(1) },
{ "_id" : 1,
"airport" : "BOS",
"connects" : [ "JFK", "PWM" ],
"numConnections" : NumberLong(1) },
{ "_id" : 0,
"airport" : "JFK",
"connects" : [ "BOS", "ORD" ],
"numConnections" : NumberLong(0) } ]
}
{
"_id" : 3,
"name" : "Jeff",
"nearestAirport" : "BOS",
"destinations" : [
{ "_id" : 2,
"airport" : "ORD",
"connects" : [ "JFK" ],
"numConnections" : NumberLong(2) },
{ "_id" : 3,
"airport" : "PWM",
"connects" : [ "BOS", "LHR" ],
"numConnections" : NumberLong(1) },
{ "_id" : 4,
"airport" : "LHR",
"connects" : [ "PWM" ],
"numConnections" : NumberLong(2) },
{ "_id" : 0,
"airport" : "JFK",
"connects" : [ "BOS", "ORD" ],
"numConnections" : NumberLong(1) },
{ "_id" : 1,
"airport" : "BOS",
"connects" : [ "JFK", "PWM" ],
"numConnections" : NumberLong(0) }
]
}
下表显示了递归搜索遍历的路径,最大深度为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
包含了下列文档:
{
"_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" ]
}
下面的聚合操作使用了3个阶段:
-
$match
匹配name
字段包含字符串"Tanya Jordan"的文档,返回一个输出文档。 -
$graphLookup
将输出文档的friends
字段与集合中其他文档的name
字段连接起来,以遍历Tanya Jordan
的社交网络。此阶段使用restrictSearchWithMatch
参数只查找爱好数组中包含golf
的文档。返回一个输出文档。 -
$project 塑造输出文档。列出的
connections who play golf
的名字取自输入文档的golfers
数组。
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"
]
}