MongoDB atomic “findOrCreate”: findOne, masukkan jika tidak ada, tetapi jangan perbarui

114

seperti judulnya, saya ingin melakukan pencarian (satu) untuk dokumen, dengan _id, dan jika tidak ada, sudah dibuat, lalu apakah ditemukan atau dibuat, apakah itu dikembalikan di callback.

Saya tidak ingin memperbaruinya jika ada, seperti yang telah saya baca findAndModify. Saya telah melihat banyak pertanyaan lain di Stackoverflow mengenai ini tetapi sekali lagi, tidak ingin memperbarui apa pun.

Saya tidak yakin apakah dengan membuat (tidak ada), BAHWA sebenarnya pembaruan yang dibicarakan semua orang, semuanya sangat membingungkan :(

Discipol
sumber

Jawaban:

192

Dimulai dengan MongoDB 2.4, tidak perlu lagi mengandalkan indeks unik (atau solusi lain) untuk findOrCreateoperasi serupa atom .

Hal ini berkat para $setOnInsertoperator yang baru untuk 2.4, yang memungkinkan Anda untuk menentukan update yang hanya harus terjadi saat memasukkan dokumen.

Ini, digabungkan dengan upsertopsi, berarti Anda dapat menggunakan findAndModifyuntuk mencapai findOrCreateoperasi seperti atom .

db.collection.findAndModify({
  query: { _id: "some potentially existing id" },
  update: {
    $setOnInsert: { foo: "bar" }
  },
  new: true,   // return new doc if one is upserted
  upsert: true // insert the document if it does not exist
})

Karena $setOnInserthanya mempengaruhi dokumen yang dimasukkan, jika dokumen yang ada ditemukan, tidak ada modifikasi yang akan terjadi. Jika tidak ada dokumen, itu akan meningkatkan satu dengan _id yang ditentukan, kemudian melakukan set penyisipan saja. Dalam kedua kasus tersebut, dokumen dikembalikan.

nomor 1311407
sumber
3
@Gank jika Anda menggunakan driver asli mongodb untuk node, sintaksnya akan lebih seperti collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Lihat dokumen
numbers1311407
7
Adakah cara untuk mengetahui apakah dokumen telah diperbarui atau disisipkan?
malapetaka
3
Jika Anda ingin memeriksa apakah query di atas ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) masukkan (upert) dokumen, Anda harus mempertimbangkan untuk menggunakan db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Ia mengembalikan {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}jika dokumen dimasukkan, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}jika dokumen sudah ada.
Константин Ван
8
tampaknya ditinggalkan dalam rasa findOneAndUpdate, findOneAndReplaceataufindOneAndDelete
Ravshan Samandarov
5
Namun, orang perlu berhati-hati di sini. Ini hanya berfungsi jika pemilih findAndModify / findOneAndUpdate / updateOne secara unik mengidentifikasi satu dokumen oleh _id. Jika tidak, upsert dipecah di server menjadi kueri dan pembaruan / sisipan. Pembaruan masih bersifat atomik. Tetapi kueri dan pembaruan bersama-sama tidak akan dijalankan secara atomik.
Anon
14

Versi Driver> 2

Menggunakan driver terbaru (> versi 2) , Anda akan menggunakan findOneAndUpdate karena findAndModifysudah tidak digunakan lagi. Metode baru mengambil 3 argumen, the filter, updateobjek (yang berisi properti default Anda, yang harus disisipkan untuk objek baru), dan di optionsmana Anda harus menentukan operasi upsert.

Menggunakan sintaks promise, terlihat seperti ini:

const result = await collection.findOneAndUpdate(
  { _id: new ObjectId(id) },
  {
    $setOnInsert: { foo: "bar" },
  },
  {
    returnOriginal: false,
    upsert: true,
  }
);

const newOrUpdatedDocument = result.value;
hansmaad
sumber
Terima kasih, returnOriginalharusnya returnNewDocumentsaya pikir - docs.mongodb.com/manual/reference/method/…
Dominic
Tidak masalah, driver node menerapkannya secara berbeda dan jawaban Anda benar
Dominic
6

Agak kotor, tapi Anda bisa memasukkannya saja.

Pastikan bahwa kunci memiliki indeks unik di atasnya (jika Anda menggunakan _id tidak apa-apa, itu sudah unik).

Dengan cara ini, jika elemen sudah ada, ia akan mengembalikan pengecualian yang bisa Anda tangkap.

Jika tidak ada, dokumen baru akan disisipkan.

Diperbarui: penjelasan rinci tentang teknik ini pada Dokumentasi MongoDB

Madarco
sumber
ok, itu ide yang bagus, tetapi untuk nilai yang sudah ada sebelumnya akan mengembalikan kesalahan tetapi BUKAN nilainya itu sendiri, bukan?
Discipol
3
Ini sebenarnya adalah salah satu solusi yang direkomendasikan untuk urutan operasi yang terisolasi (temukan kemudian buat jika tidak ditemukan, mungkin) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407
@Discipol jika Anda ingin melakukan serangkaian operasi atom, Anda harus mengunci Dokumen terlebih dahulu, kemudian memodifikasinya, dan pada akhirnya melepaskannya. Ini akan membutuhkan lebih banyak kueri, tetapi Anda dapat mengoptimalkan untuk skenario kasus terbaik dan hanya melakukan 1-2 kueri di sebagian besar waktu. lihat: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco
1

Inilah yang saya lakukan (driver Ruby MongoDB):

$db[:tags].update_one({:tag => 'flat'}, {'$set' => {:tag => 'earth' }}, { :upsert => true })}

Ini akan memperbaruinya jika ada, dan memasukkannya jika tidak.

Vidar
sumber