221

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

我想重置.handled数组中所有对象的属性 where .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": "....."
        }
        ...
    ]
}

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

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

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

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

4

15 回答 15

118

更新: 从 Mongo 3.6 版开始,此答案不再有效,因为上述问题已修复,并且有办法实现此目的。请检查其他答案。


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

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

  • 单独更新每个项目(events.0.handled events.1.handled ...)或...
  • 阅读文档,手动进行编辑并保存以替换旧文档(如果您想确保原子更新,请选中“如果当前更新” )
于 2011-01-12T14:09:43.670 回答
118

随着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"as 传递给.update()or even .updateOne(), .updateMany(), .findOneAndUpdate()or方法的选项.bulkWrite()指定匹配更新语句中给出的标识符的条件。任何符合给定条件的元素都将被更新。

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

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

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

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

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

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

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

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

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

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

返回当前设置

于 2017-09-05T11:43:20.380 回答
74

对我有用的是:

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 和朋友的人来说更清楚。

于 2013-12-15T23:32:24.570 回答
17

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

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

于 2016-01-19T21:38:34.213 回答
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();

.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"
        }
    }}
])

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

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

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

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

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

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

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

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

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

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

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

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

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

于 2015-10-18T00:58:31.940 回答
12

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

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

解决方案:

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

在此处访问 mongodb 文档

代码的作用:

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

  2. 更新操作符:修改它作用的文档的$set匹配字段。

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

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

阵列过滤器

于 2020-03-14T14:45:40.943 回答
11

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

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

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

如果只有一份文件

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);
  });
于 2020-06-22T10:25:03.587 回答
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});

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

于 2013-06-02T13:54:17.323 回答
6

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

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

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

于 2019-06-19T05:29:22.033 回答
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});
}

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

于 2018-06-06T00:27:30.743 回答
2

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

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

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

于 2019-08-19T02:22:05.360 回答
0

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

保存功能的另一个问题是,会在多次保存时产生冲突数据。 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))
})
于 2017-07-29T18:35:11.130 回答
0

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

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

参考

于 2019-08-12T11:24:46.557 回答
0

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

使用 $pull 或 $push 和 update many 查询来更新 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."
            });
        }
    });
于 2019-10-15T07:19:11.880 回答
-1

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

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

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

于 2017-09-04T16:45:27.170 回答