我有一个Mongo文档,其中包含一个元素数组。

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

文件格式如下:

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

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

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

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

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


当前回答

更新: 从Mongo 3.6版开始,这个答案不再有效,因为上面提到的问题已经修复,有办法实现这一点。请检查其他答案。


此时,不可能使用位置操作符来更新数组中的所有项。参见JIRA http://jira.mongodb.org/browse/SERVER-1243

作为一份工作,你可以:

单独更新每个项目 (events.0。处理events.1.handled )或… 阅读文件,进行编辑 手动保存并替换 旧版本(勾选“如果更新 如果你想确保“当前” 原子更新)

其他回答

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

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

目前可能的最佳解决方案是查找并循环所有匹配的文档,并处理Bulk更新,这至少将允许在单个请求中以单个响应发送多个操作。您可以选择使用.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();

当数组有一个“唯一”标识符或每个元素的所有内容都形成一个“唯一”元素本身时,.aggregate()部分将工作。这是由于$setDifference中的“set”操作符用于过滤从用于处理匹配数组的$map操作返回的任何错误值。

如果你的数组内容没有唯一的元素,你可以用$ react尝试另一种方法:

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

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

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

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

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

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

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

在更早的版本中,最好只使用.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 }});
    });
});

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

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

无论哪种情况,在更新中有一些事情是你不想做的:

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 $set the 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 $set is 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. Do not calculate index values to update: Where similar to the "one shot" approach you just work out that position 0 and 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.

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.

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

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

这使用了在这个版本中引入的过滤位置$[<identifier>]更新操作符语法:

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

"arrayFilters"被传递给.update()或甚至 . updateone (), . updatemany (), . findoneandupdate()或. bulkwrite()方法指定更新语句中给定的标识符要匹配的条件。任何匹配给定条件的元素都将被更新。

注意到问题上下文中所给出的“multi”是在期望这将“更新多个元素”的情况下使用的,但事实并非如此,而且仍然不是如此。它在这里的用法适用于“多个文档”,就像一直以来的情况一样,或者现在在现代API版本中指定为. updatemany()的强制设置。

NOTE Somewhat 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 mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument 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 mongo shell 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.

另参见positional all $[],它也更新“多个数组元素”,但不应用于指定的条件,并应用于数组中的所有元素,其中这是所需的操作。

另请参见用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

如果你想在数组中更新数组

    await Booking.updateOne(
      {
        userId: req.currentUser?.id,
        cart: {
          $elemMatch: {
            id: cartId,
            date: date,
            //timeSlots: {
              //$elemMatch: {
                //id: timeSlotId,
              //},
            //},
          },
        },
      },
      {
        $set: {
          version: booking.version + 1,
          'cart.$[i].timeSlots.$[j].spots': spots,
        },
      },
      {
        arrayFilters: [
          {
            'i.id': cartId,
          },
          {
            'j.id': timeSlotId,
          },
        ],
        new: true,
      }
    );

您可以更新MongoDB中的所有元素

db.collectioname.updateOne(
{ "key": /vikas/i },
{ $set: { 
 "arr.$[].status" : "completed"
} }
)

它将更新“arr”数组中所有的“status”值为“completed”

如果只有一份文件

db.collectioname.updateOne(
 { key:"someunique", "arr.key": "myuniq" },
 { $set: { 
   "arr.$.status" : "completed", 
   "arr.$.msgs":  {
                "result" : ""
        }
   
 } }
)

但如果不是一个,而且你也不希望数组中的所有文档都更新,那么你需要遍历元素和if块内部

db.collectioname.find({findCriteria })
  .forEach(function (doc) {
    doc.arr.forEach(function (singlearr) {
      if (singlearr check) {
        singlearr.handled =0
      }
    });
    db.collection.save(doc);
  });

我尝试了以下,它的工作良好。

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

// nodejs情况下的回调函数