umbraphile
umbraphile

Reputation: 377

MongoDB: insert record if it doesn't exist, ignore if it does

I have a series of MongoDB records like:

{"name":"Bob", "gpa":4} {"name":"Sarah", "gpa":3}

I will encounter various additional records that may or may not pertain to the same people. I want to accept them if they're new people, and not update them if they're people we've seen before. So if I get {"name":"Jim", "gpa":2}, I want to accept that as-is. If I get {"name":"Sarah", "gpa":4} (different GPA value), I want to just ignore it. This doesn't seem to be the logic of either update with "upsert" set to yes, or of findAndModify with "upsert."

https://stackoverflow.com/a/3260290/514757 suggests a way (unique index on the first field, insert record, immediately update the second field) but it's a few years old and I wonder if there isn't a better way now to do this in a single step.

EDIT:

The accepted answer seems great, but it's not working for me! First, I created the index (this is with the Java driver):

nameIndex.put("name", 1);
nameIndex.put("unique", true);
queue.ensureIndex(nameIndex);

I can see from the command line that the index exists and is unique. Then, an item is inserted:

DBObject person = new BasicDBObject();
person.put("name", "Bob");
person.put("score", 200000);
queue.insert(person);

Later, the item with the highest score has its score reduced to zero:

BasicDBObject reset = new BasicDBObject();
reset.put("$set", new BasicDBObject("score", 0));
DBObject dbObj = queue.findAndModify(null, new BasicDBObject("score", -1), reset);

All of this works just as intended! But, later, the same name may be found again, with a new score. When this last bit of code runs, a new item is created with a different score, which is exactly what I don't want:

BasicDBObject queueable = new BasicDBObject();
queueable.put("name", "Could be Bob again, could be someone new");
queueable.put("score", 1234);
queue.insert(queueable);

If I search for Bob, I find:

{ "_id" : ObjectId("4f5e865ad6d09315326ea0f0"), "score" : 0, "name" : "Bob" }
{ "_id" : ObjectId("4f5e8691d6d09315326ea190"), "name" : "Bob", "score" : 886 }

A second record has been created, with the higher score again. Does the order of the fields matter? It doesn't seem like it should, and I don't know how to control that.

Upvotes: 7

Views: 13168

Answers (2)

Trip Volpe
Trip Volpe

Reputation: 210

Your ensureIndex() call is not quite right. The first argument to ensureIndex() contains the keys that should be indexed; index options (like uniqueness) can be passed as the second argument. See the Java driver docs.

What you've got right now is attempting to create a non-unique index on two fields: "name" and "unique".

Try this to create your index:

DBObject nameIndex = new BasicDBObject();
nameIndex.put("name",1);

DBObject nameIndexOptions = new BasicDBObject();
nameIndexOptions.put("unique", true);

queue.ensureIndex(nameIndex, nameIndexOptions);

Upvotes: 6

Fapiko
Fapiko

Reputation: 120

The best way to accomplish this would be to set a unique index on the name field using:

db.foo.ensureIndex({name:1}, {unique:true});

This will cause Sarah to not get updated when you make an insert call because it will find another record where 'Sarah' is already set for the name field.

Upvotes: 7

Related Questions