Поля справочного документа в коллекции встроенных документов в конвейере агрегации

#mongodb #mongodb-query

#mongodb #mongodb-запрос

Вопрос:

Я пытаюсь объединить два поля массива, расположенные в коллекции встроенных документов, на этапе конвейера. Но я застрял на том, как ссылаться на два «внутренних» массива встроенного документа.

Коллекция

 [{
    name: "first",
    docs: [
        { a1: ["a", "b"], a2: ["c"] },
        { a1: ["d", "e"], a2: ["f"] }
    ]
},
{
    name: "second",
    docs: [
        { a1: [1, 2], a2: [3] },
        { a1: [4, 5], a2: [6] }
    ]
}]
  

Ожидаемый результат

 [{
    name: "first",
    docs: [
        { merged: ["a", "b", "c"] },
        { merged: ["d", "e", "f"] }
    ]
},
{
    name: "second",
    docs: [
        { merged: [1, 2, 3] },
        { merged: [4, 5, 6] }
    ]
}]
  

Подход

Общий подход, который я пробовал до сих пор, таков: (с 2 жестко закодированными массивами для целей тестирования)

 db.getCollection("collection").aggregate([{
    $set: {
         "docs.merged": {
             $concatArrays: [["hello"], ["world"]]
         }
    }
}])
  

Что дает ожидаемые результаты:

 [{
    name : "first",
    docs : [
        {
            a1 : ["a", "b"],
            a2 : ["c"],
            merged : ["hello", "world"] // <- OK
        },
        {
            a1 : ["d", "e"],
            a2 : ["f"],
            merged : ["hello", "world"] // <- OK
        }
    ]
},{
    name : "second",
    docs : [
        {
            a1 : [1.0, 2.0],
            a2 : [3.0],
            merged : ["hello", "world"] // <- OK
        },
        {
            a1 : [4.0, 5.0],
            a2 : [6.0],
            merged : ["hello", "world"] // <- OK
        }
    ]
}]
  

Но у меня возникают трудности с пониманием того, как ссылаться на поля в текущем встроенном документе:

 // Using the "$" reference causes following error:
// Invalid $set :: caused by :: FieldPath field names may not start with '$'.
{
    $set: {
         "docs.merged": { $concatArrays: ["$docs.$.a1", "$docs.$.a2"] }
    }
}

// $$this is only available with a MAP operator
{
    $set: {
         "docs.merged": { $concatArrays: ["$$this.a1", "$$this.a2"] }
    }
}
  

Соображения

Я не могу использовать update запрос, поскольку исходные документы не должны быть изменены. Поэтому это должно быть достигнуто в aggregate конвейере.

Я стараюсь избегать использования unwind операций на этом этапе, так как это окажет значительное влияние на производительность. Фактические документы содержат довольно много (переменных) полей в своем корне; что делает group этап после unwind довольно сложным. (Пример был значительно упрощен для удобства чтения)

Я использую MongoDB v4.4 .

Ответ №1:

Я думаю, что это поможет, пожалуйста, дайте мне знать, если я чего-то не хватает:

 db.collection.aggregate([{
   $project: {
      _id: 0,
      "name": 1,
      "docs": {
         $function: {
            body: function(docs) {
              docs.forEach(function(doc) {
                 var merged = [];
                 Object.keys(doc).forEach(function(k) {
                    merged = merged.concat(doc[k]);
                    delete doc[k];
                 });
                 doc.merged = merged;
              });
              return docs;
            },
            args: [ "$docs" ],
            lang: "js"
         }
      }
   }
}])
  

Комментарии:

1. Я ожидал, что это будет решаемо с помощью встроенной функциональности. Но $function оператор решил это отлично с приемлемым воздействием на производительность. Спасибо!

Ответ №2:

Вы можете сделать что-то вроде следующего.

  1. Сначала $unwind нужно выровнять массив документов.
  2. Поскольку a1 и a2 являются динамическими, мы превращаем это в массив. (Несколько динамических ключей могут быть сконструированы для вашего вывода, если мы используем это).
  3. Затем $reduce добавить данные в массив.
  4. И перегруппируйте его, чтобы получить желаемый результат.

Сценарий агрегации

 [
  {
    "$unwind": "$docs"
  },
  {
    $project: {
      name: 1,
      data: {
        $objectToArray: "$docs"
      }
    }
  },
  {
    $project: {
      name: 1,
      data: {
        $reduce: {
          input: "$data",
          initialValue: [],
          in: {
            $concatArrays: [
              "$$this.v",
              "$$value"
            ]
          }
        }
      }
    }
  },
  {
    $group: {
      _id: "$_id",
      name: {
        $first: "$name"
      },
      docs: {
        $push: {
          merged: "$data"
        }
      }
    }
  }
]
  

Рабочая игровая площадка Mongo

Комментарии:

1. Я стараюсь избегать unwind , поскольку это заставило бы меня перегруппировать документы. Это сложнее, поскольку мои фактические документы содержат множество (переменных) свойств в его корне. Вопрос был больше о том, как ссылаться на поля во встроенном документе.

2. Я тоже пробовал по-другому, но я все равно чувствую, что вам нужно использовать group. Но вы можете сгруппировать его $$ROOT и заменить данными. Но я с нетерпением жду ответа от кого-то, кто не использует unwind и group

3. Я не уверен, но, возможно, $function может помочь в этом — поскольку вы используете MongoDB версии 4.4 .