Skip to content

Instantly share code, notes, and snippets.

@rantav
Created June 27, 2012 05:18
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rantav/3001646 to your computer and use it in GitHub Desktop.
Save rantav/3001646 to your computer and use it in GitHub Desktop.
MongoDB increment or insert

In mongodb it's easy to make at upsert, meaning update-or-insert by calling

db.collection.update({criteria}, {updated fields}, true)

The third parameter means - insert a new document if the document doesn't exist yet. So, for example, the following will insert a new document for the user if there's no document for that user yet, and will update it if it already exists:

 db.users.update({user_id: '1234'}, {user_id: '1234', name: 'Ran'}, true)

But What If - you want to - not insert a predefined value, but instead - increment a counter? You have a visit_count for each user, and you'd like to increment that count if there's already a document for that user's visit count, or insert 1 if it's the first time. Ideally, you'd like to do something like this:

db.users.update({user_id: '1234'}, {user_id: '1234', $inc: {visit_count: 1}}, true)

Unfortunately, however, mongo doesn't work like this. Mongo does not allow upserts in combination with $inc (or other atomic operations). Atomic operations are nice b/c you're guaranteed that the counter's (in this case counter) value will remain consistent in light of multiple concurrent updates. So how do you make an "increment if the document already exists, and insert if it doesn't"?

Here's the trick I found out yesterday (and to be fair, I'm pretty green to mongo, so maybe there's a better one). Please see the file mongo-insert-or-incr.js below.

var id = '1234';
db.users.insert({_id: id, visit_count: 1});
lastError = db.getLastError();
if (lastError) {
// document already exists, so just increment the count
db.users.update({_id: id}, {$inc: {visit_count: 1}})
}
@refaelos
Copy link

This way you're making 2 calls on any increment you want to do. Did you find a way to do this in one call ?

@rantav
Copy link
Author

rantav commented Nov 2, 2015

Here's an update to this (very old) gist.
With mongodb today you can this this easily with a single DB call.

Example:

db.users.update({_id: id}, {
  $inc: {visit_count: 1}, 
  $set: {_id: id}
}, {upsert: true})

@tagplus5
Copy link

tagplus5 commented Apr 3, 2017

@rantav, thanks. Works on mongo 3.4.

@akshaymendole
Copy link

@rantav Thanks. After browsing so much I finally got the answer here. This worked as expected

@QuaXiNova
Copy link

@rantav Thank you so much, you're a life saver. Works perfectly.

@Yitzkak
Copy link

Yitzkak commented Dec 31, 2017

@rantav thank you very well

@jtomasrl
Copy link

jtomasrl commented Feb 19, 2018

what if you have a document like this

{ '$inc': { totalHits: 1, 'hourly.2': 1 },
  '$setOnInsert': 
   { _id: '832bf0a3d3bece0c685e28461e3e8d88',
     trackingCode: '358CF2',
     dateTime: '2018-02-13 8',
     hitType: 'pageview',
     hourly: 
      { '0': 0,
        '1': 0,
        '2': 0,
        '3': 0
     } } }

where you want either update an hour value or insert an update. Is this possible using a single call?

@josephrocca
Copy link

josephrocca commented Aug 14, 2018

This seems to work fine for me with the node.js 3.x mongodb driver:

let op = { updateOne: { filter: {name:"foo"}, update: {$inc: {"counter":10}}, upsert:true } };
await db.collection('collection-name').bulkWrite([op], {ordered:false});

If a doc with name=foo doesn't exist it creates it and sets the counter to 10. If it already exists then it adds 10 to the counter.

@zsf3
Copy link

zsf3 commented Dec 19, 2018

How to handle these two use cases using bulkWrite or findOneAndModify?

  • if document does not exist then set counter to 20
  • if document exists then increment counter by 1

@pawan-saini
Copy link

its not working when we do multiple increment with negative value (-1.23121). it will subtract but also increase decimal value. In end you will value like 0e-14 . Any solution for this .

I am working on some kind of trading platform. So i have maintain balance of account of users. When trading happen, order are settle, at that time transactions are created very fast on same account.

So I am using $inc function to add or subtract amount from user accounts. I faced lots of issue because when i was subtracting fractional values e.g. 0.001 from 0.003 then $inc function create issue. i was not getting correct value.

Now my balance field data type is Decimal128. my previous issue was resolved but it convert value like this .e.g 1.45057300000000. if i keep subtracting amount from this balance. Mongodb convert it into
0E-14.

Anyone can help me. I want my amount maximum with 6 decimal and correct amount. if i have 0.00000001 this much balance in account then database will show me same value.

Is it possible??????

If someone help me. i will appreciate

@vasusrinivas
Copy link

@zsf3 : You can try setOnInsert with upsert to achieve that.

@PDS42
Copy link

PDS42 commented Oct 24, 2019

@zsf3 : You can try setOnInsert with upsert to achieve that.

Very helpful answer, thank you for that. Achieved exactly what I wanted

@nolanamy
Copy link

nolanamy commented Jan 20, 2020

Yeah MongoDB (I'm using 3.6) happily starts at 0 for you if the value doesn't exist:

db.items.drop()

db.items.update({ _id: 1 }, {
  $inc: { 'counters.a': 1 }
}, { upsert: true })

// upserts:
// { "_id" : 1, "counters" : { "a" : 1 } }

db.items.update({ _id: 1 }, {
  $inc: { 'counters.b': 1 }
}, { upsert: true })

// modifies:
// { "_id" : 1, "counters" : { "a" : 1, "b" : 1 } }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment