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

时间:2011-01-12 13:13:39

标签: arrays mongodb mongodb-query

我有一个Mongo文档,其中包含一系列元素。

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

该文件采用以下形式:

{
    "_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)

但是,它仅更新每个文档中第一个匹配的数组元素。 (这是$ - the positional operator的定义行为。)

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

16 个答案:

答案 0 :(得分:107)

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

作为一项解决方案,您可以:

  • 分别更新每个项目 (events.0.handled events.1.handled ......)或......
  • 阅读文档,进行编辑 手动并保存它替换 较旧的(如果你想确保,请检查"Update if Current" 原子更新)

答案 1 :(得分:63)

对我有用的是:

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& amp;的人都会更清楚。朋友。

答案 2 :(得分:46)

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

这使用此版本中引入的filtered positional $[<identifier>]更新运算符语法:

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

传递给.update()的选项的"arrayFilters"甚至是"multi" .updateOne().updateMany().findOneAndUpdate().bulkWrite()方法指定要在update语句中给出的标识符匹配的条件。任何符合条件的元素都将被更新。

注意到在问题的上下文中给出的.update()被用于期望这将更新多个元素&#34;但事实并非如此,而事实并非如此。它的用法适用于&#34;多个文档&#34; 一如既往的情况,或者现在另行指定为现代API版本中.updateMany()的强制设置。

  

注意有点讽刺的是,因为这是在&#34;选项&#34;中指定的。对于mongo和类似方法的参数,语法通常与所有最新的发行版驱动程序版本兼容。

     

然而,arrayFilters shell不是这样,因为在那里实现方法的方式(&#34;具有讽刺意味的是为了向后兼容&#34;).update()参数无法识别和删除通过内部方法解析选项以实现向后兼容性&#34;使用先前的MongoDB服务器版本和&#34;遗产&#34; mongo API调用语法。

     

因此,如果您想在 db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } ) shell或其他基于shell的&#34;中使用该命令。产品(特别是Robo 3T)您需要3.6或更高版本的开发分支或生产版本的最新版本。

另请参阅positional all $[],它还会更新&#34;多个数组元素&#34;但不适用于指定的条件,并适用于数组中所有元素,这是所需的操作。

另见Updating a Nested Array with MongoDB这些新的位置运算符如何应用于&#34;嵌套&#34;数组结构,其中&#34;数组在其他数组中#34;。

  

重要 - 以前版本的升级安装&#34;可能&#34;尚未启用MongoDB功能,这也可能导致语句失败。您应确保升级过程完成,并提供索引升级等详细信息,然后运行

"4.0"
     

或更高版本适用于您安装的版本。即 db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } ) 版本4及以后版本。这启用了诸如新位置更新操作符等功能。您还可以查看:

String content="<html>
<head>
<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW"> 
<META HTTP-EQUIV="Refresh" CONTENT="300">
<title>Relay Control  - Cabin + Conference Logger</title>
</head>
<tr>
<td valign=top width="17%" height="100%">
<table width="100%" height="100%" align=center border=0 cellspacing=1 cellpadding=0>
    <tr><td valign=top bgcolor="#F4F4F4">
    <table width="100%" cellpadding=1 cellspacing=5>

    <tr><td align=center>

    <table><tr><td><a href="http://www.digital-loggers.com/1P.html"><img src="logo.gif" width=195 height=65 border=0 alt="Digital Loggers, Inc."></a></td>

    <td><b><font size=-1>Ethernet Power Controller</font></b></td></tr></table>
    <hr>
    </td></tr>



<tr><td nowrap><b><a href="/index.htm">Relay Control</a></b></td></tr>
<tr><td nowrap><b><a href="/admin.htm">Setup</a></b></td></tr>
<tr><td nowrap><b><a href="/script.htm">Scripting</a></b></td></tr>


<tr><td nowrap><b><a href="/rtc.htm">Date/Time</a></b></td></tr>
<tr><td nowrap><b><a href="/serial.htm">Serial Ports</a></b></td></tr>

<tr><td nowrap><b><a href="/ap.htm">AutoPing</a></b></td></tr>
<tr><td nowrap><b><a href="/syslog.htm">System Log</a></b></td></tr>
<tr><td nowrap><b><a href="/logout">Logout</a></b></td></tr>
<tr><td nowrap><b><a href="/support.htm">Support</a></b></td></tr>
<tr><td nowrap><b><a href="/help/">Help</a></b></td></tr>




</body>
</html>
";
     

返回当前设置

答案 3 :(得分:18)

这也可以通过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且handled不等于0的子文档的最大次数。因此,如果您的集合中有100个文档,并且其中一个文档具有三个与query匹配的子文档,并且所有其他文档具有较少的匹配子文档,则循环将执行三次。

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

答案 4 :(得分:13)

事实上,这与http://jira.mongodb.org/browse/SERVER-1243中的长期问题有关,实际上对于支持&#34;所有情况&#34;的明确语法存在许多挑战。找到多个数组匹配的位置。实际上已经存在的方法可以帮助&#34;解决此问题的方法,例如Bulk Operations已在此原始帖子之后实施。

在单个更新语句中仍然无法更新多个匹配的数组元素,因此即使使用&#34; multi&#34;更新所有您将能够更新的只是该单个语句中每个文档的数组中的一个mathed元素。

目前最好的解决方案是找到并循环所有匹配的文档并处理批量更新,这将至少允许在单个请求中以单一响应发送许多操作。您可以选择使用.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()部分会在有&#34;唯一&#34;数组的标识符或每个元素的所有内容形成一个&#34;唯一的&#34;元素本身。这是由于&#34; set&#34; $setDifference中的运算符用于过滤从用于处理匹配数组的$map操作返回的任何false值。

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

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

如果&#34;处理&#34;它的限制在哪里?实际上是一个意味着出现在其他文档级别的字段然后您可能会得到意想不到的结果,但是该字段只出现在一个文档位置并且是相等匹配的情况下很好。

将来发布(发布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版本支持&#34;游标&#34;从聚合输出来看,这只是选择一种方法并使用显示的相同代码块迭代结果以处理批量更新语句的问题。批量操作和&#34;游标&#34;来自聚合输出的版本在同一版本(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 }});
    });
});

如果你坚决决定做多&#34;多&#34;更新或认为最终比为每个匹配的文档处理多个更新更有效,那么您始终可以确定可能的数组匹配的最大数量,并且只执行&#34; multi&#34;多次更新,直到基本上没有更新的文件。

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

无论是哪种情况,您都会在更新中做某些想要做的事情:

  1. 不要&#34;一次拍摄&#34;更新数组:如果您认为在代码中更新整个数组内容可能更有效,那么只需$set每个文档中的整个数组。处理起来似乎更快,但无法保证阵列内容在读取和执行更新后没有发生变化。虽然$set仍然是一个原子操作符,但它只会用它所认为的#34;来更新数组。是正确的数据,因此很可能会覆盖读取和写入之间发生的任何变化。

  2. 不计算要更新的索引值:类似于&#34; one shot&#34;接近你只需计算出位置0和位置2(等等)是要更新的代码,并使用最终语句对它们进行编码,如:

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

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

  3. 因此,在确定允许在单个更新语句中处理多个匹配的数组元素的合理语法之前,基本方法是在一个单独的语句中更新每个匹配的数组元素(理想情况下为Bulk)或基本上计算出最大数组元素,用于更新或保持更新,直到不再返回修改后的结果。无论如何,你应该&#34;永远&#34;在匹配的数组元素上处理positional $更新,即使每个语句只更新一个元素。

    批量操作实际上是&#34;概括的&#34;处理任何有效的操作的解决方案&#34;多个操作&#34;,并且因为有更多的应用程序,而不仅仅是更新具有相同值的多个数组元素,那么它当然已经实现了,它目前是解决这个问题的最佳方法。

答案 5 :(得分:8)

我很惊讶这仍未在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});

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

答案 6 :(得分:4)

我一直在寻找使用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});
}

有关更多信息,请参阅我的原始帖子: Remove array element from ALL documents using MongoDB C# driver

答案 7 :(得分:4)

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

答案 8 :(得分:2)

首先:您的代码不起作用,因为您使用的是位置运算符$,该运算符仅标识要在数组中更新的元素,甚至没有明确指定其在数组中的位置。

您需要的是过滤后的位置运算符$[<identifier>]。它将更新所有与数组过滤条件匹配的元素。

解决方案:

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

Visit mongodb doc here

代码的作用:

  1. {"events.profile":10}过滤您的收藏并返回与过滤器匹配的文档

  2. $set更新运算符:修改其作用于文档的匹配字段。

  3. {multi:true}它使.update()修改所有与过滤器匹配的文档,因此其行为类似于updateMany()

  4. { "events.$[elem].handled" : 0 } and arrayFilters: [ { "elem.profile": 10 } ] 此技术涉及将已过滤的位置数组与arrayFilters一起使用。 $[elem]处经过过滤的位置数组充当数组字段中与数组过滤器中指定的条件匹配的所有元素的占位符。

Array filters

答案 9 :(得分:1)

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

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

//在nodejs的情况下回调函数

答案 10 :(得分:1)

线程很旧,但是我来这里寻求答案,因此提供了新的解决方案。

在MongoDB 3.6+版本中,现在可以使用位置运算符更新数组中的所有项目。参见official documentation here

以下查询将适用于此处提出的问题。我还使用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
)

希望这对像我这样的人有帮助。

答案 11 :(得分:0)

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

保存功能的另一个问题是,它会同时使多重保存冲突数据。 Model.Update 将统一生成数据。 所以更新文档数组中的多项。使用您熟悉的编程语言并尝试这样的东西,我使用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))
})

答案 12 :(得分:0)

$ []运算符选择所有嵌套数组..您可以使用'$ []'来更新所有数组项

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

Reference

答案 13 :(得分:0)

请注意,此线程中的一些答案提示使用$ []是错误的。

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

以上代码将“事件”数组中所有元素的“已处理”更新为0,无论其“配置文件”值如何。查询{"events.profile":10}仅用于过滤整个文档,而不是数组中的文档。在这种情况下,必须将$[elem]arrayFilters一起使用以指定数组项的条件,以便Neil Lunn的答案是正确的。

答案 14 :(得分:0)

更新mongo数据库中多个文档中的数组字段。

使用$ pull或$ push进行多次更新查询以更新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."
            });
        }
    });

答案 15 :(得分:-1)

我只想添加另一个对我有用的解决方案,而且非常简单。这里只是一个标签(字符串)数组,以便更新一个名为&#34; test&#34;的标签。到&#34;改变&#34;,只需这样做:

myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
    myDocuments.update(
        {_id: doc._id, tags: "test"}, 
        {$set:{'tags.$': "changed"}});
    });