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

Lio*_*orH 171 arrays mongodb mongodb-query

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

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

该文件采用以下形式:

{
    "_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": "....."
        }
        ...
    ]
}
Run Code Online (Sandbox Code Playgroud)

所以,我尝试了以下内容:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)
Run Code Online (Sandbox Code Playgroud)

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

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

Jav*_*ero 110

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

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

  • 单独更新每个项目(events.0.handled events.1.handled ...)或......
  • 阅读文档,手动编辑并保存以替换旧版本(如果要确保原子更新,请选中"如果当前更新")

  • 如果你有类似的问题,请投票支持这个问题 - http://jira.mongodb.org/browse/SERVER-1243 (14认同)
  • 抛开所有技术难题,令人惊讶的是MongoDB中没有此功能.此约束消除了自定义数据库架构的自由. (12认同)
  • Neil Lunn /sf/answers/3223792071/对版本3.6的回答是这样的.由于这是一个很受欢迎的问题,因此可以通过Neil Lunn的答案更新这个已接受的答案. (5认同)

Dan*_*edo 64

对我有用的是:

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);
  });
Run Code Online (Sandbox Code Playgroud)

我认为mongo新手和任何熟悉JQuery和朋友的人都会更清楚.

  • @Squirrl可能是过时的mongodb版本?该文档清楚如何在游标上应用每个函数但是没有说明支持哪个版本.http://docs.mongodb.org/manual/reference/method/cursor.forEach/ (3认同)

Nei*_*unn 56

随着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 }
)
Run Code Online (Sandbox Code Playgroud)

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

注意到"multi"在问题的上下文中给出的内容被用于期望这将"更新多个元素",但这不是,现在仍然不是这样.这里的用法适用于"多个文档",因为一直是这种情况,或者现在以其他方式指定为.updateMany()现代API版本的强制设置.

注意有点讽刺的是,因为这是在方法的"options"参数中指定的.update(),所以语法通常与所有最新的发行版驱动程序版本兼容.

但是对于mongoshell 来说并非如此,因为在那里实现该方法的方式("具有讽刺意味的是为了向后兼容性"),该arrayFilters参数不被内部方法识别和删除,该方法解析选项以便提供与先前的"向后兼容性". MongoDB服务器版本和"遗留" .update()API调用语法.

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

另请参见positional all $[]哪些更新"多个数组元素"但不应用于指定的条件,并应用于数组中所需的操作所有元素.

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

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

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )
Run Code Online (Sandbox Code Playgroud)

这启用了诸如新位置更新操作符等功能.您还可以查看:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
Run Code Online (Sandbox Code Playgroud)

要返回当前设置

  • 接受的答案应该更新并参考这个答案. (7认同)
  • 什么是“元素”? (6认同)

Sea*_*ean 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 }
    );
}
Run Code Online (Sandbox Code Playgroud)

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

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


Bla*_*ven 13

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

在单个更新语句中仍然无法更新多个匹配的数组元素,因此即使使用"多"更新,您只能在该单个文档中为每个文档中的一个数据元素进行更新声明.

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

.aggregate()当存在阵列的"唯一"标识符时,那里的部分将起作用,或者每个元素的所有内容本身形成"唯一"元素.这是因为"set"运算符$setDifference用于过滤false$map用于处理匹配数组的操作返回的任何值.

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

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

如果"处理"实际上是一个意味着出现在其他文档级别的字段,那么你可能会得到意想不到的结果,但是该字段只出现在一个文档位置并且是相等匹配的情况下很好.

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

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

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

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

在MongoDB版本支持聚合输出的"游标"的所有情况下,这只是选择一种方法并使用显示的相同代码块迭代结果以处理批量更新语句的问题.批量操作和聚合输出的"游标"在同一版本(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 }});
    });
});
Run Code Online (Sandbox Code Playgroud)

如果您坚决决定进行"多"更新或认为最终比为每个匹配的文档处理多个更新更有效,那么您始终可以确定可能的数组匹配的最大数量,并且只执行许多"多"更新时间,直到基本上没有更新的文件.

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 })
}
Run Code Online (Sandbox Code Playgroud)

无论如何,在更新中您都不希望做某些事情:

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

  2. 不要计算要更新的索引值:在类似于"一次性"方法的情况下,您只需计算出位置0和位置2(等等)是要更新的代码,并使用最终语句对其进行编码,如:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    
    Run Code Online (Sandbox Code Playgroud)

    同样,这里的问题是"推测",即在读取文档时找到的那些索引值在更新时与第一个数组中的索引值相同.如果以更改顺序的方式将新项添加到数组中,则这些位置不再有效,并且实际上更新了错误的项.

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

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


luk*_*her 8

我很惊讶这仍然没有在mongo中得到解决.在处理子阵列时,整体mongo似乎不太好.例如,您无法统计子阵列.

我使用了哈维尔的第一个解决方案.将数组读入事件然后循环并构建集合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});
Run Code Online (Sandbox Code Playgroud)

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

  • 这不是一个安全的解决方案.如果在运行更新时添加了记录,则会损坏您的数据. (9认同)

小智 8

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

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

解决方案:

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

在此处访问 mongodb 文档

代码的作用:

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

  2. $set更新操作:修改匹配的文件领域它作用于。

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

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

数组过滤器


VIK*_*HLI 7

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

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

它会将“arr”数组中的所有“状态”值更新为“已完成”

如果只有一份文件

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

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

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


ers*_*snh 5

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

在 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
)
Run Code Online (Sandbox Code Playgroud)

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