Обновление вложенного массива с MongoDB

Я пытаюсь обновить значение во вложенном массиве, но не могу заставить его работать.

Мой объект такой

 {
    "_id": {
        "$oid": "1"
    },
    "array1": [
        {
            "_id": "12",
            "array2": [
                  {
                      "_id": "123",
                      "answeredBy": [],
                  },
                  {
                      "_id": "124",
                      "answeredBy": [],
                  }
             ],
         }
     ]
 }

Мне нужно выдвинуть значение в массиве "connectedBy".

В приведенном ниже примере я попытался вставить строку "success" в массив "replyBy" объекта "123 _id", но он не работает.

callback = function(err,value){
     if(err){
         res.send(err);
     }else{
         res.send(value);
     }
};
conditions = {
    "_id": 1,
    "array1._id": 12,
    "array2._id": 123
  };
updates = {
   $push: {
     "array2.$.answeredBy": "success"
   }
};
options = {
  upsert: true
};
Model.update(conditions, updates, options, callback);

Я нашел эту ссылку, но в ее ответе только сказано, что я должен использовать объектную структуру вместо массива. Это не может быть применено в моей ситуации. Мне действительно нужно, чтобы мой объект был вложен в массивы

Было бы здорово, если бы вы могли помочь мне здесь. Я тратил часы, чтобы понять это.

Заранее спасибо!

2 ответа

Решение

Общий охват и объяснение

Есть несколько вещей не так с тем, что вы делаете здесь. Во-первых, ваши условия запроса. Вы имеете в виду несколько _id значения, которые вам не нужны, и хотя бы одно из которых находится не на верхнем уровне.

Чтобы получить "вложенное" значение, а также предположить, что _id значение уникально и не будет отображаться ни в каком другом документе, форма запроса должна быть такой:

Model.update(
    { "array1.array2._id": "123" },
    { "$push": { "array1.0.array2.$.answeredBy": "success" } },
    function(err,numAffected) {
       // something with the result in here
    }
);

Теперь это действительно сработает, но на самом деле это просто случайность, так как есть очень веские причины, по которым он не должен работать для вас.

Важное чтение в официальной документации для позиционного $ оператор по теме "Вложенные массивы". Что это говорит:

Позиционный оператор $ нельзя использовать для запросов, которые пересекают более одного массива, таких как запросы, которые пересекают массивы, вложенные в другие массивы, поскольку замена для $ placeholder является одним значением

В частности, это означает, что элемент, который будет сопоставлен и возвращен в позиционном заполнителе, является значением индекса из первого соответствующего массива. В вашем случае это означает совпадение индекса в массиве верхнего уровня.

Таким образом, если вы посмотрите на обозначение запроса, как показано, мы "жестко закодировали" первую (или 0 индексную) позицию в массиве верхнего уровня, и так получилось, что соответствующий элемент в "array2" также является нулевой позицией индекса.

Чтобы продемонстрировать это, вы можете изменить соответствие _id значение до "124", и результат будет $push новая запись на элемент с _id "123", так как они оба находятся в нулевой записи индекса "array1", и это значение, возвращаемое заполнителю.

Так что это общая проблема с вложенными массивами. Вы можете удалить один из уровней, и вы все равно сможете $push к правильному элементу в вашем "верхнем" массиве, но все равно будет несколько уровней.

Старайтесь избегать вложенных массивов, так как вы столкнетесь с проблемами обновления, как показано на рисунке.

Общий случай состоит в том, чтобы "сгладить" то, что вы "думаете", являются "уровнями" и фактически сделать тезисы "атрибутами" на конечных деталях. Например, "уплощенная" форма структуры в вопросе должна выглядеть примерно так:

 {
   "answers": [
     { "by": "success", "type2": "123", "type1": "12" }
   ]
 }

Или даже при принятии внутреннего массива $push только и никогда не обновляется:

 {
   "array": [
     { "type1": "12", "type2": "123", "answeredBy": ["success"] },
     { "type1": "12", "type2": "124", "answeredBy": [] }
   ]
 }

Которые оба поддаются атомным обновлениям в рамках позиционного $ оператор


MongoDB 3.6 и выше

В MongoDB 3.6 появились новые функции для работы с вложенными массивами. Это использует позиционный отфильтрованный $[<identifier>] синтаксис, чтобы соответствовать конкретным элементам и применять различные условия через arrayFilters в заявлении об обновлении:

Model.update(
  {
    "_id": 1,
    "array1": {
      "$elemMatch": {
        "_id": "12","array2._id": "123"
      }
    }
  },
  {
    "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
  },
  {
    "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] 
  }
)

"arrayFilters" как переходили к опциям для .update() или даже .updateOne(), .updateMany(), .findOneAndUpdate() или же .bulkWrite() Метод определяет условия для сопоставления по идентификатору, указанному в операторе обновления. Все элементы, соответствующие указанному условию, будут обновлены.

Поскольку структура является "вложенной", мы фактически используем "несколько фильтров", как указано в "массиве" определений фильтров, как показано. Помеченный "идентификатор" используется при сопоставлении с позиционным фильтром $[<identifier>] синтаксис, фактически используемый в блоке обновления оператора. В этом случае inner а также outer являются идентификаторами, используемыми для каждого условия, как указано во вложенной цепочке.

Это новое расширение делает возможным обновление содержимого вложенного массива, но на самом деле оно не помогает с практичностью "запрашивать" такие данные, поэтому применяются те же предостережения, как объяснено ранее.

Как правило, вы действительно "хотите" выразить себя как "атрибуты", даже если ваш мозг изначально считает "вложенным", это просто реакция на то, как вы полагаете, что "предыдущие реляционные части" объединяются. На самом деле вам действительно нужно больше денормализации.

Также смотрите Как обновить несколько элементов массива в mongodb, так как эти новые операторы обновления фактически соответствуют и обновляют "несколько элементов массива", а не только первый, который был предыдущим действием позиционных обновлений.

ПРИМЕЧАНИЕ Несколько иронично, так как это указано в аргументе "options" для .update() и, как и методы, синтаксис обычно совместим со всеми последними версиями драйверов.

Однако это не относится к mongo оболочки, так как способ реализации метода там ( "по иронии судьбы для обратной совместимости") arrayFilters Аргумент не распознается и удаляется внутренним методом, который анализирует параметры для обеспечения "обратной совместимости" с предыдущими версиями сервера MongoDB и "устаревшими" .update() Синтаксис вызова API.

Так что если вы хотите использовать команду в mongo shell или другие "основанные на оболочке" продукты (особенно Robo 3T), вам нужна последняя версия из ветки разработки или производственной версии начиная с версии 3.6 или выше.

Смотрите также positional all $[] который также обновляет "множественные элементы массива", но без применения к указанным условиям и применяется ко всем элементам в массиве, где это желаемое действие.

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

Способ решить эту проблему - использовать Sub-Documents, Это делается путем вложения схем в ваши схемы

MainSchema = new mongoose.Schema({
   array1: [Array1Schema]
})

Array1Schema = new mongoose.Schema({
   array2: [Array2Schema]
})

Array2Schema = new mongoose.Schema({
   answeredBy": [...]
})

Таким образом, объект будет выглядеть так, как показано на рисунке, но теперь каждый массив заполнен поддокументами. Это позволяет вам проникнуть в нужный документ. Вместо использования .update Затем вы используете .find или же .findOne чтобы получить документ, который вы хотите обновить.

Main.findOne((
    {
        _id: 1
    }
)
.exec(
    function(err, result){
        result.array1.id(12).array2.id(123).answeredBy.push('success')
        result.save(function(err){
            console.log(result)
        });
    }
)

Не использовал .push() функционировать таким образом сам, поэтому синтаксис может быть неправильным, но я использовал оба .set() а также .remove() и то, и другое работает отлично.

Другие вопросы по тегам