MongoDB atomic “findOrCreate”: findOne, inserir se não existir, mas não atualizar

114

como o título diz, quero realizar um find (one) para um documento, por _id, e se não existir, fazer com que ele seja criado, então se ele foi encontrado ou criado, retorne no callback.

Não quero atualizá-lo se ele existir, como li que findAndModify faz. Tenho visto muitas outras perguntas no Stackoverflow sobre isso, mas, novamente, não desejo atualizar nada.

Não tenho certeza se ao criar (ou não existir), ESSA é na verdade a atualização de que todos estão falando, é tudo tão confuso :(

Discipol
fonte

Respostas:

192

A partir do MongoDB 2.4, não é mais necessário contar com um índice exclusivo (ou qualquer outra solução alternativa) para findOrCreateoperações do tipo atômico .

Isso é graças ao o $setOnInsertoperador de novo para 2.4, o que permite que você especifique as atualizações que só deve acontecer quando inserir documentos.

Isso, combinado com a upsertopção, significa que você pode usar findAndModifypara obter uma findOrCreateoperação semelhante a atômica .

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 $setOnInsertafeta apenas os documentos inseridos, se um documento existente for encontrado, nenhuma modificação ocorrerá. Se não houver nenhum documento, ele fará um upsert com o _id especificado e, em seguida, realizará o conjunto de inserção apenas. Em ambos os casos, o documento é devolvido.

números 1311407
fonte
3
@Gank se você estiver usando o driver nativo mongodb para o nó, a sintaxe seria mais semelhante collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Veja os documentos
números 1311407
7
Existe uma maneira de saber se o documento foi atualizado ou inserido?
condenação de
3
Se você quiser verificar se a consulta acima ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) inseriu (upsert) ed um documento, você deve considerar o uso db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Ele retorna {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}se o documento foi inserido, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}se o documento já existe.
Константин Ван
8
parece estar obsoleto no sabor de findOneAndUpdate, findOneAndReplaceoufindOneAndDelete
Ravshan Samandarov
5
É preciso ter cuidado aqui, no entanto. Isso funciona apenas se o seletor de findAndModify / findOneAndUpdate / updateOne identificar exclusivamente um documento por _id. Caso contrário, o upsert é dividido no servidor em uma consulta e uma atualização / inserção. A atualização ainda será atômica. Mas a consulta e a atualização juntas não serão executadas atomicamente.
Anon,
15

Versões do driver> 2

Usando o driver mais recente (> versão 2) , você usará findOneAndUpdate porque findAndModifyfoi descontinuado. O novo método leva 3 argumentos, o filter, o updateobjeto (que contém suas propriedades padrão, que deve ser inserido para um novo objeto) e optionsonde você deve especificar a operação de upsert.

Usando a sintaxe de promessa, fica assim:

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

const newOrUpdatedDocument = result.value;
Hansmaad
fonte
Obrigado, acho que returnOriginaldeveria ser returnNewDocument- docs.mongodb.com/manual/reference/method/…
Dominic
Esqueça, o driver do nó implementou de forma diferente e sua resposta está correta
Dominic
6

Está um pouco sujo, mas você pode simplesmente inseri-lo.

Certifique-se de que a chave possui um índice exclusivo (se você usar _id, está tudo bem, já é exclusivo).

Desta forma, se o elemento já estiver presente, ele retornará uma exceção que você pode capturar.

Se não estiver presente, o novo documento será inserido.

Atualizado: uma explicação detalhada desta técnica na documentação do MongoDB

Madarco
fonte
ok, é uma boa ideia, mas para o valor pré-existente ele retornará um erro, mas NÃO o valor em si, certo?
Discipol
3
Esta é, na verdade, uma das soluções recomendadas para sequências isoladas de operações (localizar e criar se não for encontrado, presumivelmente) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
números1311407
@Discipol se você quiser fazer um conjunto de operações atômicas, primeiro deve bloquear o documento, depois modificá-lo e, no final, liberá-lo. Isso exigirá mais consultas, mas você pode otimizar para o melhor cenário e fazer apenas 1-2 consultas na maioria das vezes. consulte: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco
1

Aqui está o que eu fiz (driver Ruby MongoDB):

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

Ele o atualizará se existir e o inserirá se não existir.

Vidar
fonte