MongoDB atómico "findOrCreate": findOne, insertar si no existe, pero no actualizar

114

como dice el título, quiero realizar una búsqueda (uno) para un documento, por _id, y si no existe, hacer que se cree, luego si se encontró o se creó, que se devuelva en la devolución de llamada.

No quiero actualizarlo si existe, como leí que hace findAndModify. He visto muchas otras preguntas en Stackoverflow con respecto a esto, pero nuevamente, no deseo actualizar nada.

No estoy seguro si al crear (de no existir), ESA es en realidad la actualización de la que todos están hablando, todo es tan confuso :(

Discipol
fuente

Respuestas:

192

A partir de MongoDB 2.4, ya no es necesario depender de un índice único (o cualquier otra solución alternativa) para findOrCreateoperaciones de tipo atómico .

Esto es gracias a la $setOnInsertoperadora nuevo a 2.4, lo que le permite especificar las actualizaciones, que sólo debe suceder al insertar documentos.

Esto, combinado con la upsertopción, significa que puede usarlo findAndModifypara lograr una findOrCreateoperación de tipo atómico .

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
})

Como $setOnInsertsolo afecta a los documentos que se insertan, si se encuentra un documento existente, no se producirá ninguna modificación. Si no existe ningún documento, insertará uno con el _id especificado y luego realizará el conjunto de solo inserción. En ambos casos se devuelve el documento.

números1311407
fuente
3
@Gank, si está utilizando el controlador nativo mongodb para el nodo, la sintaxis sería más parecida a collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Ver los documentos
números 1311407
7
¿Hay alguna forma de saber si el documento se ha actualizado o insertado?
condenación
3
Si desea verificar si la consulta anterior ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) insert (upsert) ed un documento, debería considerar usar db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Devuelve {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}si el documento se insertó, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}si el documento ya existe.
Константин Ван
8
parece estar desaprobado en sabor de findOneAndUpdate, findOneAndReplaceofindOneAndDelete
Ravshan Samandarov
5
Sin embargo, hay que tener cuidado aquí. Esto solo funciona si el selector de findAndModify / findOneAndUpdate / updateOne identifica un documento de forma única por _id. De lo contrario, la actualización se divide en el servidor en una consulta y una actualización / inserción. La actualización seguirá siendo atómica. Pero la consulta y la actualización juntas no se ejecutarán de forma atómica.
Anon
14

Versiones de controlador> 2

Con el controlador más reciente (> versión 2) , usará findOneAndUpdate ya que findAndModifyestaba en desuso. El nuevo método toma 3 argumentos, el filter, el updateobjeto (que contiene sus propiedades predeterminadas, que deben insertarse para un nuevo objeto), y optionsdonde debe especificar la operación upsert.

Usando la sintaxis de promesa, se ve así:

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

const newOrUpdatedDocument = result.value;
Hansmaad
fuente
Gracias, creo que returnOriginaldebería ser returnNewDocument- docs.mongodb.com/manual/reference/method/…
Dominic
No importa, el controlador de nodo lo implementó de manera diferente y su respuesta es correcta
Dominic
6

Está un poco sucio, pero puedes insertarlo.

Asegúrese de que la clave tenga un índice único (si usa el _id, está bien, ya es único).

De esta manera, si el elemento ya está presente, devolverá una excepción que puede detectar.

Si no está presente, se insertará el nuevo documento.

Actualizado: una explicación detallada de esta técnica en la documentación de MongoDB

Madarco
fuente
ok, esa es una buena idea, pero para el valor preexistente devolverá un error pero NO el valor en sí, ¿verdad?
Discipol
3
Esta es en realidad una de las soluciones recomendadas para secuencias aisladas de operaciones (busque y luego cree si no se encuentra, presumiblemente) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407
@Discipol si quieres hacer un conjunto de operaciones atómicas, primero debes bloquear el Documento, luego modificarlo y al final liberarlo. Esto requerirá más consultas, pero puede optimizar para el mejor de los casos y hacer solo 1-2 consultas la mayoría de las veces. ver: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco
1

Esto es lo que hice (controlador Ruby MongoDB):

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

Lo actualizará si existe y lo insertará si no existe.

Vidar
fuente