#arrays #mongodb #aggregation-framework #tree-structure
#массивы #mongodb #агрегация-фреймворк #древовидная структура
Вопрос:
У меня есть такая база данных mongo:
db.templates.insertMany( [
{ _id: 1, uuid: "1", name: "t1", related_templates: [ "2", "2" ] },
{ _id: 2, uuid: "2", name: "t2", related_templates: [ "3", "3" ] },
{ _id: 3, uuid: "3", name: "t3", related_templates: [ "4", "4" ] },
{ _id: 4, uuid: "4", name: "t4"},
] )
Как вы можете видеть, данные представляют древовидную структуру, но поддерживают повторяющиеся ссылки на один и тот же дочерний узел. Я пытаюсь рекурсивно извлечь все дерево, начиная с t1, включая повторяющиеся ссылки.
Результат будет выглядеть следующим образом:
{
"_id" : 1,
"uuid" : "1",
"name": "t1",
"related_templates" : [
{
"_id" : 2,
"uuid" : "2",
"name" : "t2",
"related_templates" : [
{
"_id" : 3,
"uuid" : "3",
"name" : "t3",
"related_templates" : [
{
"_id" : 4,
"uuid" : "4",
"name" : "t4"
},
{
"_id" : 4,
"uuid" : "4",
"name" : "t4"
}
]
},
{
"_id" : 3,
"uuid" : "3",
"name" : "t3",
"related_templates" : [
{
"_id" : 4,
"uuid" : "4",
"name" : "t4"
},
{
"_id" : 4,
"uuid" : "4",
"name" : "t4"
}
]
}
]
},
...(t2 repeats here)
]
}
Решение, предложенное на веб-сайте Mongo, находится здесь: https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#std-label-unwind-example .
Если нет повторяющихся ссылок, это решение отлично работает, с небольшими изменениями, даже позволяющими выполнять рекурсивный поиск. Однако в моей ситуации мне нужно сохранить повторяющиеся запросы
Я также рассмотрел устаревшее решение с использованием unwind group. Это решение сохраняет дубликаты, но я не понял, как использовать его рекурсивно.
Я также рассматривал возможность использования решения на веб-сайте mongo для извлечения без дубликатов, а затем что-то делать с картой, чтобы прикрепить полученные данные к исходному массиву related_templates. Я думаю, это сработало бы, но это не кажется очень элегантным.
Есть ли элегантное / простое решение для этого, которого мне не хватает?
Ответ №1:
На случай, если у кого-нибудь возникнет эта проблема в будущем, я продолжу и опубликую решение, которое я придумал:
db.templates.aggregate([
{
"$match": {'uuid': "1"}
},
{
'$lookup': {
'from': "templates",
'let': { 'uuids': "$related_templates"},
'pipeline': [
{
'$match': {
'$expr': {
'$and': [
{ '$in': [ "$uuid", "$uuids" ] },
]
}
}
}
],
'as': "related_templates_objects"
}
},
{
$addFields: {
"related_templates_objects_uuids": {
$map: {
input: "$related_templates_objects",
in: "$this.uuid"
}
}
}
},
{
$addFields: {
"related_templates": {
$map: {
input: "$related_templates",
in: {"$arrayElemAt":[
"$related_templates_objects",
{"$indexOfArray":["$related_templates_objects_uuids","$this"]}
]}
}
}
}
},
{"$project":{"related_templates_objects":0,"related_templates_objects_uuids":0}}
])
В итоге:
- выполните поиск без дубликатов, называемых related_templates_objects .
- создайте идентичный массив для related_templates_objects, за исключением извлечения только идентификаторов uuid, называемых related_templates_objects_uuids .
- Создайте желаемый массив объектов, сопоставив каждую из исходных ссылок в related_templates с правильным объектом из related_templates_objects (индекс которого находится с помощью related_templates_objects_uuids).
- Спроецируйте исходные два промежуточных поля, которые использовались для создания новых related_templates.
Конечно, это решение не является рекурсивным. Можно выполнить рекурсию один раз, скопировав последние 4 элемента внешнего конвейера во внутренний конвейер. А затем повторите x еще раз, следуя той же формуле копирования и вставки, которую я закодировал в свой проект.
Надеюсь, решение кому-то поможет.