我有一个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 )或… 阅读文件,进行编辑 手动保存并替换 旧版本(勾选“如果更新 如果你想确保“当前” 原子更新)


我很惊讶这个问题在mongo中还没有解决。总的来说,mongo在处理子数组时似乎不太好。例如,不能简单地计算子数组。

我用了哈维尔的第一个方法。将数组读入事件,然后循环并构建set 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});

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


对我有用的是:

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

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


这实际上与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.

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


这也可以用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 }
    );
}

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

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


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

保存功能的另一个问题是,同时在多个保存中会产生冲突数据。 模型。更新将使数据一致。 因此要更新文档数组中的多个项目。使用你熟悉的编程语言,尝试这样的东西,我用mongoose:

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

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

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

// nodejs情况下的回调函数


随着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


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

代码如下:

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

要了解更多信息,请参阅我的原文: 使用MongoDB c#驱动程序从所有文档中删除数组元素


这个帖子很老了,但我来这里寻找答案,因此提供了新的解决方案。

在MongoDB 3.6+版本中,现在可以使用位置操作符更新数组中的所有项。点击这里查看官方文件。

下面的问题将适用于这里提出的问题。我也用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
)

希望这能帮助到像我这样的人。


$[]操作符选择所有嵌套数组。使用'$[]'可以更新所有数组项

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

参考


请注意,在这个帖子中建议使用$[]的一些答案是错误的。

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

上面的代码将“events”数组中的所有元素的“handled”更新为0,不管它的“profile”值是多少。查询{"events. events. "Profile ":10}仅用于过滤整个文档,而不是数组中的文档。在这种情况下,必须使用$[elem]和arrayFilters来指定数组项的条件,以便Neil Lunn的答案是正确的。


在mongodb中更新多个文档中的数组字段。

使用$pull或$push和update many query来更新mongoDb中的数组元素。

Notification.updateMany(
    { "_id": { $in: req.body.notificationIds } },
    {
        $pull: { "receiversId": req.body.userId }
    }, function (err) {
        if (err) {
            res.status(500).json({ "msg": err });
        } else {
            res.status(200).json({
                "msg": "Notification Deleted Successfully."
            });
        }
    });

首先:您的代码无法工作,因为您使用了位置操作符$,该操作符仅标识数组中要更新的元素,但甚至没有显式地指定其在数组中的位置。

您需要的是筛选位置操作符$[<identifier>]。它将更新所有匹配数组筛选条件的元素。

解决方案:

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

点击这里访问mongodb doc

代码的作用:

{"events.profile":10} filters your collection and return the documents matching the filter The $set update operator: modifies matching fields of documents it acts on. {multi:true} It makes .update() modifies all documents matching the filter hence behaving like updateMany() { "events.$[elem].handled" : 0 } and arrayFilters: [ { "elem.profile": 10 } ] This technique involves the use of the filtered positional array with arrayFilters. the filtered positional array here $[elem] acts as a placeholder for all elements in the array fields that match the conditions specified in the array filter.

数组的过滤器


您可以更新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);
  });

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

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