Mongo update based on array index - mongodb

I have a list of people in my database, where each person is one document. I would like to give them standard names (this is just for demo purposes – I don't care who gets which name so long as its unique).
I have an array of names to give them but I am not sure how to do this in Mongo. One simple way would be to just have a cursor and update the documents one by one, but that seems inefficient. Is there a better way to do this?
EDIT:
example db:
{id: 1234, age: 50}
{id: 1235, age: 40}
{id: 1236, age: 30}
Names:
['Bob', 'Jill', 'Gina']
Desired end state:
{id: 1234, age: 50, name: 'Bob'}
{id: 1235, age: 40, name: 'Jill'}
{id: 1236, age: 30, name: 'Gina'}

I'm not sure how you are specifying which names go with which _ids, so I'm going to assume you have a mapping specified in the following form:
> nameMapping
[{ "_id" : 1234, "name" : "Bob" }, { "_id" : 1235, "name" : "Jill" }, { "_id" : 1236, "name" : "Gina" }]
Then a good way to update every doc with a name is to use bulk operations. Bulk operations are supported in the drivers but I'll give the example in the mongo shell with bulk.find.update(), which is available as of MongoDB 2.6.
> var bulk = db.people.initializeUnorderedBulkOp()
> nameMapping.forEach(function(pair) {
bulk.find( { "_id" : pair._id } ).update( { $set: { "name" : pair.name } } )
})
> bulk.execute()

Related

How can i get the specific matched fields values in mongodb?

I have created one table below the table code.
use organization
db.test.insert({ bookname : "Mongodb", author : "Alex", price : 45, qty : 100})
db.test.insert({ bookname : "Cassandra", author : "John", price : 75, qty : 75})
Now I need the MongoDb and alex values when the book name is Mongodb
here we matched the bookname only remaining author name will display automaically?
What you looking is find with projection
Like this
db.test.find({ bookname: "mongodb" }, { _id: 0, bookname: 1, author: 1 })
Mongodb docs
https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/
To get one or more documents, you use -
db.test.find(<match>, <projection>)
where is the filtering part (where clause in SQL), and specifies the keys(columns in SQL tables) to return in results.
To filter the results on some field, use -
db.test.find({ "bookname" : "Mongodb" })
The above command will result all authors matching bookname as Mongodb. { "bookname" : "Mongodb" } is your match part of the command. You can modify your the find() command similar to -
db.test.find({ "bookname" : "Mongodb" }, {"bookname": 1, author: 1})
The output documents of this command will have bookname, author and _id as keys. {"bookname": 1, author: 1} is your projection part of command. For each key we want in output we set to 1 and 0 for those we don't want.
By default all keys will be in output. If you have set any key for 1 then only that key and _id will be returned. If you do not want _id then set it to 0 in projection.
db.test.find({ "bookname" : "Mongodb" }, {"bookname": 1, author: 1, _id: 0})

$elemMatch query in MongoDB

I have a collection 'name' with 2 documents of the structure :
doc 1:
{
a: 1
b : [{name:"AAA",age:10},
{name:"BBB",age:12},
{name:"CCC",age:13}]
}
doc 2 :
{
a: 2
b : [{name:"DDD",age:14},
{name:"EEE",age:15},
{name:"FFF",age:16}]
}
Since I am new to MongoDB, I am trying to find the difference of using the $elemMatch operator and not using it. Basically I am trying to query and find the first doc ( doc 1) with name AAA and age 10. So using the $elemMatch, my query looks like this :
db.name.find({b: {$elemMatch :{name:"AAA",age:10}}})
This query works perfectly fine, but my question is that what's the need to use this query when I can query like this :
db.name.find({b:{name:"AAA",age:10}})
I am sure there should be some reason for $elemMatch, just trying to find the difference. Thanks in advance for the reply !!!
The key difference is that the second query (without the $elemMatch) would only match elements in the b array that only contained those two fields, and only in that order.
So the first query would match the following two documents, but the second query wouldn't:
{
a: 1
b: [{name: "AAA", age: 10, city: 'New York'},
{name: "BBB", age: 12, city: 'Paris'},
{name: "CCC", age: 13, city: 'London'}]
}
{
a: 1,
b: [{age: 10, name: "AAA"},
{name: "BBB", age: 12},
{name: "CCC", age: 13}]
}
Another important difference is that how Mongo uses indexes.
If we have declared a multi-key-compound index:
db.name.createIndex({ "b.name": 1, "b.age": 1 })
And we execute this:
db.name.explain().find({
b: {
name: "DDD",
age: 14
}
})
we get:
"winningPlan" : {
"stage" : "COLLSCAN",
If we execute this:
db.name.explain().find({
b: {
$elemMatch: {
name: "DDD",
age: 14
}
}
})
we get:
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
But if we have a simple multi-key index in the array:
db.name.createIndex({b: 1})
The above index will be used in this query:
db.name.explain().find({
b: {
name: "DDD",
age: 14
}
})
And under my very un-professional profiling tests looks like this is faster.

MongoDB: Retrieving an entire array from a specific document

I have set up some test data in mongoDB that has the following form:
{
"_id" : ObjectId("579ab44c0f9f0dc3aeec42ab"),
"name" : "Bob",
"references" : [ 1, 2, 3, 4, 5, 6 ]
}
{
"_id" : ObjectId("579ab7a20f9f0dc3aeec42ac"),
"name" : "Jeff",
"references" : [ 11, 12, 13, 14, 15 ]
}
I want to be able to return the references array only for Bob. Currently I am able to return the complete Document for Bob with the following query:
db.test_2.find({"name" : "Bob"}, bob).pretty()
Basically the general question is how to return an array for a single document in a collection in MongoDB? If I could get any help for this that would be much appreciated!
You can add a projection document to limit the fields returned.
For example:
db.products.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 } )
Take a look at the documentation:
https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find
The other option would be to select the field from the given document (if you use it in a loop for example).
In any case mongo will return a json document which you need to take the array from.
Regards
Jony
You can do this...
db.test_2.findOne({ "name": "Bob" }).select({ references: 1, _id: 1 })
P.S this is with MongoDB v4.2
db.test_2.find({ "name": "Bob" }, { "references": 1 });

Increment nested value

I create players the following way.
Players.insert({
name: name,
score: 0,
items: [{'name': 0}, {'name2': 0}...]
});
How do I increment the score in a specific player and specific item name (upserting if necessary)?
Sorry for the terrible wording :p
Well, the answer is - as in life - to simplify the problem by breaking it up.
And to avoid arrays in mongoDB - after all, objects can have as many keys as you like. So, my structure became:
{
"_id": <id>,
"name": <name>,
"score": <score>,
"items": {}
}
And to increment the a dynamic key in items:
// create your update skeleton first
var ud = { $inc: {} };
// fill it in
ud.$inc['item.' + key] = value;
// call it
db.Players.update(player, ud, true);
Works a charm :)
Lets say you have:
{
"_id" : ObjectId("5465332e6c3e2eeb66ef3683"),
"name" : "Alex",
"score" : 0,
"items" : [
{
"food" : 0
}
]
}
To update you can do:
db.Players.update({name: "Alex", "items.food": {$exists : true}},
{$inc: {score: 1, "items.$.food": 5}})
Result:
{
"_id" : ObjectId("5465332e6c3e2eeb66ef3683"),
"name" : "Alex",
"score" : 1,
"items" : [
{
"food" : 5
}
]
}
I am not sure you can upsert if the document doesn't exist because of the positional operator needed to update the array.

MongoDB: Removing duplicate document based on ObjectId?

This is really an open question. I am sorry if this goes little vague but I am trying to collect thoughts from other people since I am very new to Mongo
Situation
I realized that my collection has multiple duplicate documents (based on name key)
These documents may be same or might got changed during the subsequent dumps from file(we want to keep later changes)
Since there is no insert date, it will be hard to tell looking at document which one is latest (bad schema design)
Wanted
To remove the documents which were inserted earlier
I read that each document in collection is assigned an ObjectId(here) that makes document unique
Question
Is it possible to know which document is inserted earlier based on ObjectId and remove it using Map Reduce?
Any other thoughts and advices?
I'm bored this evening, so here we go.
Step 1. Let's prepare our test data.
> db.users.insert({name: 'John', other_field: Math.random()})
> db.users.insert({name: 'Bob', other_field: Math.random()})
> db.users.insert({name: 'Mary', other_field: Math.random()})
> db.users.insert({name: 'John', other_field: Math.random()})
> db.users.insert({name: 'Jeff', other_field: Math.random()})
> db.users.insert({name: 'Ivan', other_field: Math.random()})
> db.users.insert({name: 'Mary', other_field: Math.random()})
> db.users.find()
{
"_id" : ObjectId("501976e9bee9b253265bba8b"),
"name" : "John",
"other_field" : 0.9884713875252772
}
{
"_id" : ObjectId("501976e9bee9b253265bba8c"),
"name" : "Bob",
"other_field" : 0.048004131996396415
}
{
"_id" : ObjectId("501976e9bee9b253265bba8d"),
"name" : "Mary",
"other_field" : 0.20415803582615222
}
{
"_id" : ObjectId("501976e9bee9b253265bba8e"),
"name" : "John",
"other_field" : 0.5514446987265585
}
{
"_id" : ObjectId("501976e9bee9b253265bba8f"),
"name" : "Jeff",
"other_field" : 0.8685077449753242
}
{
"_id" : ObjectId("501976e9bee9b253265bba90"),
"name" : "Ivan",
"other_field" : 0.2842514340422925
}
{
"_id" : ObjectId("501976eabee9b253265bba91"),
"name" : "Mary",
"other_field" : 0.984048520281136
}
Step 2. The map-reduce
var map = function() {
emit(this.name, this);
};
var reduce = function(name, vals) {
var last_obj = null;
vals.forEach(function(v) {
if(!last_obj || v._id > last_obj._id) {
last_obj = v;
}
});
return last_obj;
};
db.users.mapReduce(map, reduce, {out: 'temp_coll'})
It basically groups all documents by name and then selects the one with the largest _id.
Step 3. Do something with unique data.
> db.temp_coll.find()
{
"_id" : "Bob",
"value" : {
"_id" : ObjectId("501976e9bee9b253265bba8c"),
"name" : "Bob",
"other_field" : 0.048004131996396415
}
}
{
"_id" : "Ivan",
"value" : {
"_id" : ObjectId("501976e9bee9b253265bba90"),
"name" : "Ivan",
"other_field" : 0.2842514340422925
}
}
{
"_id" : "Jeff",
"value" : {
"_id" : ObjectId("501976e9bee9b253265bba8f"),
"name" : "Jeff",
"other_field" : 0.8685077449753242
}
}
{
"_id" : "John",
"value" : {
"_id" : ObjectId("501976e9bee9b253265bba8e"),
"name" : "John",
"other_field" : 0.5514446987265585
}
}
{
"_id" : "Mary",
"value" : {
"_id" : ObjectId("501976eabee9b253265bba91"),
"name" : "Mary",
"other_field" : 0.984048520281136
}
}
For example, drop the original collection, iterate this one and insert values into new collection. Don't forget to drop the temp collection when you're done.
Important
I didn't bother with extraction of a timestamp from objectid, because I assumed that you run your import jobs not twice a second (not even every second, maybe).
Ok since object id uses timestamp as it's leading four bytes you can do this with a bit of math.
Thankfully the mongo shell has a way to get the timestamp from an object id by you will need to do some more javascript to first query your documents with the same name then store them in a temp variable (if using the command line) or in a temp table (if using drivers) and parse each individual id's using the timestamp getter that's shown in the link below.
http://www.mongodb.org/display/DOCS/Optimizing+Object+IDs#OptimizingObjectIDs-Extractinsertiontimesfromidratherthanhavingaseparatetimestampfield.
Remember that object id's are only accurate to the second so this still doesn't help in rapid insertion mode.
But either way what you are asking for is doable either in a map reduce function or in the way shown above which does it through the command line.
Give that a shot and if you get stuck let me know. If i know your collection structure i can probably whip up something real quick but only after you bang your head on it a couple of times :)