Эффективный способ изменить объект из массива объектов с помощью индекса и вставить новые объекты в тот же массив в mongodb?

#mongodb #aggregation-framework

Вопрос:

У меня есть массив, доступный в БД с ключевыми героями.

 Heros: [
{
  name: 'Ironman',
  country: 'USA'
},
{
  name: 'Shaktiman',
  country: 'India'
},
{
  name: 'Black Panther',
  country: 'Wakanda'
}
]
 

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

 additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
 

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

 At index 2 -> update name: 'Krishh', country: 'India' -> put rest of the heros
 

Обновленное значение базы данных для героев составляет:

 Heros: [
{
  name: 'Ironman',
  country: 'USA'
},
{
  name: 'Shaktiman',
  country: 'India'
},
{
  name: 'Krishh',
  country: 'India'
}, 
{ name: 'Wonder woman',
  country: 'USA' 
}, 
{ name: 'Kenshiro',
 country: 'Japan' 
}
]
 

Я знаю о части, которая должна указывать значение, но я не могу найти никакого эффективного способа обновить и вставить сразу

  $push: { heros: { $each: additionalHeros } }
 

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

1. 1) регулярный запрос на обновление не позволит выполнять 2 операции в одном поле — это создаст конфликт 2) вы можете использовать обновление с конвейером агрегации — но это не эффективный способ выполнить эту операцию.

2. @turivishal Тогда что было бы лучше, любое другое хорошее решение

Ответ №1:

  1. Обычный запрос на обновление не позволит выполнить 2 операции в одном поле, это приведет к ошибке конфликта
  2. Вы можете использовать обновление с конвейером агрегации, начиная с MongoDB 4.2, но я думаю, что этот подход неэффективен, и вам нужен эффективный подход,
    • $map для повторения цикла heros массива
    • $indexOfArray чтобы получить индекс текущего объекта в heros массиве
    • $cond чтобы проверить, совпадает ли приведенное выше index значение с нашим входным индексом, затем обновите последнее значение, в противном случае верните тот же объект
    • второй $set этап для добавления нового heros $concatArrays оператора using
 additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
updateIndex = {
  name: "Krishh",
  country: "India"
}
index = 1
db.collection.updateOne(
  {}, // your query
  [{
    $set: {
      heros: {
        $map: {
          input: "$heros",
          in: {
            $cond: [
              {
                $eq: [
                  {
                    $indexOfArray: [
                      "$heros",
                      {
                        name: "$this.name",
                        country: "$this.country"
                      }
                    ]
                  },
                  index
                ]
              },
              updateIndex,
              "$this"
            ]
          }
        }
      }
    }
  },
  {
    $set: {
      heros: {
        $concatArrays: ["$heros", additionalHeros]
      }
    }
  }]
)
 

Игровая площадка

  1. Вы можете выполнить два отдельных запроса на обновление или запрос на массовую запись
  • обновите конкретное свойство индекса
 db.collection.updateOne(
  {}, // your query
  {
    $set: {
      "heros.1.name": "Krishh",
      "heros.1.country": "India"
    }
  }
)
 

Игровая площадка

  • вставьте массив в heros
 db.collection.updateOne(
  {}, // your query
  {
    $push: {
      heros: {
        $each: [
          {
            name: "Wonder woman",
            country: "USA"
          },
          {
            name: "Kenshiro",
            country: "Japan"
          }
        ]
      }
    }
  }
)
 

Игровая площадка

Ответ №2:

это сработает, пусть документ будет

 { 
    "_id" : "heroes", 
    "Heros" : [
        {
            "name" : "Ironman", 
            "country" : "USA"
        }, 
        {
            "name" : "Shaktiman", 
            "country" : "India"
        }, 
        {
            "name" : "Black Panther", 
            "country" : "Wakanda"
        }
    ]
}
 

Код был бы

  let additionalHeros = [{ name: 'Wonder woman', country: 'USA' }, { name: 'Kenshiro', country: 'Japan' }]
    let index = 2;
    let data = { name: 'Krishh', country: 'India' }
    let updateQuery = {};
    updateQuery[`Heros.${index}`] = data;
    let ops = [];
    ops.push(
        {
            updateOne: {
                filter: { _id: "heroes" },
                update: {
                    $set: updateQuery,
                },
                upsert: true
            }
        },
    );
    ops.push(
        {
            updateOne: {
                filter: { _id: "heroes" },
                update: {
                    $push: { Heros: { $each: additionalHeros } }
                },
                upsert: true
            }
        },
    );
    let x = await db1.bulkWrite(ops, { ordered: true, upsert: true })
 

Тогда выход был бы

 { 
    "_id" : "heroes", 
    "Heros" : [
        {
            "name" : "Ironman", 
            "country" : "USA"
        }, 
        {
            "name" : "Shaktiman", 
            "country" : "India"
        }, 
        {
            "name" : "Krishh", 
            "country" : "India"
        }, 
        {
            "name" : "Wonder woman", 
            "country" : "USA"
        }, 
        {
            "name" : "Kenshiro", 
            "country" : "Japan"
        }
    ]
}