Py学习  »  MongoDB

Mongodb更新子文档字段的方法比逐个更新更好[重复]

Thomas Kim • 4 年前 • 3700 次点击  

我有一个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": "....."
        }
        ...
    ]
}

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

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

但是它只更新 第一 每个文档中匹配的数组元素。(这就是 $ - the positional operator .)

如何更新 全部的 匹配的数组元素?

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/50472
 
3700 次点击  
文章 [ 14 ]  |  最新文章 4 年前
cfs
Reply   •   1 楼
cfs    9 年前

我只是想添加另一个对我有效的解决方案,而且非常简单。这里只是一个标记(字符串)数组,因此要将名为“test”的标记更新为“changed”,只需执行以下操作:

myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
    myDocuments.update(
        {_id: doc._id, tags: "test"}, 
        {$set:{'tags.$': "changed"}});
    });
Irfan
Reply   •   2 楼
Irfan    4 年前

在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."
            });
        }
    });
Wenda Hu
Reply   •   3 楼
Wenda Hu    4 年前

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

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

上面的代码将“events”数组中的所有元素的“handled”更新为0,而不管其“profile”值如何。询问 {"events.profile":10} 只过滤整个文档,而不是数组中的文档。在这种情况下,必须使用 $[elem] 具有 arrayFilters 指定数组项的条件,以便尼尔·伦恩的答案是正确的。

Beyaz
Reply   •   4 楼
Beyaz    4 年前

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

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

Reference

user3176403
Reply   •   5 楼
user3176403    6 年前

实际上,save命令只在Document类的实例上执行。 有很多方法和属性。所以你可以用 精益() 减少工作负荷的功能。 请参阅此处。 https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

save函数的另一个问题是,它会使冲突数据同时进行多次保存。 模型.更新 将使数据一致。 以便更新文档数组中的多个项。使用您熟悉的编程语言并尝试类似的方法,我在其中使用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))
})
ersnh
Reply   •   6 楼
ersnh    4 年前

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

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

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

Pranay Saha
Reply   •   7 楼
Pranay Saha    6 年前

我试了下面的方法,效果很好。

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

//nodejs的回调函数

C0d3 0n3
Reply   •   8 楼
C0d3 0n3    5 年前

我一直在寻找一个解决方案,使用最新的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

UpTheCreek lukenofurther
Reply   •   9 楼
UpTheCreek lukenofurther    10 年前

我很惊讶蒙哥还没有解决这个问题。总的来说,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});

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

The Bearded Llama Blakes Seven
Reply   •   10 楼
The Bearded Llama Blakes Seven    6 年前

事实上,这与 http://jira.mongodb.org/browse/SERVER-1243 事实上,对于一个支持“所有情况”的清晰语法来说,如果发现多个数组匹配,则存在许多挑战。事实上,已经有一些方法可以“帮助”解决这个问题,比如 Bulk Operations 在这篇文章发表后已经实施了。

在一个update语句中仍然不可能更新多个匹配的数组元素,因此即使使用“multi”update,也只能更新该语句中每个文档的数组中的一个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() 如果数组有一个“唯一”标识符,或者每个元素的所有内容形成一个“唯一”元素本身,那么这个部分就可以工作。这是由于 $setDifference 用于过滤任何 false 从返回的值 $map 用于处理数组中匹配项的操作。

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

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版本支持聚合输出中的“游标”的情况下,这只是选择一种方法,并用显示的相同代码块迭代结果来处理批量更新语句的问题。批量操作和聚合输出中的“游标”是在同一版本(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 每个文档中的整个数组。处理起来可能更快,但不能保证阵列内容自读取并执行更新后没有更改。尽管 $集 仍然是一个原子运算符,它只会用它“认为”的正确数据更新数组,因此可能会覆盖读写之间发生的任何更改。

  2. 不计算要更新的索引值: 在类似于“一次射击”的方法中,你只需计算出那个位置 0 和位置 2 (依此类推)是用来更新和编码这些内容的元素,并最终声明如下:

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

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

因此,直到有一个合理的语法被确定为允许在单个更新语句中处理多个匹配的数组元素,则基本方法是在一个动态语句中更新每一个匹配的数组元素(理想地是批量的),或者基本上计算出最大的数组元素以更新或保持更新,直到没有更多。返回修改后的结果。无论如何,你应该“一直”在处理 positional $ 对匹配的数组元素进行更新,即使每个语句只更新一个元素。

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

sean
Reply   •   11 楼
sean    7 年前

这也可以通过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 而且所有其他文档都有较少的匹配子文档,循环将执行三次。

此方法避免了在执行此脚本时删除其他可能由其他进程更新的数据的危险。它还最小化了在客户机和服务器之间传输的数据量。

Neil Lunn
Reply   •   12 楼
Neil Lunn    4 年前

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

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

注意到 "multi" 正如在问题的上下文中所给出的,使用它是为了“更新多个元素”,但事实并非如此。这里的用法适用于 “多个文档” 一如既往,或者现在被指定为 .updateMany() 在现代的API版本中。

注意 有点讽刺,因为这是在 .update() 和方法一样,语法通常与所有最新版本的驱动程序版本兼容。

但是这不是真的 mongo shell,因为方法是在那里实现的(“讽刺的是为了向后兼容”),所以 arrayFilters 内部方法无法识别和删除参数,该方法分析选项以提供与以前的MongoDB服务器版本和“旧版”的“向后兼容性” .update() API调用语法。

所以如果你想在 蒙哥 shell或其他“基于shell”的产品(尤其是Robo 3T),您需要从3.6版或更高版本的开发分支或生产版本获得最新版本。

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

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

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

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

或更高版本,适用于您安装的版本。即 "4.0" 目前适用于版本4及更高版本。这就启用了新的位置更新操作符等功能。您还可以查看:

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

返回当前设置

Balthazar Daniel Cerecedo
Reply   •   13 楼
Balthazar Daniel Cerecedo    8 年前

对我有用的是:

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&friends的任何人来说,这一点都比较清楚。

Neil Lunn
Reply   •   14 楼
Neil Lunn    5 年前

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

作为一项工作,你可以:

  • 单独更新每个项目 (事件0.handled事件1.handled …)或。。。
  • 阅读文档,进行编辑 手动保存替换 旧的(检查 "Update if Current" 如果你想确保 原子更新)