2013-08-11 2 views
46

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

{id: 1, 
forecasts: [ { 
      forecast_id: 123, 
      name: "Forecast 1", 
      levels: [ 
       { level: "proven", 
        configs: [ 
          { 
           config: "Custom 1", 
           variables: [{ x: 1, y:2, z:3}] 
          }, 
          { 
           config: "Custom 2", 
           variables: [{ x: 10, y:20, z:30}] 
          }, 
        ] 
       }, 
       { level: "likely", 
        configs: [ 
          { 
           config: "Custom 1", 
           variables: [{ x: 1, y:2, z:3}] 
          }, 
          { 
           config: "Custom 2", 
           variables: [{ x: 10, y:20, z:30}] 
          }, 
        ] 
       } 
      ] 
     }, 
    ] 

} 

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

newdata = { 
    config: "Custom 1", 
    variables: [{ x: 111, y:2222, z:3333}] 
} 

Я пытаюсь что-то подобное в Монго (в Python):

db.myCollection.update({"id": 1, 
         "forecasts.forecast-id": 123, 
         "forecasts.levels.level": "proven", 
         "forecasts.levels.configs.config": "Custom 1" 
         }, 
         {"$set": {"forecasts.$.levels.$.configs.$": newData}} 
        ) 

я получаю «не удается применить оператор позиционирования без соответствующего поля запроса, содержащего ошибку массива. Каков правильный способ сделать это в монго? Это mongo v2.4.1.

+0

Вы пытаетесь заменить данные, находящиеся там, или добавить еще один индекс в массиве с этими новыми данными? – tymeJV

+0

Замените данные, которые уже есть. – reptilicus

+0

Попробуйте вырезать последний позиционный оператор: '$ set": {"прогнозы. $. Levels. $. Configs": newData' – tymeJV

ответ

32

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

db.myCollection.update({ 
    "id": 1, 
    "forecasts.forecast-id": 123, 
    "forecasts.levels.level": "proven", 
    "forecasts.levels.configs.config": "Custom 1" 
    }, 
    {"$set": {"forecasts.$.levels.0.configs.0": newData}} 
) 

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

Одним из возможных вариантов: сделать forecasts свою собственную коллекцию, и если у вас есть фиксированный набор level значений, сделать level объект вместо массива:

{ 
    _id: 123, 
    parentId: 1, 
    name: "Forecast 1", 
    levels: { 
    proven: { 
     configs: [ 
     { 
      config: "Custom 1", 
      variables: [{ x: 1, y:2, z:3}] 
     }, 
     { 
      config: "Custom 2", 
      variables: [{ x: 10, y:20, z:30}] 
     }, 
     ] 
    }, 
    likely: { 
     configs: [ 
     { 
      config: "Custom 1", 
      variables: [{ x: 1, y:2, z:3}] 
     }, 
     { 
      config: "Custom 2", 
      variables: [{ x: 10, y:20, z:30}] 
     }, 
     ] 
    } 
    } 
} 

Затем вы можете обновить его с помощью:

db.myCollection.update({ 
    _id: 123, 
    'levels.proven.configs.config': 'Custom 1' 
    }, 
    { $set: { 'levels.proven.configs.$': newData }} 
) 
+0

Привет @JohnnyHK, я разместил [вопрос] (http://stackoverflow.com/questions/34604069/issue-updating-value-inside-the-subdocument-of-a-subdocument), где у меня есть аналогичная проблема , Я пробовал использовать '$ set', как вы показали, но без успеха. Любая помощь будет высоко ценится! – charliebrownie

+0

Этот конкретный подход к повороту данных страдает от дополнительных проблем, таких как фрагментация индекса. – amcgregor

+1

@JohnnyHK, думаю, что в первой части вашего ответа есть ошибка. В '{"$ set": {"прогнозы.0.levels.0.configs. $": NewData}}' оператор $ помещается неправильно, так как оператор возвращает индекс объекта в первом вложенном списке , это означает, что он всегда будет 0 в текущем запросе. Вы можете проверить это, изменив свой запрос на использование '"forecasts.levels.configs.config": «Пользовательский 2», и вы увидите, что обновленный документ - это тот, у которого есть «config»: «Custom 1» , Правильно должно быть «{« $ set »: {« прогнозы. $. Levels.0.configs.0 »: newData}} ' – sergiuz

2

Учитывая, что MongoDB не является хорошим механизмом для этого, я считаю разумным использовать мангуст, чтобы просто извлечь элемент из коллекции монго, используя .findOne(...), запустить для поиска по петле в соответствующих подэлементах (поиск, например, ObjectID), изменить этот JSON, затем сделать Schema.markModified('your.subdocument'); Schema.save(); Это, вероятно, неэффективно, но это очень просто и отлично работает.

+10

Да, это работает, но не является атомной операцией. – reptilicus

16

Успели решить с помощью мангуста:

Все, что вам нужно знать, это «_ID х годах все поддокументу в цепочке (мангуст автоматически создавать„_id“для каждого суб-документа).

, например -

SchemaName.findById(_id, function (e, data) { 
     if (e) console.log(e); 
     data.sub1.id(_id1).sub2.id(_id2).field = req.body.something; 

     // or if you want to change more then one field - 
     //=> var t = data.sub1.id(_id1).sub2.id(_id2); 
     //=> t.field = req.body.something; 

     data.save(); 
    }); 

Подробнее о суб-документе метод _id in mongoose documentation.

описание: _id для SchemaName, _id1 для sub1 и _id2 для sub2 - вы можете сохранить цепочку таким образом.

* Вам не нужно использовать метод findById, но это кажется мне наиболее удобным, так как вам все равно нужно знать остальную часть «_id».

+1

Точечная нотация работает как очарование. ОГРОМНАЯ помощь.Ищете это простое решение за последние 2 дня! Спасибо! –

+1

отличная помощь благодаря @Noam El –

+0

Это огромная горшка с производительностью, так как обновление занимает много времени –

4

Это очень старая ошибка в MongoDB

https://jira.mongodb.org/browse/SERVER-831

+1

кажется, что mongodb еще не поддерживает эту функцию. –

+0

Исправлено. https://jira.mongodb.org/browse/SERVER-831 Но эта функция доступна, начиная с версии разработки MongoDB 3.5.12. – 6339

0

Okkk.we может обновить наш вложенная поддокумент mongodb.this наша схема.

var Post = new mongoose.Schema({ 
    name:String, 
    post:[{ 
     like:String, 
     comment:[{ 
      date:String, 
      username:String, 
      detail:{ 
       time:String, 
       day:String 
      } 
     }] 
    }] 
}) 

решение для этой схемы

Test.update({"post._id":"58206a6aa7b5b99e32b7eb58"}, 
    {$set:{"post.$.comment.0.detail.time":"aajtk"}}, 
      function(err,data){ 
//data is updated 
}) 
1

Это исправлено. https://jira.mongodb.org/browse/SERVER-831

Но эта функция доступна, начиная с версии разработки MongoDB 3.5.12.

Примечание: Этот вопрос задают на Aug 11 2013 и он решился на Aug 11 2017

0

Sharing мои уроки. Недавно я столкнулся с тем же требованием, когда мне нужно обновить вложенный элемент массива. Моя структура выглядит следующим образом

{ 
    "main": { 
     "id": "ID_001", 
     "name": "Fred flinstone Inc" 
    }, 
    "types": [ 
     { 
     "typeId": "TYPE1", 
     "locations": [ 
      { 
      "name": "Sydney", 
      "units": [ 
       { 
       "unitId": "PHG_BTG1" 
       } 
      ] 
      }, 
      { 
      "name": "Brisbane", 
      "units": [ 
       { 
       "unitId": "PHG_KTN1" 
       }, 
       { 
       "unitId": "PHG_KTN2" 
       } 
      ] 
      } 
     ] 
     } 
    ] 
    } 

Мое требование, чтобы добавить некоторые поля в конкретных единицах []. Мое решение первым найти индекс вложенного элемента массива (скажем, foundUnitIdx) две методики, которые я использовал в

  1. использовать $ набор ключевых слов
  2. указать динамическое поле в $ наборе с помощью [ ] синтаксис

      query = { 
           "locations.units.unitId": "PHG_KTN2" 
          }; 
          var updateItem = { 
           $set: { 
            ["locations.$.units."+ foundUnitIdx]: unitItem 
           } 
          }; 
          var result = collection.update(
           query, 
           updateItem, 
           { 
            upsert: true 
           } 
          ); 
    

Надеется, что это помогает другим. :)

+0

Это работает на версии 3.2? –

+0

Я думаю, вам нужно как минимум v3.4 (хотя doco сказал, что это ошибка, которая была исправлена ​​в версии 3.5) fyi Я успешно использовал v3.4 – erickyi2006

+0

Спасибо. Вы знаете, как мы можем реализовать в версиях в 3.2 и более поздних версиях? –

0

простое решение для MongoDB 3.2+ https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/

У меня была похожая ситуация, и решить ее, как это. Я использовал мангуст, но он все равно должен работать в vanilla MongoDB. Надеюсь, это кому-то полезно.

const MyModel = require('./model.js') 
const query = {id: 1} 

// First get the doc 
MyModel.findOne(query, (error, doc) => { 

    // Do some mutations 
    doc.foo.bar.etc = 'some new value' 

    // Pass in the mutated doc and replace 
    MyModel.replaceOne(query, doc, (error, newDoc) => { 
     console.log('It worked!') 
    }) 
} 

В зависимости от вашего случая использования, вы можете быть в состоянии пропустить начальный findOne()

1

MongoDB ввела ArrayFilters для решения этой проблемы в версии 3.5.2 и более поздние версии.

Новое в версии 3.6.

Начиная с MongoDB 3.6 при обновлении поля массива вы можете указать arrayFilters, которые определяют, какие элементы массива обновляются.

[https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-an-array-update-operations][1]

Скажем, проект схемы следующим образом:

var ProfileSchema = new Schema({ 
    name: String, 
    albums: [{ 
     tour_name: String, 
     images: [{ 
      title: String, 
      image: String 
     }] 
    }] 
}); 

И Документ создан выглядит следующим образом:

{ 
    "_id": "1", 
    "albums": [{ 
      "images": [ 
       { 
        "title": "t1", 
        "url": "url1" 
       }, 
       { 
        "title": "t2", 
        "url": "url2" 
       } 
      ], 
      "tour_name": "london-trip" 
     }, 
     { 
      "images": [.........]: 
     }] 
} 

сказать, что я хочу, чтобы обновить "URL" изображения. Учитывая - "document id", "tour_name" and "title"

Для этого обновления запроса:

Profiles.update({_id : req.body.id}, 
    { 
     $set: { 

      'albums.$[i].images.$[j].title': req.body.new_name 
     } 
    }, 
    { 
     arrayFilters: [ 
      { 
       "i.tour_name": req.body.tour_name, "j.image": req.body.new_name // tour_name - current tour name, new_name - new tour name 
      }] 
    }) 
    .then(function (resp) { 
     console.log(resp) 
     res.json({status: 'success', resp}); 
    }).catch(function (err) { 
    console.log(err); 
    res.status(500).json('Failed'); 
}) 
Смежные вопросы