ChatGPT解决这个技术问题 Extra ChatGPT

如何更新一个 MongoDB 文档的 _id?

我想更新一个文档的 _id 字段。我知道这不是一个很好的做法。但由于某种技术原因,我需要更新它。

如果我尝试更新它,我会得到:

db.clients.update({ _id: ObjectId("123")}, { $set: { _id: ObjectId("456")}})

Performing an update on the path '_id' would modify the immutable field '_id'

并且更新被拒绝。我怎样才能更新它?


N
Niels van der Rest

你不能更新它。您必须使用新的 _id 保存文档,然后删除旧文档。

// store the document in a variable
doc = db.clients.findOne({_id: ObjectId("4cc45467c55f4d2d2a000002")})

// set a new _id on the document
doc._id = ObjectId("4c8a331bda76c559ef000004")

// insert the document, using the new _id
db.clients.insert(doc)

// remove the document with the old _id
db.clients.remove({_id: ObjectId("4cc45467c55f4d2d2a000002")})

如果该文档上的某些字段具有唯一索引,则会出现一个有趣的问题。在这种情况下,您的示例将失败,因为无法在唯一索引字段中插入具有重复值的文档。您可以通过先执行删除来解决此问题,但这是一个坏主意,因为如果您的插入由于某种原因失败,您的数据现在就会丢失。您必须改为删除索引,执行工作,然后恢复索引。
好点@skelly!我碰巧想到了类似的问题,并在 2 小时前看到了您的新评论。那么这种修改id的麻烦是否被认为是允许用户选择ID引起的内在问题?
如果您在 insert 行中获得 duplicate key error 并且不担心@skelly 提到的问题,最简单的解决方案是先调用 remove 行,然后调用 insert 行。 doc 应该已经打印在您的屏幕上,因此对于简单的文档,即使插入失败,在最坏的情况下也很容易恢复。
仅使用没有字符串作为参数的 ObjectId() 将生成一个新的唯一的。
@ShankhadeepGhoshal 是的,这是一种风险,尤其是当您针对实时生产系统执行此操作时。不幸的是,我认为您最好的选择是在此过程中进行预定的中断并停止所有编写器。另一个可能不那么痛苦的选择是暂时强制应用程序进入只读模式。将写入数据库的所有应用重新配置为仅指向辅助节点。在此期间读取将成功,但写入将失败,并且您的数据库将保持静态。
B
BrazaBR

要为整个集合执行此操作,您还可以使用循环(基于 Niels 示例):

db.status.find().forEach(function(doc){ 
    doc._id=doc.UserId; db.status_new.insert(doc);
});
db.status_new.renameCollection("status", true);

在这种情况下,UserId 是我想使用的新 ID


是否会建议 find 上的 snapshot() 以防止 forEach 在迭代时意外拾取更新的文档?
此代码段永远不会完成。它一直在不断地迭代集合。快照没有达到您的预期(您可以通过拍摄“快照”将文档添加到集合中来测试它,然后查看该新文档是否在快照中)
有关快照的替代方法,请参见 stackoverflow.com/a/28083980/305324list() 是合乎逻辑的,但对于大型数据库,这可能会耗尽内存
呃,这有 11 个赞成票,但有人说这是一个无限循环?这是怎么回事?
@Andrew,因为当代流行编码器文化规定,在实际验证所述输入是否确实有效之前,您应该始终确认良好的输入。
M
Mark

如果您想在同一个集合中重命名 _id(例如,如果您想为一些 _id 添加前缀):

db.someCollection.find().snapshot().forEach(function(doc) { 
   if (doc._id.indexOf("2019:") != 0) {
       print("Processing: " + doc._id);
       var oldDocId = doc._id;
       doc._id = "2019:" + doc._id; 
       db.someCollection.insert(doc);
       db.someCollection.remove({_id: oldDocId});
   }
});

if (doc._id.indexOf("2019:") != 0) {... 需要防止无限循环,因为 forEach 会选择插入的文档,即使使用了 .snapshot() 方法。


find(...).snapshot is not a function 但除此之外,很好的解决方案。此外,如果您想用您的自定义 ID 替换 _id,您可以检查 doc._id.toString().length === 24 以防止无限循环(假设您的自定义 ID 也不是 24 characters long),
F
Florent Arlandis

在这里,我有一个解决方案,可以避免多个请求、for 循环和旧文档删除。

您可以轻松地手动创建一个新想法,例如:_id:ObjectId() 但是如果知道 Mongo 会在缺少 _id 时自动分配,您可以使用聚合创建一个包含文档所有字段的 $project,但忽略字段 _id。然后您可以使用 $out 保存它

因此,如果您的文件是:

{
"_id":ObjectId("5b5ed345cfbce6787588e480"),
"title": "foo",
"description": "bar"
}

那么您的查询将是:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])

有趣的想法...但您通常希望将 _id 设置为给定值,而不是让 MongoDB 生成另一个值。
D
D. Schreier

您还可以从 MongoDB 指南针或使用命令创建新文档并设置所需的 specific _id 值。


D
Dharman

作为对上述答案的一个非常小的改进,我建议使用

let doc1 = {... doc};

然后

db.dyn_user_metricFormulaDefinitions.deleteOne({_id: doc._id});

这样我们就不需要创建额外的变量来保存旧的_id。


d
dododo

稍微修改了上面@Florent Arlandis 的示例,我们从文档中的不同字段插入_id:

 > db.coll.insertOne({ "_id": 1, "item": { "product": { "id": 11 } },   "source": "Good Store" })
 { "acknowledged" : true, "insertedId" : 1 }
 > db.coll.aggregate( [ { $set: { _id : "$item.product.id" }}, { $out: "coll" } ]) // inserting _id you want for the current collection
 > db.coll.find() // check that _id is changed
 { "_id" : 11, "item" : { "product" : { "id" : 11 } }, "source" : "Good Store" }

不要像@Florent Arlandis 的回答那样使用 $match 过滤器 + $out,因为 $out 在插入聚合结果之前会完全删除 collection 中的数据,因此您将有效地丢失所有与 $match 过滤器不匹配的数据