web-dev-qa-db-ja.com

MongoDBのコレクションの再帰検索

MongoDBにツリー構造のあるドキュメントのリストがあります。ここで、 親参照のあるモデルツリー構造 パターンが使用されています。 'name'プロパティを指定すると、祖先リスト(ルートまで)を返す単一の集約クエリが必要です。

構造:

{
  '_id': '1',
  'name': 'A',
  'parent': '',
},
{
  '_id': '2',
  'name': 'B',
  'parent': 'A',
},
{
  '_id': '3',
  'name': 'C',
  'parent': 'B',
},
{
  '_id': '4',
  'name': 'D',
  'parent': 'C',
}

集計結果:(指定された名前= 'D')

{
  '_id': '4',
  'name': 'D',
  'ancestors': [{name:'C'}, {name:'B'}, {name:'A'}]
}

Note:現在、ドキュメントの構造を変更できません。それは多くの問題を引き起こします。 祖先の配列を含むモデルツリー構造 の使用を提案する多くのソリューションを見ました。でも今は使えません。単一の集計クエリを使用して上記のパターンでそれを達成する方法はありますか?ありがとうございました

13
RaR

MongoDB 3.4以降では、これを集約フレームワークで実行できます。

パイプラインの最初で最も重要なステージは _$graphLookup_ ステージです。 _$graphLookup_を使用すると、「親」フィールドと「名前」フィールドを再帰的に照合できます。その結果、各「名前」の祖先が取得されます。

パイプラインの次のステージは _$match_ ステージで、対象の「名前」を選択するだけです。

最後のステージは _$addFields_ または _$project_ ステージで、- _$map_ 配列演算子。

もちろん _$reverseArray_ 演算子を使用すると、期待どおりの結果を得るために 配列を反転 できます。

_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" }
                    } 
                } 
            }
        }}
    ]
)
_
15
styvane

クライアント側のJavaScriptを使用できる場合は、mongoシェルで再帰を使用してこれを実現できます。

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);
})

これにより、すべての製品を完全に階層化できます。出力例:

{ "_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" } ] }

正しいコレクションを更新しないことが要件である場合は、別のコレクションにデータを挿入し、そこで更新します。 pushAncesstors関数は次のように変わります。

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}))
  }
}
2
ares