问题
I have a list of documents in MongoDB with tree structure, where Model Tree Structures with Parent References pattern used. I want a single aggregation query which returns ancestor list(till the root), given the 'name' property.
Structure:
{
'_id': '1',
'name': 'A',
'parent': '',
},
{
'_id': '2',
'name': 'B',
'parent': 'A',
},
{
'_id': '3',
'name': 'C',
'parent': 'B',
},
{
'_id': '4',
'name': 'D',
'parent': 'C',
}
Aggregation result:(Given, name = 'D')
{
'_id': '4',
'name': 'D',
'ancestors': [{name:'C'}, {name:'B'}, {name:'A'}]
}
Note:
I can't change the document structure now. It will cause many problems. I saw many solutions which suggest to use Model Tree Structures with an Array of Ancestors. But I cannot use it now. Is there any way to achieve it with the above pattern using single aggregation query? Thank you
回答1:
Starting from MongoDB 3.4, we can do this with the Aggregation Framework.
The first and most important stage in our pipeline is the $graphLookup stage. $graphLookup
allows us to recursively match on the "parent" and "name" field. As result, we get the ancestors of each "name".
The next stage in the pipeline is the $match stage where we simply select the "name" we are interested in.
The final stage is the $addFields or $project stage where we apply an expression to the "ancestors" array using the $map array operator.
Of course with the $reverseArray operator we reverse our array in order to get the expected result.
db.collection.aggregate(
[
{ "$graphLookup": {
"from": "collection",
"startWith": "$parent",
"connectFromField": "parent",
"connectToField": "name",
"as": "ancestors"
}},
{ "$match": { "name": "D" } },
{ "$addFields": {
"ancestors": {
"$reverseArray": {
"$map": {
"input": "$ancestors",
"as": "t",
"in": { "name": "$$t.name" }
}
}
}
}}
]
)
回答2:
If you are open to use client side javascript, you can use recursion on the mongo shell to achieve this:
var pushAncesstors = function (name, doc) {
if(doc.parent) {
db.collection.update({name : name}, {$addToSet : {"ancesstors" : {name : doc.parent}}});
pushAncesstors(name, db.collection.findOne({name : doc.parent}))
}
}
db.collection.find().forEach(function (doc){
pushAncesstors(doc.name, doc);
})
This will give you complete hirearchy for all products. Sample output:
{ "_id" : "1", "name" : "A", "parent" : "" }
{ "_id" : "2", "name" : "B", "parent" : "A", "ancesstors" : [ { "name" : "A" } ] }
{ "_id" : "3", "name" : "C", "parent" : "B", "ancesstors" : [ { "name" : "B" }, { "name" : "A" } ] }
{ "_id" : "4", "name" : "D", "parent" : "C", "ancesstors" : [ { "name" : "C" }, { "name" : "B" }, { "name" : "A" } ] }
If your requirement is not to update the correct collection, insert the data in a diffferent collection and update there. The pushAncesstors
function will change to:
var pushAncesstors = function (name, doc) {
if(doc.parent) {
db.outputColl.save(doc)
db.outputColl.update({name : name}, {$addToSet : {"ancesstors" : {name : doc.parent}}});
pushAncesstors(name, db.collection.findOne({name : doc.parent}))
}
}
来源:https://stackoverflow.com/questions/40526148/recursive-search-on-a-collection-in-mongodb