Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB Increment or Upsert

Tags:

mongodb

Given the following document structure:

{
         '_id': ObjectId("559943fcda2485a66285576e"),
         'user': '70gw3',
         'data': [
            {
              'date': ISODate("2015-06-29T00:00:00.0Z"),
              'clicks': 1,
            },
            {
              'date': ISODate("2015-06-30T00:00:00.0Z"),
              'clicks': 5,
            },
          ]
    }

How can I increment the clicks value of 2015-06-30 and increase if it doesn't exists [the whole document OR the specific date], make it equals to 1?

like image 259
Broshi Avatar asked Jul 05 '15 14:07

Broshi


Video Answer


2 Answers

Unfortunately it is not possible to achieve your goal within a single query. But instead you can do the following

var exists = db.yourCollection.find(
    {'_id': ObjectId("559943fcda2485a66285576e"), 'data.date': <your date>}
).count()

This query will return the number of documents which have the specified _id and an object with the specified date in the data array. As the _id field is unique, you will get at most one result. If such element does not exist in the data array, you will not get any result. Thus, you have the following cases:

  • exists == 1: There IS an element with the specified date
  • exists == 0: There IS NO element with the specified date

Then this can become your condition:

if (exists) {
    db.yourCollection.update({_id: <your id>, 'data.date': <your date>}, {$inc: {'data.$.clicks': 1}})
} else {
    if (db.runCommand({findAndModify: 'yourCollection', query: {_id: <your id>, 'data.date': {$ne: <your date>}}, update: {$push: {data: {date: <your date>, clicks: 1}}}}).value) {
         db.yourCollection.update({_id: <your id>, 'data.date': <your date>}, {$inc: {'data.$.clicks': 1}})
    }
}
like image 198
bagrat Avatar answered Jan 02 '23 20:01

bagrat


The correct answer is that while you need to perform two update operations in order to fulfil the logic there is no need to find and check data in a way.

You have two basic conditions

  1. If the element is there then update it
  2. If the element is not there then add it

The process is basically then two updates. One will succeed and the other will not. No need to check.

Probably the best way to implement this is using the "Bulk" operations API. This allows both update statements to be sent to the server at the same time, and the response it a single response so there are less trips. But the logic is the same even if you don't use this.

var bulk = db.collection.initializeOrderedBulkOp();

// Update the date where it matched
bulk.find({ "user": "70gw3", "data.date": new Date("2015-06-30") })
    .updateOne({ "$inc": { "data.$.clicks": 1 } });

// Push a new element where date does not match
bulk.find({ "user": "70gw3", "data.date": { "$ne": new Date("2015-06-30") } })
    .updateOne({ "$push": { "data": { "date": new Date("2015-06-30"), "clicks": 1 } }});

bulk.execute();

In short, out of two update requests sent for any document then only one of the operations will actually modify anything. This is because the conditions for testing "date" are the "inverse" of one another, in that you "update" where present, and "create" where the matching element is not present.

Both states cannot exist at the same time, as long as the "create" follows the "update" the logic is sound. So first try to update, and then try to "create" where no match is found.

like image 38
Blakes Seven Avatar answered Jan 02 '23 20:01

Blakes Seven