mongodb: masukkan jika tidak ada


146

Setiap hari, saya menerima stok dokumen (pembaruan). Yang ingin saya lakukan adalah memasukkan setiap item yang belum ada.

  • Saya juga ingin melacak saat pertama kali saya memasukkannya, dan terakhir kali saya melihatnya dalam pembaruan.
  • Saya tidak ingin memiliki dokumen duplikat.
  • Saya tidak ingin menghapus dokumen yang sebelumnya telah disimpan, tetapi tidak ada dalam pembaruan saya.
  • 95% (diperkirakan) dari catatan tidak dimodifikasi dari hari ke hari.

Saya menggunakan driver Python (pymongo).

Apa yang saya lakukan saat ini adalah (pseudo-code):

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)

Masalah saya adalah sangat lambat (40 menit untuk kurang dari 100.000 catatan, dan saya memiliki jutaan dari mereka dalam pembaruan). Saya cukup yakin ada sesuatu yang dibangun untuk melakukan ini, tetapi dokumen untuk pembaruan () adalah mmmhhh .... agak singkat .... ( http://www.mongodb.org/display/DOCS/Updating )

Adakah yang bisa menyarankan cara melakukannya dengan lebih cepat?

Jawaban:


153

Kedengarannya seperti Anda ingin melakukan "upert". MongoDB memiliki dukungan bawaan untuk ini. Berikan parameter ekstra ke pembaruan () panggilan Anda: {upsert: true}. Sebagai contoh:

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

Ini menggantikan blok if-find-else-update Anda sepenuhnya. Ini akan memasukkan jika kunci tidak ada dan akan memperbarui jika ada.

Sebelum:

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

Setelah:

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

Anda juga dapat menentukan data apa yang ingin Anda tulis:

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

Sekarang dokumen yang Anda pilih akan memperbarui nilai "key2" saja dan membiarkan semuanya tidak tersentuh.


5
Ini hampir seperti yang saya inginkan! Bagaimana saya bisa menyentuh bidang insertion_date jika objek sudah ada?
LeMiz

24
dapatkah Anda memberikan contoh pengaturan lapangan pada sisipan pertama dan jangan perbarui jika ada? @VanNguyen
Ali Shakiba

7
Bagian pertama dari jawaban Anda salah, saya kira. coll.update akan mengganti data kecuali Anda menggunakan $ set. Jadi Setelah sebenarnya akan menjadi: {'key2': 'value2', 'key3': 'value3'}
James Blackburn

9
-1 Jawaban ini berbahaya. Anda menemukan dengan nilai "kunci" dan kemudian Anda menghapus "kunci", sehingga selanjutnya Anda tidak akan dapat menemukannya lagi. Ini adalah kasus penggunaan yang sangat tidak mungkin.
Mark E. Haase

23
Anda harus menggunakan operator $ setOnInsert! Upsert bahkan akan memperbarui dokumen jika ditemukan kueri.
YulCheney

64

Pada MongoDB 2.4, Anda dapat menggunakan $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

Set 'insertion_date' menggunakan $ setOnInsert dan 'last_update_date' menggunakan $ set pada perintah upsert Anda.

Untuk mengubah kodesemu menjadi contoh yang berfungsi:

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
Ini benar, Anda dapat memeriksa dokumen yang cocok dengan filter, dan memasukkan sesuatu jika tidak ditemukan, dengan menggunakan $ setOnInsert. Perhatikan bahwa ada bug di mana Anda tidak dapat $ setOnInsert dengan bidang _id - ia akan mengatakan sesuatu seperti "tidak dapat memodifikasikan bidang _id". Ini adalah bug, diperbaiki di v2.5.4 atau ada tentang. Jika Anda melihat pesan atau masalah ini, dapatkan versi terbaru.
Kieren Johnstone

19

Anda selalu dapat membuat indeks unik, yang menyebabkan MongoDB menolak penyimpanan yang bertentangan. Pertimbangkan hal-hal berikut yang dilakukan dengan menggunakan shell 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. Gunakan Pembaruan.

Menggambar dari jawaban Van Nguyen di atas, gunakan pembaruan alih-alih menyimpan. Ini memberi Anda akses ke opsi yang aktif.

CATATAN : Metode ini mengesampingkan seluruh dokumen saat ditemukan ( Dari dokumen )

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. Gunakan $ set

Jika Anda ingin memperbarui pilihan dokumen, tetapi tidak semuanya, Anda dapat menggunakan metode $ set dengan pembaruan. (lagi, Dari dokumen ) ... Jadi, jika Anda ingin mengatur ...

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

Kirimkan sebagai ...

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

Ini membantu mencegah secara tidak sengaja menimpa semua dokumen Anda { name: 'jason borne' }.


6

Ringkasan

  • Anda memiliki koleksi catatan yang ada.
  • Anda memiliki kumpulan catatan yang berisi pembaruan untuk catatan yang ada.
  • Beberapa pembaruan tidak benar-benar memperbarui apa pun, mereka menduplikasi apa yang sudah Anda miliki.
  • Semua pembaruan berisi bidang yang sama yang sudah ada, mungkin saja nilainya berbeda.
  • Anda ingin melacak ketika catatan terakhir kali diubah, di mana nilai sebenarnya berubah.

Catatan, saya menduga PyMongo, ubah sesuai dengan bahasa pilihan Anda.

Instruksi:

  1. Buat koleksi dengan indeks dengan unique = true sehingga Anda tidak mendapatkan catatan duplikat.

  2. Iterate atas catatan input Anda, buat batch mereka 15.000 catatan atau lebih. Untuk setiap catatan dalam kumpulan, buat dikt yang terdiri dari data yang ingin Anda masukkan, anggap masing-masing akan menjadi catatan baru. Tambahkan cap waktu 'dibuat' dan 'diperbarui' ke ini. Terbitkan ini sebagai perintah penyisipan batch dengan flag 'LanjutkanOnError' = benar, sehingga penyisipan semua yang lain terjadi bahkan jika ada kunci duplikat di sana (yang sepertinya akan ada). INI AKAN TERJADI SANGAT CEPAT. Menyisipkan massal rock, saya mendapatkan level kinerja 15k / detik. Catatan lebih lanjut tentang ContinueOnError, lihat http://docs.mongodb.org/manual/core/write-operations/

    Rekaman sisipan terjadi SANGAT cepat, jadi Anda akan selesai dengan sisipan itu dalam waktu singkat. Sekarang, saatnya untuk memperbarui catatan yang relevan. Lakukan ini dengan pengambilan batch, jauh lebih cepat dari satu per satu.

  3. Ulangi semua catatan input Anda lagi, buat kumpulan 15K atau lebih. Ekstrak kunci (terbaik jika ada satu kunci, tetapi tidak dapat membantu jika tidak ada). Ambil kumpulan rekaman ini dari Mongo dengan permintaan db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Untuk setiap catatan ini, tentukan apakah ada pembaruan, dan jika demikian, keluarkan pembaruan, termasuk memperbarui cap waktu yang 'diperbarui'.

    Sayangnya, kita harus mencatat, MongoDB 2.4 dan di bawah ini TIDAK termasuk operasi pembaruan massal. Mereka sedang mengerjakannya.

Poin Optimalisasi Kunci:

  • Sisipan akan sangat mempercepat operasi Anda dalam jumlah besar.
  • Mengambil catatan secara massal akan mempercepat, juga.
  • Pembaruan individual adalah satu-satunya rute yang mungkin sekarang, tetapi 10Gen sedang mengerjakannya. Agaknya, ini akan di 2.6, meskipun saya tidak yakin apakah itu akan selesai saat itu, ada banyak hal yang harus dilakukan (saya sudah mengikuti sistem Jira mereka).

5

Saya tidak berpikir mongodb mendukung jenis uperting selektif ini. Saya memiliki masalah yang sama dengan LeMiz, dan menggunakan pembaruan (kriteria, newObj, upsert, multi) tidak berfungsi dengan baik ketika berhadapan dengan stempel waktu 'dibuat' dan 'diperbarui'. Diberikan pernyataan upert berikut:

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

Skenario # 1 - dokumen dengan 'nama' dari 'abc' tidak ada: Dokumen baru dibuat dengan 'nama' = 'abc', 'dibuat' = 2010-07-14 11:11:11, dan 'diperbarui' = 2010-07-14 11:11:11.

Skenario # 2 - dokumen dengan 'nama' dari 'abc' sudah ada dengan yang berikut ini: 'name' = 'abc', 'Created' = 2010-07-12 09:09:09, dan 'updated' = 2010-07 -13 10:10:10. Setelah upert, dokumen sekarang akan sama dengan hasil dalam skenario # 1. Tidak ada cara untuk menentukan dalam bidang mana yang disetel jika memasukkan, dan bidang mana yang dibiarkan sendiri jika diperbarui.

Solusi saya adalah membuat indeks unik pada bidang kritera , melakukan penyisipan, dan segera setelah itu melakukan pembaruan hanya di bidang 'diperbarui'.


4

Secara umum, menggunakan pembaruan lebih baik di MongoDB karena hanya akan membuat dokumen jika belum ada, meskipun saya tidak yakin bagaimana cara kerjanya dengan adaptor python Anda.

Kedua, jika Anda hanya perlu tahu apakah dokumen itu ada atau tidak, hitung () yang mengembalikan hanya angka akan menjadi pilihan yang lebih baik daripada find_one yang seharusnya mentransfer seluruh dokumen dari MongoDB Anda yang menyebabkan lalu lintas yang tidak perlu.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.