Mongodb 更新深度嵌套的子文档

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18173482/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 13:21:13  来源:igfitidea点击:

Mongodb update deeply nested subdocument

mongodb

提问by reptilicus

I have a document structure that is deeply nested, like this:

我有一个深度嵌套的文档结构,如下所示:

{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}]
                            }, 
                    ]
                }
            ]
        }, 
    ]

}

I'm trying to update the collection to insert a new config, that looks like this:

我正在尝试更新集合以插入新配置,如下所示:

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

I'm trying something like this in mongo (in Python):

我正在 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}}
                      )

I'm getting "Cannot apply the positional operator without a corresponding query field containing an array" error though. What is the proper way to do this in mongo? This is mongo v2.4.1.

但是,我收到“无法在没有包含数组的相应查询字段的情况下应用位置运算符”错误。在 mongo 中执行此操作的正确方法是什么?这是 mongo v2.4.1。

回答by JohnnyHK

Unfortunately, you can't use the $operator more than once per key, so you have to use numeric values for the rest. As in:

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

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's support for updating nested arrays is poor. So you're best off avoiding their use if you need to update the data frequently, and consider using multiple collections instead.

MongoDB 对更新嵌套数组的支持很差。因此,如果您需要频繁更新数据,最好避免使用它们,并考​​虑使用多个集合。

One possibility: make forecastsits own collection, and assuming you have a fixed set of levelvalues, make levelan object instead of an array:

一种可能性:创建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}]
        }, 
      ]
    }
  }
}

Then you can update it using:

然后您可以使用以下方法更新它:

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

回答by Noam El

Managed to solve it with using mongoose:

设法使用猫鼬解决了这个问题:

All you need to know is the '_id's of all of the sub-document in the chain (mongoose automatically create '_id' for each sub-document).

您只需要知道链中所有子文档的“_id”(猫鼬自动为每个子文档创建“_id”)。

for example -

例如 -

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

More about the sub-document _id method in mongoose documentation.

更多关于mongoose 文档中的子文档 _id 方法。

explanation:_id is for the SchemaName, _id1 for sub1 and _id2 for sub2 - you can keep chaining like that.

解释:_id 用于 SchemaName,_id1 用于 sub1,_id2 用于 sub2 - 你可以像这样继续链接。

*You don't have to use findById method, but it's seem to me the most convenient as you need to know the rest of the '_id's anyway.

*您不必使用 findById 方法,但在我看来它是最方便的,因为您无论如何都需要知道其余的 '_id 。

回答by NIKHIL C M

MongoDB has introduced ArrayFiltersto tackle this issue in Version 3.5.2 and later.

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

New in version 3.6.

Starting in MongoDB 3.6, when updating an array field, you can specify arrayFilters that determine which array elements to update.

3.6 版中的新功能。

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

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

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

Let's say the Schema design as follows :

假设 Schema 设计如下:

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

And Document created looks like this :

创建的文档如下所示:

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

Say I want to update the "url" of an image. Given - "document id", "tour_name" and "title"

假设我想更新图像的“url”。鉴于 - "document id", "tour_name" and "title"

For this the update query :

为此,更新查询:

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

回答by victor sosa

This is a very OLD bug in MongoDB

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

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

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

回答by THE INN-VISIBLE

I was facing same kind of problem today, and after lot of exploring on google/stackoverflow/github, I figured arrayFiltersare the best solution to this problem. Which would work with mongo 3.6 and above. This link finally saved my day: https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html

我今天面临同样的问题,经过在 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
});

And using mongoose in express, updating the name of roster of given id.

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

回答by Engineer

Given how MongoDB doesn't appear to provide a good mechanism for this, I find it prudent to use mongoose to simply extract the element from the mongo collection using .findOne(...), run a for-loop search on its relevant subelements (seeking by say ObjectID), modify that JSON, then do Schema.markModified('your.subdocument'); Schema.save();It's probably not efficient, but it is very simple and works fine.

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

回答by 6339

It's fixed. https://jira.mongodb.org/browse/SERVER-831

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

But this feature is available starting with the MongoDB 3.5.12 development version.

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

Note: This question asked on Aug 11 2013and it's resolved on Aug 11 2017

注意:此问题已提出Aug 11 2013并已解决Aug 11 2017

回答by erickyi2006

Sharing my lessons learned. I faced the same requirement recently where i need to update a nested array item. My structure is as follows

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

  {
    "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"
              }
            ]
          }
        ]
      }
    ]
  }

My requirement is to add some fields in a specific units[]. My solution is first to find the index of the nested array item (say foundUnitIdx) The two techniques I used are

我的要求是在特定的单位[]中添加一些字段。我的解决方案是首先找到嵌套数组项的索引(比如foundUnitIdx)我使用的两种技术是

  1. use the $set keyword
  2. specify the dynamic field in $set using the [] syntax

                query = {
                    "locations.units.unitId": "PHG_KTN2"
                };
                var updateItem = {
                    $set: {
                        ["locations.$.units."+ foundUnitIdx]: unitItem
                    }
                };
                var result = collection.update(
                    query,
                    updateItem,
                    {
                        upsert: true
                    }
                );
    
  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
                    }
                );
    

Hope this helps others. :)

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

回答by AaronCoding

EASY SOLUTION FOR Mongodb 3.2+https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/

Mongodb 3.2+ 的简单解决方案https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/

I had a similar situation and solved it like this. I was using mongoose, but it should still work in vanilla MongoDB. Hope it's useful to someone.

我也遇到过类似的情况,就这样解决了。我使用的是猫鼬,但它仍然可以在 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!')
    })
}

Depending on your use case, you might be able to skip the initial findOne()

根据您的用例,您可能可以跳过初始 findOne()

回答by coder

Okkk.we can update our nested subdocument in mongodb.this is our schema.

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

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

solution for this schema

此模式的解决方案

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