Nick Schroeder Nick Schroeder - 1 year ago 93
Node.js Question

Update nested object in MongoDB if it exists, otherwise add it

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

{ "_id" : ObjectId("57ed88c0965bedd2b11d5727"),
"refid" : 2,
"votes" : [
{ "ip" : "", "rating" : 5 },
{ "ip" : "", "rating" : 2 },
{ "ip" : "", "rating" : 50 },
{ "ip" : "", "rating" : 5 },
{ "ip" : "", "rating" : 5 },
{ "ip" : "", "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


db.collection('ratings').findOne({ "refid":refid }).then(function(vote) {
if (vote == null) {
newrating = {refid: refid, votes: [{ ip: ip, rating: rating }]};
} else {
db.collection('ratings').findOne({ "refid":refid, "votes.ip":ip }).then(function(rate) {
if (rate != null) {
{ 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, }}}

Answer Source

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.


  {"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