MongoDB atomic "findOrCreate": findOne, insert if nonexistent, but do not update

node.jsMongodb

node.js Problem Overview


as the title says, I want to perform a find (one) for a document, by _id, and if doesn't exist, have it created, then whether it was found or was created, have it returned in the callback.

I don't want to update it if it exists, as I've read findAndModify does. I have seen many other questions on Stackoverflow regarding this but again, don't wish to update anything.

I am unsure if by creating (of not existing), THAT is actually the update everyone is talking about, it's all so confuzzling :(

node.js Solutions


Solution 1 - node.js

Beginning with MongoDB 2.4, it's no longer necessary to rely on a unique index (or any other workaround) for atomic findOrCreate like operations.

This is thanks to the $setOnInsert operator new to 2.4, which allows you to specify updates which should only happen when inserting documents.

This, combined with the upsert option, means you can use findAndModify to achieve an atomic findOrCreate-like operation.

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

As $setOnInsert only affects documents being inserted, if an existing document is found, no modification will occur. If no document exists, it will upsert one with the specified _id, then perform the insert only set. In both cases, the document is returned.

Solution 2 - node.js

Driver Versions > 2

Using the latest driver (> version 2), you'll use findOneAndUpdate as findAndModify was deprecated. The new method takes 3 arguments, the filter, the update object (which contains your default properties, that should be inserted for a new object), and options where you have to specify the upsert operation.

Using the promise syntax, it looks like this:

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

const newOrUpdatedDocument = result.value;

Solution 3 - node.js

Its a bit dirty, but you can just insert it.

Be sure that the key has a unique index on it (if you use the _id it's ok, it's already unique).

In this way if the element is already present it will return an exception that you can catch.

If it isn't present, the new document will be inserted.

Updated: a detailed explanation of this technique on the MongoDB Documentation

Solution 4 - node.js

Here's what I did (Ruby MongoDB driver):

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

It will update it if it exists, and insert it if it doesn't.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDiscipolView Question on Stackoverflow
Solution 1 - node.jsnumbers1311407View Answer on Stackoverflow
Solution 2 - node.jshansmaadView Answer on Stackoverflow
Solution 3 - node.jsMadarcoView Answer on Stackoverflow
Solution 4 - node.jsVidarView Answer on Stackoverflow