如何在 mongodb 中更新多个数组元素

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4669178/
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 11:55:48  来源:igfitidea点击:

How to Update Multiple Array Elements in mongodb

arraysmongodbmongodb-query

提问by LiorH

I have a Mongo document which holds an array of elements.

我有一个包含元素数组的 Mongo 文档。

I'd like to reset the .handledattribute of all objects in the array where .profile= XX.

我想重置.handled数组中所有对象的属性,其中.profile= XX。

The document is in the following form:

该文件采用以下形式:

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

so, I tried the following:

所以,我尝试了以下方法:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

However it updates only the firstmatched array element in each document. (That's the defined behaviour for $ - the positional operator.)

但是它只更新每个文档中第一个匹配的数组元素。(这是$ - 位置运算符的定义行为。)

How can I update allmatched array elements?

如何更新所有匹配的数组元素?

采纳答案by Javier Ferrero

At this moment it is not possible to use the positional operator to update all items in an array. See JIRA http://jira.mongodb.org/browse/SERVER-1243

目前无法使用位置运算符更新数组中的所有项目。见 JIRA http://jira.mongodb.org/browse/SERVER-1243

As a work around you can:

作为一种解决方法,您可以:

  • Update each item individually (events.0.handled events.1.handled ...) or...
  • Read the document, do the edits manually and save it replacing the older one (check "Update if Current"if you want to ensure atomic updates)
  • 单独更新每个项目(events.0.handled events.1.handled ...)或...
  • 阅读文档,手动编辑并保存替换旧文档(如果要确保原子更新,请选中“如果当前更新”)

回答by Neil Lunn

With the release of MongoDB 3.6( and available in the development branch from MongoDB 3.5.12 ) you can now update multiple array elements in a single request.

随着MongoDB 3.6发布(在 MongoDB 3.5.12 的开发分支中可用),您现在可以在单个请求中更新多个数组元素。

This uses the filtered positional $[<identifier>]update operator syntax introduced in this version:

这使用了此版本中引入的过滤位置$[<identifier>]更新运算符语法:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

The "arrayFilters"as passed to the options for .update()or even .updateOne(), .updateMany(), .findOneAndUpdate()or .bulkWrite()method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.

"arrayFilters"传递给.update()or even .updateOne(), .updateMany(), .findOneAndUpdate()or.bulkWrite()方法的as指定了与更新语句中给定的标识符匹配的条件。任何符合给定条件的元素都将被更新。

Noting that the "multi"as given in the context of the question was used in the expectation that this would "update multiple elements" but this was not and still is not the case. It's usage here applies to "multiple documents"as has always been the case or now otherwise specified as the mandatory setting of .updateMany()in modern API versions.

注意到"multi"在问题上下文中给出的 被用于预期这将“更新多个元素”,但事实并非如此,现在仍然不是这种情况。此处的用法适用于“多个文档”,一直如此,或者现在以其他方式指定为.updateMany()现代 API 版本中的强制设置。

NOTESomewhat ironically, since this is specified in the "options" argument for .update()and like methods, the syntax is generally compatible with all recent release driver versions.

However this is not true of the mongoshell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFiltersargument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update()API call syntax.

So if you want to use the command in the mongoshell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.

注意有点讽刺的是,由于这是在“选项”参数中指定的.update()和类似方法,语法通常与所有最新发布的驱动程序版本兼容。

然而,这对于mongoshell 而言并非如此,因为该方法在那里实现的方式(“具有讽刺意味的是向后兼容”)该arrayFilters参数未被内部方法识别和删除,该方法解析选项以提供与先前的“向后兼容性” MongoDB 服务器版本和“传统” .update()API 调用语法。

因此,如果您想在mongoshell 或其他“基于 shell”的产品(特别是 Robo 3T )中使用该命令,您需要来自开发分支或生产版本 3.6 或更高版本的最新版本。

See also positional all $[]which also updates "multiple array elements" but without applying to specified conditions and applies to allelements in the array where that is the desired action.

另请参阅positional all $[]which 还更新“多个数组元素”,但不应用于指定条件,并应用于数组中所需操作的所有元素。

Also see Updating a Nested Array with MongoDBfor how these new positional operators apply to "nested" array structures, where "arrays are within other arrays".

另请参阅使用 MongoDB 更新嵌套数组,了解这些新的位置运算符如何应用于“嵌套”数组结构,其中“数组位于其他数组中”。

IMPORTANT- Upgraded installations from previous versions "may" have not enabled MongoDB features, which can also cause statements to fail. You should ensure your upgrade procedure is complete with details such as index upgrades and then run

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

Or higher version as is applicable to your installed version. i.e "4.0"for version 4 and onwards at present. This enabled such features as the new positional update operators and others. You can also check with:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

To return the current setting

重要- 从以前版本升级的安装“可能”没有启用 MongoDB 功能,这也可能导致语句失败。你应该确保你的升级过程完成了索引升级等细节,然后运行

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

或适用于您安装的版本的更高版本。即"4.0"目前的第 4 版及以后的版本。这启用了诸如新的位置更新运算符等功能。您还可以通过以下方式检查:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

返回当前设置

回答by Daniel Cerecedo

What worked for me was this:

对我有用的是:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

I think it's clearer for mongo newbies and anyone familiar with JQuery & friends.

我认为对于 mongo 新手和任何熟悉 JQuery 和朋友的人来说更清楚。

回答by Sean

This can also be accomplished with a while loop which checks to see if any documents remain that still have subdocuments that have not been updated. This method preserves the atomicity of your updates (which many of the other solutions here do not).

这也可以通过 while 循环来完成,该循环检查是否有任何文档仍然具有尚未更新的子文档。这种方法保留了更新的原子性(这里的许多其他解决方案都没有)。

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

The number of times the loop is executed will equal the maximum number of times subdocuments with profileequal to 10 and handlednot equal to 0 occur in any of the documents in your collection. So if you have 100 documents in your collection and one of them has three subdocuments that match queryand all the other documents have fewer matching subdocuments, the loop will execute three times.

执行循环的次数将等于在您的集合中的任何文档中出现profile等于 10handled且不等于 0 的子文档的最大次数。因此,如果您的集合中有 100 个文档,其中一个具有三个匹配的子文档,query而所有其他文档的匹配子文档较少,则循环将执行 3 次。

This method avoids the danger of clobbering other data that may be updated by another process while this script executes. It also minimizes the amount of data being transferred between client and server.

此方法避免了在此脚本执行时破坏可能由另一个进程更新的其他数据的危险。它还最大限度地减少了客户端和服务器之间传输的数据量。

回答by Blakes Seven

This does in fact relate to the long standing issue at http://jira.mongodb.org/browse/SERVER-1243where there are in fact a number of challenges to a clear syntax that supports "all cases" where mutiple array matches are found. There are in fact methods already in place that "aid" in solutions to this problem, such as Bulk Operationswhich have been implemented after this original post.

这实际上与http://jira.mongodb.org/browse/SERVER-1243中长期存在的问题有关,其中实际上对支持多个数组匹配的“所有情况”的清晰语法存在许多挑战成立。事实上,已经有一些方法可以“帮助”解决这个问题,例如在这篇原始帖子之后实施的批量操作

It is still not possible to update more than a single matched array element in a single update statement, so even with a "multi" update all you will ever be able to update is just one mathed element in the array for each document in that single statement.

仍然不可能在单个更新语句中更新多个匹配的数组元素,因此即使使用“多”更新,您也只能为该单个文档中的每个文档更新数组中的一个数学元素陈述。

The best possible solution at present is to find and loop all matched documents and process Bulk updates which will at least allow many operations to be sent in a single request with a singular response. You can optionally use .aggregate()to reduce the array content returned in the search result to just those that match the conditions for the update selection:

目前最好的解决方案是查找和循环所有匹配的文档并处理批量更新,这至少允许在具有单一响应的单个请求中发送许多操作。您可以选择使用.aggregate()将搜索结果中返回的数组内容减少到与更新选择条件匹配的内容:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$setDifference": [
               { "$map": {
                   "input": "$events",
                   "as": "event",
                   "in": {
                       "$cond": [
                           { "$eq": [ "$$event.handled", 1 ] },
                           "$$el",
                           false
                       ]
                   }
               }},
               [false]
            ]
        }
    }}
]).forEach(function(doc) {
    doc.events.forEach(function(event) {
        bulk.find({ "_id": doc._id, "events.handled": 1  }).updateOne({
            "$set": { "events.$.handled": 0 }
        });
        count++;

        if ( count % 1000 == 0 ) {
            bulk.execute();
            bulk = db.collection.initializeOrderedBulkOp();
        }
    });
});

if ( count % 1000 != 0 )
    bulk.execute();

The .aggregate()portion there will work when there is a "unique" identifier for the array or all content for each element forms a "unique" element itself. This is due to the "set" operator in $setDifferenceused to filter any falsevalues returned from the $mapoperation used to process the array for matches.

.aggregate()当数组有一个“唯一”标识符或每个元素的所有内容形成一个“唯一”元素本身时,那里的部分将起作用。这是由于“设置”运算符$setDifference用于过滤false$map用于处理匹配数组的操作返回的任何值。

If your array content does not have unique elements you can try an alternate approach with $redact:

如果您的数组内容没有唯一元素,您可以尝试使用以下替代方法$redact

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}
])

Where it's limitation is that if "handled" was in fact a field meant to be present at other document levels then you are likely going to get unexepected results, but is fine where that field appears only in one document position and is an equality match.

它的局限性在于,如果“处理”实际上是一个字段,该字段旨在出现在其他文档级别,那么您可能会得到意想不到的结果,但是如果该字段仅出现在一个文档位置并且是相等匹配,则很好。

Future releases ( post 3.1 MongoDB ) as of writing will have a $filteroperation that is simpler:

编写时的未来版本(3.1 MongoDB 后)将有一个$filter更简单的操作:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

And all releases that support .aggregate()can use the following approach with $unwind, but the usage of that operator makes it the least efficient approach due to the array expansion in the pipeline:

并且所有支持的版本都.aggregate()可以使用以下方法 with $unwind,但由于管道中的数组扩展,该运算符的使用使其成为效率最低的方法:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "events": { "$push": "$events" }
    }}        
])

In all cases where the MongoDB version supports a "cursor" from aggregate output, then this is just a matter of choosing an approach and iterating the results with the same block of code shown to process the Bulk update statements. Bulk Operations and "cursors" from aggregate output are introduced in the same version ( MongoDB 2.6 ) and therefore usually work hand in hand for processing.

在 MongoDB 版本支持来自聚合输出的“游标”的所有情况下,这只是选择一种方法并使用显示的相同代码块迭代结果来处理批量更新语句的问题。来自聚合输出的批量操作和“游标”在同一版本 (MongoDB 2.6) 中引入,因此通常在处理过程中携手合作。

In even earlier versions then it is probably best to just use .find()to return the cursor, and filter out the execution of statements to just the number of times the array element is matched for the .update()iterations:

在更早的版本中,最好只使用.find()返回游标,并将语句的执行过滤为仅匹配数组元素以进行.update()迭代的次数:

db.collection.find({ "events.handled": 1 }).forEach(function(doc){ 
    doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
        db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
    });
});

If you are aboslutely determined to do "multi" updates or deem that to be ultimately more efficient than processing multiple updates for each matched document, then you can always determine the maximum number of possible array matches and just execute a "multi" update that many times, until basically there are no more documents to update.

如果您绝对决定进行“多”更新或认为这最终比为每个匹配的文档处理多个更新更有效,那么您始终可以确定可能的数组匹配的最大数量,然后执行“多”更新次,直到基本上没有更多的文件需要更新。

A valid approach for MongoDB 2.4 and 2.2 versions could also use .aggregate()to find this value:

MongoDB 2.4 和 2.2 版本的有效方法也可.aggregate()用于查找此值:

var result = db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "count": { "$max": "$count" }
    }}
]);

var max = result.result[0].count;

while ( max-- ) {
    db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}

Whatever the case, there are certain things you do notwant to do within the update:

无论是哪种情况,还有你做某些事情希望在更新中做到:

  1. Do not "one shot" update the array:Where if you think it might be more efficient to update the whole array content in code and then just $setthe whole array in each document. This might seem faster to process, but there is no guarantee that the array content has not changed since it was read and the update is performed. Though $setis still an atomic operator, it will only update the array with what it "thinks" is the correct data, and thus is likely to overwrite any changes occurring between read and write.

  2. Do not calculate index values to update:Where similar to the "one shot" approach you just work out that position 0and position 2( and so on ) are the elements to update and code these in with and eventual statement like:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    Again the problem here is the "presumption" that those index values found when the document was read are the same index values in th array at the time of update. If new items are added to the array in a way that changes the order then those positions are not longer valid and the wrong items are in fact updated.

  1. 不要“一次性”更新数组:如果您认为在代码中更新整个数组内容可能更有效,然后$set在每个文档中只更新整个数组。这似乎处理起来更快,但不能保证数组内容自读取和执行更新以来没有更改。虽然$set它仍然是一个原子运算符,但它只会用它“认为”是正确的数据来更新数组,因此很可能会覆盖读取和写入之间发生的任何更改。

  2. 不要计算要更新的索引值:在类似于“一次性”方法的地方,您只需计算出位置0和位置2(等等)是更新和编码这些的元素,最终语句如下:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    这里的问题再次是“假设”,即读取文档时找到的索引值与更新时数组中的索引值相同。如果以更改顺序的方式将新项目添加到数组中,则这些位置不再有效,并且错误的项目实际上已更新。

So until there is a reasonable syntax determined for allowing multiple matched array elements to be processed in single update statement then the basic approach is to either update each matched array element in an indvidual statement ( ideally in Bulk ) or essentially work out the maximum array elements to update or keep updating until no more modified results are returned. At any rate, you should "always" be processing positional $updates on the matched array element, even if that is only updating one element per statement.

因此,直到确定了允许在单个更新语句中处理多个匹配数组元素的合理语法之前,基本方法是在单个语句中更新每个匹配的数组元素(理想情况下为 Bulk )或基本上计算出最大数组元素更新或继续更新,直到不再返回修改后的结果。无论如何,您应该“始终”处理匹配数组元素的位置$更新,即使每个语句只更新一个元素。

Bulk Operations are in fact the "generalized" solution to processing any operations that work out to be "multiple operations", and since there are more applications for this than merely updating mutiple array elements with the same value, then it has of course been implemented already, and it is presently the best approach to solve this problem.

批量操作实际上是处理任何“多操作”操作的“通用”解决方案,并且由于这有更多的应用程序而不仅仅是更新具有相同值的多个数组元素,那么它当然已经实现已经,这是目前解决这个问题的最佳方法。

回答by lukenofurther

I'm amazed this still hasn't been addressed in mongo. Overall mongo doesn't seem to be great when dealing with sub-arrays. You can't count sub-arrays simply for example.

我很惊讶这仍然没有在 mongo 中得到解决。在处理子数组时,整体 mongo 似乎不是很好。例如,您不能简单地计算子数组。

I used Javier's first solution. Read the array into events then loop through and build the set exp:

我使用了哈维尔的第一个解决方案。将数组读入事件,然后循环遍历并构建集合 exp:

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

This can be abstracted into a function using a callback for the conditional test

可以使用条件测试的回调将其抽象为函数

回答by C0d3 0n3

I've been looking for a solution to this using the newest driver for C# 3.6 and here's the fix I eventually settled on. The key here is using "$[]"which according to MongoDB is new as of version 3.6. See https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[]for more information.

我一直在寻找使用 C# 3.6 的最新驱动程序的解决方案,这是我最终确定的修复程序。这里的关键是使用“$[]”,根据 MongoDB 是 3.6 版的新内容。请参阅https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up。S[]了解更多信息。

Here's the code:

这是代码:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

For more context see my original post here: Remove array element from ALL documents using MongoDB C# driver

有关更多上下文,请参阅我的原始帖子: 使用 MongoDB C# 驱动程序从所有文档中删除数组元素

回答by ersnh

The thread is very old, but I came looking for answer here hence providing new solution.

该线程很旧,但我在这里寻找答案,因此提供了新的解决方案。

With MongoDB version 3.6+, it is now possible to use the positional operator to update all items in an array. See official documentation here.

在 MongoDB 3.6+ 版本中,现在可以使用位置运算符来更新数组中的所有项目。请参阅此处的官方文档

Following query would work for the question asked here. I have also verified with Java-MongoDB driver and it works successfully.

以下查询适用于此处提出的问题。我还使用 Java-MongoDB 驱动程序进行了验证,并且成功运行。

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

Hope this helps someone like me.

希望这可以帮助像我这样的人。

回答by Pranay Saha

I tried the following and its working fine.

我尝试了以下方法并且工作正常。

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// callback function in case of nodejs

// nodejs 的回调函数

回答by user3176403

Actually, The save command is only on instance of Document class. That have a lot of methods and attribute. So you can use lean()function to reduce work load. Refer here. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

实际上, save 命令仅适用于 Document 类的实例。那有很多方法和属性。所以你可以使用lean()函数来减少工作量。参考这里。https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Another problem with save function, that will make conflict data in with multi-save at a same time. Model.Updatewill make data consistently. So to update multi items in array of document. Use your familiar programming language and try something like this, I use mongoose in that:

保存功能的另一个问题,即会在同时多次保存时产生冲突数据。 Model.Update将使数据一致。所以要更新文档数组中的多个项目。使用你熟悉的编程语言并尝试这样的事情,我使用猫鼬:

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})