4

I've got a json doc like this in mongoDB:

 { "_id" : ObjectId("57ed88c0965bedd2b11d5727"),
   "refid" : 2,
   "votes" : [
     { "ip" : "127.0.2.1", "rating" : 5 },
     { "ip" : "127.0.3.1", "rating" : 2 },
     { "ip" : "127.59.83.210", "rating" : 50 },
     { "ip" : "127.56.26.191", "rating" : 5 },
     { "ip" : "127.59.83.210", "rating" : 5 },
     { "ip" : "127.59.83.210", "rating" : 30 }
    ]
 }

And instead of creating dupes per IP, I want to update the record found if the IP exists, or add a new entry if it doesn't. There are tons of questions on how to do similar operations, but none that I could find exactly asking something this simple. I am trying this in Node.js:

db.collection('ratings').update( { "refid":refid, "votes.ip":ip },
  {
    $push: { votes: { "ip":ip, "rating":rating }
  }
})

But it just keeps adding new entries when I vote from the same IP.

NOTE: I am not using Mongoose.

I apologize if my question is overly elementary, I'm pretty new to MongoDB. I'm just wondering if there's a good way to do this baked in rather than iterating through every record in "votes" programmatically. Thanks!

EDIT: What I'm asking isn't currently possible, I've marked an answer below correct which contains the explanation and a link to a ticket at MongoDB for an enhancement.

As for the scenario in my question here, the code below works for me. I have no idea how good or bad it is performance wise, but it covers all of these scenarios:

  • If the record doesn't exist at all, create it
  • If the record exists, look for the IP and if found, update the rating
  • If the record exists but the IP does not, insert a new rating

Works:

db.collection('ratings').findOne({ "refid":refid }).then(function(vote) {  
  if (vote == null) {
    newrating = {refid: refid, votes: [{ ip: ip, rating: rating }]};
    db.collection('ratings').save(newrating);
  } else {
    db.collection('ratings').findOne({ "refid":refid, "votes.ip":ip }).then(function(rate) {
      if (rate != null) {
        db.collection('ratings').update(
          { refid: refid, "votes.ip" : ip },
          { $set: { "votes.$.rating" : rating } }
        )
      } else {
        db.collection('ratings').update({"refid":refid,"votes.ip" : {$ne : ip }},
          { $push: { votes: { "ip" : ip, "rating" : rating, }}}
        )
      }
    })
  }
})
Nick Schroeder
  • 1,340
  • 1
  • 13
  • 17
  • Looks like a duplicate to this: http://stackoverflow.com/questions/14527980/can-you-specify-a-key-for-addtoset-in-mongo – a0viedo Sep 29 '16 at 23:28
  • As far as I can see, both the link and the subsequent question in the comments don't cover my scenario; only how to prevent an update based on key, and separately, how to add a record if the key is not present. It doesn't cover the scenario of: if present update else add. – Nick Schroeder Sep 30 '16 at 01:20

5 Answers5

4

To insert a document if not exist is done by upsert and if you want to update a conditional embedded document you need $ positional operator. So you need to use both in query to implement above functionality.

But right now mongodb don't support upserting with $ positional operator

Do not use the positional operator $ with upsert operations because, inserts will use the $ as a field name in the inserted document.

So what you want is not possible to do it in one query for now, alternatively you can do it in two queries.

First

db.collection('ratings').update(
  {"refid":refid, "votes.ip": ip},
  {
     $set: { "votes.$.rating":rating }
  }
)

It returns the number of documents updated, if it is 1 it's fine, and if it is 0 you need to push new record.

db.collection('ratings').update( { "refid":refid, "votes.ip":{$ne: ip}},
    {$push: { votes: { "ip":ip , "rating":rating  }}
})

There also jira ticket for positional operator and upserting, plz vote for this issue if you want this functionality in mongodb. Below is the link of issue

https://jira.mongodb.org/browse/SERVER-3326

(EDIT: The jira ticket was closed with Won't Do in June 2019)

nclskfm
  • 154
  • 1
  • 10
Puneet Singh
  • 3,477
  • 1
  • 26
  • 39
  • Thanks. Puneet, I am going to edit the original post with the code that worked best for me, but mark yours as the correct answer because it's a precise explanation why it's not possible. Thanks! – Nick Schroeder Sep 30 '16 at 13:41
0

Try this

db.collection('ratings')).update( { "refid":refid, "votes.ip":ip , "votes.rating":{$ne: rating }},
    {$push: { votes: { "ip":ip , "rating":rating  }}
})

it'll add new record if rating is not existent for given ip, and if record is exist for given ip and rating then no duplicate record added.

Vaibhav Patil
  • 2,603
  • 4
  • 14
  • 22
0

It might be solved this way as well. This is the one I used for my work.

Pull out the matching value and push the new value as below:

db.collection(‘ratings’).update({“refid”:refid},
         {$pull: {votes: {“ip": ip}}},
    {multi: true});

db.collection("ratings").update({“refid":refid},
     {$push:{‘votes’:{"ip":ip,"rating":rating}}})
Bikash
  • 41
  • 8
0

If the refid exists, this code should do the work

let bulk = await db.collection('ratings').initializeOrderedBulkOp();

//edit if exist
await bulk.find({ refid: refid, 'votes.ip': ip })
          .updateOne({ $set: {'votes.$.rating': rating} });
//add if doesn't exist
await bulk.find({ refid: refid, 'votes.ip': {'$ne': ip} })
          .updateOne({ $push: {votes: {ip: ip, rating: rating}} });

await bulk.execute();
-1

Maybe

db.collection('ratings').update( { "refid":refid, "ip":ip },
   {
      $addToSet: { votes: { "ip":ip, "rating":rating }
   }
})
programmerj
  • 1,634
  • 18
  • 29