【问题标题】:Mongodb update deeply nested subdocumentMongodb 更新深度嵌套的子文档
【发布时间】:2013-08-12 23:12:46
【问题描述】:

我有一个深度嵌套的文档结构,像这样:

{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。

【问题讨论】:

  • 您是要替换其中的数据,还是使用该新数据向数组添加另一个索引?
  • 这就是我正在收集的内容。那么目前的解决方法是什么?这很蹩脚,如果你不能做这样的事情,嵌套文档有什么意义。
  • @tymeJV 是的,您需要为密钥中的其他索引使用数值。 Mongo 对更新嵌套数组的支持很差。
  • 这是不可能的,可耻。如果您无法创建嵌套文档,那么 mongo 的意义何在。您必须创建多个集合,此时您又回到了关系数据库!
  • @reptilicus -- 同意。

标签: mongodb


【解决方案1】:

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

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

【讨论】:

  • 嗨@JohnnyHK,我已经发布了question,我遇到了类似的问题。我试过像你展示的那样使用$set,但没有成功。任何帮助将不胜感激!
  • 这种特殊的数据透视方法存在其他问题,例如索引碎片。
  • @JohnnyHK,我认为您的答案的第一部分有错误。在 '{"$set": {"forecasts.0.levels.0.configs.$": newData}}' 中,$ 运算符放置错误,因为该运算符返回第一个嵌套列表中对象的索引,这意味着它在当前查询中始终为 0。您可以通过将查询更改为使用 '"forecasts.levels.configs.config": "Custom 2"' 来测试这一点,您将看到更新的文档是具有 "config" : "Custom 1" 的文档。正确的应该是 '{"$set": {"forecasts.$.levels.0.configs.0": newData}}'
  • 如果您对所有数组项使用唯一 ID,这似乎是一个不错的解决方法。 forecasts.$.levels.0.configs.0 应始终为自定义 1。有什么理由不使用它?
  • @user2491336 只要您提前知道所需的levelsconfigs 元素的索引,它就可以正常工作,但情况并非总是如此。
【解决方案2】:

设法使用猫鼬解决它:

你只需要知道链中所有子文档的'_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 方法,但在我看来这是最方便的,因为无论如何您都需要知道 '_id' 的其余部分。

【讨论】:

  • 点符号就像一个魅力。巨大的帮助。过去两天一直在寻找这个简单的解决方案!谢谢!
  • 这是一个巨大的性能瓶颈,因为更新需要很长时间
  • 这应该是公认的答案!救了我这么头疼
  • 确实很慢!
【解决方案3】:

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]

假设架构设计如下:

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

【讨论】:

【解决方案4】:

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

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

【讨论】:

【解决方案5】:

我今天遇到了同样的问题,在 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
});

并在 express 中使用 mongoose,更新给定 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
        });
    });
}
}

【讨论】:

    【解决方案6】:

    已经解决了。 https://jira.mongodb.org/browse/SERVER-831

    但是这个功能从 MongoDB 3.5.12 开发版本开始可用。

    注意:这个问题是在 Aug 11 2013 上提出的,它已在 Aug 11 2017 上得到解决

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        我搜索了大约 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);
                        });
        

        【讨论】:

        • 像魅力一样工作!顺便说一句,您可以删除 $elemMatch 部分,没有它查询将运行
        【解决方案9】:

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

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

        希望这对其他人有所帮助。 :)

        【讨论】:

        • 我认为你至少需要 v3.4(尽管 doco 说这是 v3.5 中修复的错误)我正在成功使用 v3.4
        • 谢谢。你知道我们如何在 3.2 及更早的版本中实现吗?
        • 抱歉,回复晚了。在 v3.2 中,它在更新中支持 $position(自 v2.6 起)。你可以尝试使用它。我自己没有试过。祝你好运
        【解决方案10】:

        适用于 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()

        【讨论】:

          【解决方案11】:

          好的。我们可以在 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
          })
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-09-23
            • 2016-08-04
            • 2018-04-05
            • 2021-03-26
            • 1970-01-01
            相关资源
            最近更新 更多