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}}
)
Я получаю сообщение об ошибке "Не удается применить позиционный оператор без соответствующего поля запроса, содержащего массив". Как правильно сделать это в монго? Это Монго v2.4.1.
9 ответов
К сожалению, вы не можете использовать $
Оператор более одного раза для каждого ключа, поэтому вы должны использовать числовые значения для остальных. Как в:
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 }}
)
Решил ее с помощью мангуста:
Все, что вам нужно знать, это _id всех вложенных документов в цепочке (mongoose автоматически создает _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();
});
Подробнее о методе sub-document _id в документации mongoose.
объяснение:_id для SchemaName, _id1 для sub1 и _id2 для sub2 - вы можете продолжать цепочку таким образом.
* Вам не нужно использовать метод findById, но он кажется мне наиболее удобным, так как вам все равно нужно знать остальную часть _id.
MongoDB представил ArrayFilters для решения этой проблемы в версии 3.5.2 и более поздних.
Новое в версии 3.6.
Начиная с MongoDB 3.6, при обновлении поля массива вы можете указать arrayFilters, который определяет, какие элементы массива обновлять.
[ https://docs.mongodb.com/manual/reference/method/db.collection.update/
Допустим, схема разработана следующим образом:
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');
})
Это очень старая ошибка в MongoDB
Я столкнулся с такой же проблемой сегодня, и после долгих поисков в google/stackru/github я решил, arrayFilters
являются лучшим решением этой проблемы. Который будет работать с Монго 3.6 и выше. Эта ссылка наконец спасла мой день: https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html
const OrganizationInformationSchema = mongoose.Schema({
user: {
_id: String,
name: String
},
organizations: [{
name: {
type: String,
unique: true,
sparse: true
},
rosters: [{
name: {
type: String
},
designation: {
type: String
}
}]
}]
}, {
timestamps: true
});
И используя mongoose в express, обновляя имя реестра с указанным идентификатором.
const mongoose = require('mongoose');
const ControllerModel = require('../models/organizations.model.js');
module.exports = {
// Find one record from database and update.
findOneRosterAndUpdate: (req, res, next) => {
ControllerModel.updateOne({}, {
$set: {
"organizations.$[].rosters.$[i].name": req.body.name
}
}, {
arrayFilters: [
{ "i._id": mongoose.Types.ObjectId(req.params.id) }
]
}).then(response => {
res.send(response);
}).catch(err => {
res.status(500).send({
message: "Failed! record cannot be updated.",
err
});
});
}
}
Это фиксированная. https://jira.mongodb.org/browse/SERVER-831
Но эта функция доступна, начиная с версии разработки MongoDB 3.5.12.
Примечание: этот вопрос задан на Aug 11 2013
и это решено на Aug 11 2017
Учитывая, что MongoDB не обеспечивает хороший механизм для этого, я считаю целесообразным использовать mongoose для простого извлечения элемента из коллекции mongo, используя .findOne(...)
запустите поиск по циклу для соответствующих подэлементов (ища, скажем, с помощью ObjectID), измените этот JSON, затем выполните Schema.markModified('your.subdocument'); Schema.save();
Это, вероятно, не эффективно, но это очень просто и отлично работает.
Я искал это около 5 часов и, наконец, нашел лучшее и простое решение: КАК ОБНОВИТЬ Вложенные вспомогательные документы в базе данных MONGO.
{id: 1,
forecasts: [ {
forecast_id: 123,
name: "Forecast 1",
levels: [
{
levelid:1221
levelname: "proven",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
{
levelid:1221
levelname: "likely",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
]
},
]}
Запрос:
db.weather.updateOne({
"_id": ObjectId("1"), //this is level O select
"forecasts": {
"$elemMatch": {
"forecast_id": ObjectId("123"), //this is level one select
"levels.levelid": ObjectId("1221") // this is level to select
}
}
},
{
"$set": {
"forecasts.$[outer].levels.$[inner].levelname": "New proven",
}
},
{
"arrayFilters": [
{ "outer.forecast_id": ObjectId("123") },
{ "inner.levelid": ObjectId("1221") }
]
}).then((result) => {
resolve(result);
}, (err) => {
reject(err);
});
Делимся своими уроками. Недавно я столкнулся с тем же требованием, когда мне нужно обновить вложенный элемент массива. Моя структура выглядит следующим образом
{
"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). Я использовал два метода:
- используйте ключевое слово $set
укажите динамическое поле в $set, используя синтаксис []
query = { "locations.units.unitId": "PHG_KTN2" }; var updateItem = { $set: { ["locations.$.units."+ foundUnitIdx]: unitItem } }; var result = collection.update( query, updateItem, { upsert: true } );
Надеюсь, что это помогает другим.:)
ПРОСТОЕ РЕШЕНИЕ ДЛЯ Mongodb 3.2+ https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/
У меня была похожая ситуация, и я решил ее следующим образом. Я использовал мангуст, но он все равно должен работать в ванильном 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()
Okkk.we может обновить наш вложенный поддокумент в mongodb. Это наша схема.
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
})