Как выполнять рекурсивный поиск при сохранении дубликатов в MongoDB

#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}}
])
 

В итоге:

  1. выполните поиск без дубликатов, называемых related_templates_objects .
  2. создайте идентичный массив для related_templates_objects, за исключением извлечения только идентификаторов uuid, называемых related_templates_objects_uuids .
  3. Создайте желаемый массив объектов, сопоставив каждую из исходных ссылок в related_templates с правильным объектом из related_templates_objects (индекс которого находится с помощью related_templates_objects_uuids).
  4. Спроецируйте исходные два промежуточных поля, которые использовались для создания новых related_templates.

Конечно, это решение не является рекурсивным. Можно выполнить рекурсию один раз, скопировав последние 4 элемента внешнего конвейера во внутренний конвейер. А затем повторите x еще раз, следуя той же формуле копирования и вставки, которую я закодировал в свой проект.

Надеюсь, решение кому-то поможет.