Mongodb更新了深层嵌套的子文档

时间:2013-08-11 15:25:22

标签: 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}]
}

我在mongo(在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中执行此操作的正确方法是什么?这是mongo v2.4.1。

11 个答案:

答案 0 :(得分:41)

不幸的是,每个键不能多次使用$运算符,因此必须使用数值。如:

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 }}
)

答案 1 :(得分:18)

使用mongoose管理解决它:

所有您需要知道的是链中所有子文档的“_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();
  });

有关子文档_id方法in mongoose documentation的更多信息。

解释:_id用于SchemaName,_id1用于sub1,_id2用于sub2 - 你可以保持链接。

*您不必使用findById方法,但在我看来这是最方便的,因为您无论如何都需要了解其余的方法。

答案 2 :(得分:8)

MongoDB在版本3.5.2及更高版本中引入了 ArrayFilters 来解决此问题。

  

版本3.6中的新功能。

     

从MongoDB 3.6开始,在更新数组字段时,您可以指定   arrayFilters确定要更新的数组元素。

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

让我们将Schema设计说如下:

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');
})

答案 3 :(得分:5)

这是MongoDB中的一个非常老的错误

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

答案 4 :(得分:3)

鉴于MongoDB似乎没有为此提供良好的机制,我发现谨慎使用mongoose简单地使用.findOne(...)从mongo集合中提取元素,对其相关运行for循环搜索subelements(通过说ObjectID寻找),修改那个JSON,然后做Schema.markModified('your.subdocument'); Schema.save();它可能效率不高,但它非常简单并且工作正常。

答案 5 :(得分:2)

这是固定的。 https://jira.mongodb.org/browse/SERVER-831

但是从MongoDB 3.5.12开发版本开始可以使用此功能。

注意:此问题在Aug 11 2013上提出,并在Aug 11 2017

上得到解决

答案 6 :(得分:1)

分享我的经验教训。我最近遇到了相同的要求,我需要更新嵌套数组项。 我的结构如下

  {
    "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. 使用$ set关键字
  2. 使用[]语法

    指定$ set中的动态字段
                query = {
                    "locations.units.unitId": "PHG_KTN2"
                };
                var updateItem = {
                    $set: {
                        ["locations.$.units."+ foundUnitIdx]: unitItem
                    }
                };
                var result = collection.update(
                    query,
                    updateItem,
                    {
                        upsert: true
                    }
                );
    
  3. 希望这有助于他人。 :)

答案 7 :(得分:1)

我今天遇到了同样的问题,在google / stackoverflow / github上进行了很多探索之后,我发现arrayFilters是解决此问题的最佳方法。它将与mongo 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
});

在快递中使用猫鼬,更新给定ID的花名册名称。

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
        });
    });
}
}

答案 8 :(得分:0)

我搜索了大约5个小时,终于找到了最佳,最简单的解决方案: HOW TO UPDATE NESTED SUB-DOCUMENTS IN MONGO DB

{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);
                });

答案 9 :(得分:-1)

Okkk.we可以在mongodb中更新我们的嵌套子文档。这是我们的架构。

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

此架构的解决方案

psmt1 = con.prepareStatement("insert into medication(tid,titname,minvalue,maxvalue,disc) values(AES_ENCRYPT(?, 'key'),AES_ENCRYPT(?, 'key'),AES_ENCRYPT(?, 'key'),AES_ENCRYPT(?, 'key'),AES_ENCRYPT(?, 'key'))");

byte[] keyBytes = "key".getBytes();
psmt1.setString(1, title[0]);
psmt1.setBytes(2, keyBytes);
psmt1.setString(3, title[1]);
psmt1.setBytes(4, keyBytes);
psmt1.setString(5, min);
psmt1.setBytes(6, keyBytes);
psmt1.setString(7, max);
psmt1.setBytes(8, keyBytes);
psmt1.setString(9, disc);
psmt1.setBytes(10, keyBytes);
psmt1.executeUpdate();

答案 10 :(得分:-1)

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()