1つのMongoDBドキュメントの_idをどのように更新しますか?


129

_id1つのドキュメントのフィールドを更新します。私はそれが本当に良い実践ではないことを知っています。しかし、技術的な理由により、更新する必要があります。それを更新しようとすると、次のようになります。

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

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

そして、更新は行われません。どうすれば更新できますか?

回答:


216

更新できません。新しいを使用してドキュメントを保存してから_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")})

29
そのドキュメントの一部のフィールドに一意のインデックスがある場合、これに関する楽しい問題が発生します。そのような状況では、一意のインデックス付きフィールドに重複する値を含むドキュメントを挿入できないため、この例は失敗します。最初に削除を行うことでこれを修正できますが、何らかの理由で挿入が失敗するとデータが失われるため、これは悪い考えです。代わりに、インデックスを削除し、作業を実行してから、インデックスを復元する必要があります。
skelly

良い点@skelly!私はたまたま同じような問題について考えていて、ちょうど2時間前に行われたあなたの新鮮なコメントを見ました。したがって、この変更IDの手間は、ユーザーがIDを選択できるようにすることによって引き起こされる本質的な問題と見なされますか?
RayLuo

1
回線にa duplicate key errorinsert表示され、@ skellyで言及された問題が心配されていない場合、最も簡単な解決策は、remove最初にinsert回線を呼び出してから、回線を呼び出すことです。doc簡単な文書のために、挿入は失敗しても、最悪のケースを回復するのは簡単だろうので、すでにお使いの画面に印刷されなければなりません。
philfreo 2015

パラメータとして文字列なしでObjectId()だけを使用すると、新しい一意のものが生成されます。
エリック

1
@ShankhadeepGhoshalええ、それは特にライブの本番システムに対してこれを実行している場合はリスクです。残念ながら、私はあなたの最善の選択肢は、計画された停止を取り、このプロセスの間すべてのライターを停止することだと思います。もう少し痛みが少ないかもしれない別のオプションは、アプリケーションを一時的に読み取り専用モードにすることです。セカンダリノードのみを指すようにDBに書き込むすべてのアプリを再構成します。この間、読み取りは成功しますが、書き込みは失敗し、DBは静的なままです。
スケリー

32

コレクション全体でそれを行うには、ループを使用することもできます(Nielsの例に基づく):

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

この場合、UserIdは、使用したい新しいIDでした。


1
findのsnapshot()は、繰り返し実行中にforEachが誤って新しいドキュメントを取得しないようにアドバイスされますか?
John Flinchbaugh、2014年

このコードスニペットは完了しません。コレクションを何度も繰り返し使用します。スナップショットが期待どおりに動作しない(コレクションにドキュメントを追加する「スナップショット」を撮り、その新しいドキュメントがスナップショットにあることを確認することでテストできます)
Patrick

スナップショットの代替方法については、stackoverflow.com / a / 28083980/305324を参照してください。list()論理的なものですが、大規模なデータベースの場合、これはメモリを使い果たす可能性があります
Patrick

4
ええと、これには11の賛成票がありますが、誰かが無限ループだと言っていますか?ここでの取引は何ですか?
アンドリュー

4
@Andrewは、現代のポップコーダーカルチャーにより、適切な入力が実際に機能することを実際に確認する前に、常に適切な入力を認めるべきであると規定しているためです。
csvan

5

同じコレクション内の_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の挿入ドキュメントを取り出すため、無限ループを防ぐために必要であってもthrought .snapshot()メソッド使用。


find(...).snapshot is not a functionそれ以外は素晴らしい解決策です。また、_idカスタムIDで置き換える場合doc._id.toString().length === 24は、無限ループを回避するかどうかを確認できます(カスタムIDも24文字ではないと想定)
Dan Dascalescu

1

ここでは、ループや古いドキュメントの削除のために、複数のリクエストを回避するソリューションがあります。

次のような方法で手動で新しいアイデアを簡単に作成できます。_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に別の値を生成させるのではなく、を特定の値に設定する必要があります。
Dan Dascalescu

0

MongoDBコンパスから、またはコマンドを使用して新しいドキュメントを作成し、必要なspecific _id値を設定することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.