mongodb:存在しない場合は挿入


146

毎日、ドキュメントのストックを受け取ります(更新)。私がやりたいことは、まだ存在していない各アイテムを挿入することです。

  • また、それらを最初に挿入したときと、更新で最後に見たときも追跡したいと思います。
  • 重複したドキュメントが欲しいのですが。
  • 以前に保存したドキュメントを削除したくありませんが、更新には含まれていません。
  • レコードの95%(推定)は、毎日変更されていません。

Pythonドライバー(pymongo)を使用しています。

私が現在行っているのは(疑似コード)です。

for each document in update:
      existing_document = collection.find_one(document)
      if not existing_document:
           document['insertion_date'] = now
      else:
           document = existing_document
      document['last_update_date'] = now
      my_collection.save(document)

私の問題は、それが非常に遅いということです(10万レコード未満の場合は40分、更新には何百万ものレコードがあります)。これを行うために何かが組み込まれていると確信していますが、update()のドキュメントはmmmhhh ....少し簡潔です...(http://www.mongodb.org/display/DOCS/Updating

誰かがそれをより速く行う方法をアドバイスできますか?

回答:


153

"upsert"を実行したいようです。MongoDBには、これに対するサポートが組み込まれています。update()呼び出しに追加のパラメーターを渡します:{upsert:true}。例えば:

key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument

これは、if-find-else-updateブロックを完全に置き換えます。キーが存在しない場合は挿入され、存在する場合は更新されます。

前:

{"key":"value", "key2":"Ohai."}

後:

{"key":"value", "key2":"value2", "key3":"value3"}

書き込むデータを指定することもできます。

data = {"$set":{"key2":"value2"}}

これで、選択したドキュメントは「key2」の値のみを更新し、その他はすべてそのままにします。


5
これはほとんど私が欲しいものです!オブジェクトが既に存在する場合、insertion_dateフィールドを変更できないのはなぜですか。
LeMiz、

24
最初の挿入時にフィールドを設定するだけの例を挙げて、存在する場合は更新しないでください。@VanNguyen
Ali Shakiba 2012

7
あなたの答えの最初の部分は間違っていると思います。$ setを使用しない限り、coll.updateはデータを置き換えます。したがって、Afterは実際には{{key2 ':' value2 '、' key3 ':' value3 '}
James Blackburn

9
-1この答えは危険です。「key」の値で検索してから「key」を消去すると、その後再びそれを見つけることができなくなります。これは非常にまれなユースケースです。
Mark E. Haase

23
$ setOnInsert演算子を使用する必要があります!クエリが見つかった場合、Upsertはドキュメントを更新します。
YulCheney 2014

64

MongoDB 2.4以降では、$ setOnInsert(http://docs.mongodb.org/manual/reference/operator/setOnInsert/を使用できます。)を

upsertコマンドで$ setOnInsertを使用して 'insertion_date'を設定し、$ setを使用して 'last_update_date'を設定します。

疑似コードを実用的な例にするには:

now = datetime.utcnow()
for document in update:
    collection.update_one(
        {"_id": document["_id"]},
        {
            "$setOnInsert": {"insertion_date": now},
            "$set": {"last_update_date": now},
        },
        upsert=True,
    )

3
これは正しいです。$ setOnInsertを使用して、フィルターに一致するドキュメントを確認し、見つからない場合は何かを挿入できます。ただし、_idフィールドで$ setOnInsertを実行できないバグがあったことに注意してください。「_ idフィールドを変更できません」などと表示されます。これはバグであり、v2.5.4以降で修正されています。このメッセージまたは問題が表示された場合は、最新バージョンを入手してください。
キーレンジョンストーン

19

常に一意のインデックスを作成できます。これにより、MongoDBは競合する保存を拒否します。mongodbシェルを使用して次のことを行ったとします。

> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13})      # This works
> db.getCollection("test").insert({a:1, b:12, c:13})      # This fails
E11000 duplicate key error index: foo.test.$a_1  dup key: { : 1.0 }


6

1.更新を使用します。

上記のVan Nguyenの答えを参考にして、保存ではなく更新を使用します。これにより、アップサートオプションにアクセスできます。

:このメソッドは、見つかったときにドキュメント全体をオーバーライドします(ドキュメントから

var conditions = { name: 'borne' }   , update = { $inc: { visits: 1 }} , options = { multi: true };

Model.update(conditions, update, options, callback);

function callback (err, numAffected) {   // numAffected is the number of updated documents })

1.a. $ setを使用

ドキュメント全体を更新するのではなく、選択したドキュメントを更新する場合は、更新で$ setメソッドを使用できます。(再び、ドキュメントから)...したがって、設定したい場合...

var query = { name: 'borne' };  Model.update(query, ***{ name: 'jason borne' }***, options, callback)

として送信...

Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)

これは、誤ってすべてのドキュメントをで上書きするのを防ぐのに役立ちます{ name: 'jason borne' }


6

概要

  • レコードの既存のコレクションがあります。
  • 既存のレコードの更新を含むセットレコードがあります。
  • 一部のアップデートは実際には何もアップデートせず、すでに持っているものを複製します。
  • すべての更新には、すでに存在している同じフィールドが含まれていますが、値が異なる可能性があります。
  • レコードが最後に変更されたとき、実際に値が変更された場所を追跡したいとします。

PyMongoを想定していることに注意してください。選択した言語に合わせて変更してください。

手順:

  1. 重複したレコードを取得しないように、unique = trueのインデックスでコレクションを作成します。

  2. 入力レコードを反復処理して、15,000レコード程度のバッチを作成します。バッチ内の各レコードについて、挿入するデータで構成される辞書を作成します。各レコードが新しいレコードになると想定します。これらに「作成」および「更新」タイムスタンプを追加します。これを 'ContinueOnError'フラグ= trueを指定したバッチ挿入コマンドとして発行します。これにより、そこに重複するキーが存在する場合でも(それがあるように聞こえます)、他のすべての挿入が行われます。これは非常に速く起こります。一括挿入はロックです。毎秒15,000のパフォーマンスレベルを取得しました。ContinueOnErrorの詳細については、以下を参照してください。 http://docs.mongodb.org/manual/core/write-operations/を

    レコードの挿入は非常に高速に行われるため、これらの挿入はすぐに完了します。次に、関連するレコードを更新します。これは、一度に1つよりもはるかに高速なバッチ検索で行います。

  3. すべての入力レコードを繰り返し処理して、15K程度のバッチを作成します。キーを抽出します(キーが1つの場合に最適ですが、キーがない場合は手助けできません)。db.collectionNameBlah.find({field:{$ in:[1、2,3 ...})クエリを使用して、Mongoからこの一連のレコードを取得します。これらの各レコードについて、更新があるかどうかを確認し、ある場合は、「更新された」タイムスタンプの更新を含む更新を発行します。

    残念ながら、MongoDB 2.4以下には一括更新操作が含まれていないことに注意してください。彼らはそれに取り組んでいます。

重要な最適化ポイント:

  • 挿入により、操作が大幅に高速化されます。
  • レコードをまとめて取得すると、処理速度も向上します。
  • 現在、個別の更新のみが可能なルートですが、10Genが取り組んでいます。おそらく、2.6になると思いますが、それまでに完成するかどうかはわかりませんが、やるべきことがたくさんあります(私はJiraシステムに従っています)。

5

mongodbがこのタイプの選択的アップサーティングをサポートしているとは思いません。私はLeMizと同じ問題を抱えており、「作成」と「更新」の両方のタイムスタンプを処理する場合、update(criteria、newObj、upsert、multi)を使用しても正しく機能しません。次のupsertステートメントがあるとします。

update( { "name": "abc" }, 
        { $set: { "created": "2010-07-14 11:11:11", 
                  "updated": "2010-07-14 11:11:11" }},
        true, true ) 

シナリオ#1-「名前」が「abc」のドキュメントは存在しません:新しいドキュメントは「名前」=「abc」、「作成済み」= 2010-07-14 11:11:11、「更新済み」=で作成されます2010-07-14 11:11:11。

シナリオ#2-「名前」が「abc」のドキュメントは、次の名前ですでに存在します:「名前」=「abc」、「作成」= 2010-07-12 09:09:09、および「更新」= 2010-07 -13 10:10:10。アップサート後、ドキュメントはシナリオ#1の結果と同じになります。挿入する場合に設定するフィールド、および更新する場合にそのままにするフィールドをアップサートで指定する方法はありません。

私の解決策は、上の一意のインデックスを作成することでしたcriteraの、フィールドの挿入を実行し、その直後に単に「更新」フィールドに更新を実行します。


4

MongoDBでは、一般に、それがまだ存在しない場合にドキュメントを作成するだけなので、更新を使用する方が優れていますが、Pythonアダプターでどのように操作するかはわかりません。

次に、そのドキュメントが存在するかどうかのみを知る必要がある場合は、MongoDBからドキュメント全体を転送すると思われるfind_oneよりも、数値のみを返すcount()がより良いオプションとなり、不要なトラフィックが発生します。

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