How to make mongo properly run my javascript script - mongodb

I'm trying to update my document named "things" running javascript script from mongo shell. When I explicitly say which field to update it works like a charm but I'm having problem when I'm trying to read field name from the array.
This is my code:
var continuousWithMissingValues = ["A2","A14"];
var categoricWithMissingValues = ["A1", "A4", "A5", "A6", "A7"];
for (var i = 0; i < continuousWithMissingValues.length; i++){
// db.things.update({A2: "?" },{$set: {A2: -1 }}, { multi : true }); this line is working properly
db.things.update({continuousWithMissingValues[i]: "?" },{$set: {continuousWithMissingValues[i]: -1 }}); //if I try to update while reading values from the array I'm getting error
print('Updated missing values for'+ continuousWithMissingValues[i]);
}
I have also tried using this line without any luck(this does not produce error but it doesn't update anything):
db.things.update({"continuousWithMissingValues.i": "?" },{$set: {"continuousWithMissingValues.i": -1 }});

The mongo shell doesn't support computed property names, so you have to build up your query and update objects in a few steps:
var query = {};
query[continuousWithMissingValues[i]] = "?";
var update = {$set: {}};
update.$set[continuousWithMissingValues[i]] = -1;
db.things.update(query, update);

Related

mongo forEach unable to update array in document

I'm trying write a mongo script to run in RoboMongo that will loop through all documents in a collection. Each document contains an array myArray. The documents look like this:
{
"name": "myApp",
"myArray": [
{ "env": "dev", "dbHost": "db2dev.local" },
{ "env": "prod", "dbHost": "db1prod.local" }
]
I want to copy the dbHost field that is defined in dev to prod. So the above result would be:
{
"name": "myApp",
"myArray": [
{ "env": "dev", "dbHost": "db2dev.local" },
{ "env": "prod", "dbHost": "db2dev.local" }
]
When I try to access the field myArray[0] I get a syntax error that says:
TypeError: myDoc.myArray[0] is undefined
The function is something like this:
db.myCollection.find().forEach( function(myDoc) {
var devIdx = 0;
var prodIdx = 1;
if (myDoc.myArray[0].env !== 'dev')}
devIdx = 1;
prodIdx = 0;
}
myDoc.myArray[prodIdx].dbHost = myDoc.myArray[devIdx].dbHost;
print(myDoc);
});
I've examined the collection (it is very small) and each document has a myArray field as it should with exactly two values (one for dev and one for prod) in the array.
What am I doing wrong? What is the correct syntax to use inside a mongo script? Is updating arrays in a document not supported?
Searching for solution
I've searched and found forEach examples but most are trivial and none include an array being accessed or changed.
The mongo docs are also very simplistic: https://docs.mongodb.com/v3.6/reference/method/cursor.forEach/
Mongo javascript does not allow you to access arrays directly like you are trying to do (unless you are in a for loop). So a solution is shown below:
db.myCollection.find({}).forEach( function(myDoc) {
var foundDevEntry = null;
var updatedProdEntry = false;
// First time loop to get a copy of the dev entry
for (var idx in myDoc.myArray) {
if (myDoc.myArray[idx].env === 'dev') {
foundDevEntry = myDoc.myArray[idx];
}
}
// 2nd time loop to update the value
for (var idx in myDoc.myArray) {
if (myDoc.myArray[idx].env === 'prod') {
myDoc.myArray[idx].dbHost = foundDevEntry.dbHost;
}
}
// Now update the database with this change
db.myCollection.update({_id: myDoc._id}, {$set: {"myArray": myDoc.myArray}});
print(myDoc); // So results are also returned when query is run
});
I've stripped out error checking to focus on the change required. What (to me) is odd is that the syntax myDoc.myArray[idx] is actually valid but only inside a loop!
The following references helped me come to a solution:
Update in forEach on mongodb shell
https://www.mysoftkey.com/mongodb/how-to-use-foreach-loop-in-mongodb-to-manipulate-document/
I should add that some solutions I read said that to update an array you had to re-build the array (https://stackoverflow.com/a/22657219/3281336). I did not do that in my solution and it did work but wanted to share it.

Mongo Document Increment Sequence is skipping numbers

I'm currently having issues when querying for one of my Documents inside a Database through Meteor.
Using this line of code I'm trying to retrieve the next sequence number out of the DB. But it sometimes skips numbers randomly for some reason.
var col = MyCounters.findOne(type);
MyCounters.update(col._id, {$inc: {seq: 1}});
return col.seq;
Not getting any kind of errors server side.
Does anybody know what the issue might be?
I'm on Meteor 1.4+
====================
Update
I also update another Collection with the new value obtained from MyCounters collection, so it would be something like this:
var col = MyCounters.findOne(type);
MyCounters.update(col._id, {$inc: {seq: 1}});
var barId = col.seq;
// declare barObject + onInsertError
barObject.barId = barId;
// ...
FooCollection.insert(barObject, onInsertError);
And FooCollection ends up having skipped sequence numbers up to 5000 sometimes.
If you want increament at that item Document, you can use :
var col = MyCounters.findOne(type);
var valueOne = 1;
var nameItem = 'seq';
var inc = {};
inc[ nameItem ] = valueOne;
MyCounters.update({ _id: col._id }, { '$inc': inc } )
But if you want increament value from all Document from Collections MyCounters ( maks seq + 1 ) you can use :
var count = MyCounters.findOne({}, {sort:{seq:-1}}).seq;
count = count + 1;
MyCounters.update({_id:col._id}, {$set:{seq:count}})
I hope it work for you. Thanks
refer to : https://stackoverflow.com/a/33968766/4287229

Invoke db.eval in FindAndModify using MongoDB C# Client

I have the following Document:
{
"_id": 100,
"Version": 1,
"Data": "Hello"
}
I have a function which return a number from a sequence:
function getNextSequence(name) {
var ret = db.Counter.findAndModify(
{
query: { _id: name },
update: { $inc: { value: 1 } },
new: true,
upsert: true
}
);
return ret.value;
}
I can use this for optimistic concurrency by performing the following Mongo command:
db.CollectionName.findAndModify({
query: { "_id" : NumberLong(100), "Version" : 1 },
update: { "$set" : {
"Data": "Here is new data!",
"Version" : db.eval('getNextSequence("CollectionName")') }
},
new: true
}
);
This will update the document (as the _id and Version) match, with the new Data field, and also the new number out of the eval call.
It also returns a modified document, from which I can retrieve the new Version value if I want to make another update later (in the same 'session').
My problem is:
You cannot create an Update document using the MongoDB C# client that will serialize to this command.
I used:
var update = Update.Combine(
new UpdateDocument("$set", doc),
Update.Set(versionMap.ElementName, new BsonJavaScript("db.eval('getNextSequence(\"Version:CollectionName\")')")))
);
If you use what I first expected to perform this task, BsonJavascript, you get the following document, which incorrectly sets Version to a string of javascript.
update: { "$set" : {
"Data": "Here is new data!",
"Version" : { "$code" : "db.eval('getNextSequence(\"Version:CollectionName\")')" }
}
}
How can I get MongoDB C# client to serialize an Update document with my db.eval function call in it?
I have tried to add a new BsonValue type in my assembly which I would serialize down to db.eval(''); However there is a BsonType enum which I cannot modify, without making a mod to MongoDB which I would not like to do incase of any issues with the change, compatibility etc.
I have also tried simply creating the Update document myself as a BsonDocument, however FindAndModify will only accept an IMongoUpdate interface which a simply a marker that at present I find superfluous.
I have just tried to construct the command manually by creating a BsonDocument myself to set the Value: db.eval, however I get the following exception:
A String value cannot be written to the root level of a BSON document.
I see no other way now than drop down to the Mongo stream level to accomplish this.
So I gave up with trying to get Mongo C# Client to do what I needed and instead wrote the following MongoDB function to do this for me:
db.system.js.save(
{
_id : "optimisticFindAndModify" ,
value : function optimisticFindAndModify(collectionName, operationArgs) {
var collection = db.getCollection(collectionName);
var ret = collection.findAndModify(operationArgs);
return ret;
}
}
);
This will get the collection to operate over, and execute the passed operationArgs in a FindAndModify operation.
Because I could not get the shell to set a literal value (ie, not a "quoted string") on a javascript object, I had to to this in my C# code:
var counterName = "Version:" + CollectionName;
var sequenceJs = string.Format("getNextSequence(\"{0}\")", counterName);
var doc = entity.ToBsonDocument();
doc.Remove("_id");
doc.Remove(versionMap.ElementName);
doc.Add(versionMap.ElementName, "SEQUENCEJS");
var findAndModifyDocument = new BsonDocument
{
{"query", query.ToBsonDocument()},
{"update", doc},
{"new", true},
{"fields", Fields.Include(versionMap.ElementName).ToBsonDocument() }
};
// We have to strip the quotes from getNextSequence.
var findAndModifyArgs = findAndModifyDocument.ToString();
findAndModifyArgs = findAndModifyArgs.Replace("\"SEQUENCEJS\"", sequenceJs);
var evalCommand = string.Format("db.eval('optimisticFindAndModify(\"{0}\", {1})');", CollectionName, findAndModifyArgs);
var modifiedDocument = Database.Eval(new EvalArgs
{
Code = new BsonJavaScript(evalCommand)
});
The result of this is that I can now call my Sequence Javascript, the getNextSequence function, inside the optimisticFindAndModify function.
Unforunately I had to use a string replace in C# as again there is no way of setting a BsonDocument to use the literal type db.eval necessary, although Mongo Shell likes it just fine.
All is now working.
EDIT:
Although, if you really want to push boundaries, and are actually awake, you will realize this same action can be accomplished by performing an $inc on the Version field.... and none of this is necessary....
However: If you want to follow along to the MongoDB tutorial on how they to say to implement concurrency, or you just want to use a function in a FindAndModify, this will help you. I know I'll probably refer back to it a few times in this project!

Get the actual object rather than DBQuery in mongo

I'm running this code in a mongodb console:
var participantsWithoutCategory = db.participant.find({eventId: ObjectId("536d5564e7b237df30b628cc"), category: {$exists: false}});
var event = db.event.find({_id: ObjectId("536556c4eaa237df30b628cc")});
participantsWithoutCategory.forEach(function (entry) {
var userId = new ObjectId("" + entry._id + "");
var user = db.user.find( { _id: userId} );
print("got user: " + user);
});
and the result of the prints is:
got user: DBQuery: Oc5mjdKkhyDb3r6rhnzw.user -> { "_id" : ObjectId("536d8586ebb237df30b62bcb") }
so I'm just wondering how to get the actual object rather than a pointer to it?
if I try to get any of its properties such as user.dob I get a null :(
Turns out that's how mongo says that the query had no results... I've fixed it by using the right property of the object and not the _id but that's beyond the point.
The interesting thing here is that mongo gives back the DBQuery when find() returns nothing.

MongoDB MapReduce: Not working as expected for more than 1000 records

I wrote a mapreduce function where the records are emitted in the following format
{userid:<xyz>, {event:adduser, count:1}}
{userid:<xyz>, {event:login, count:1}}
{userid:<xyz>, {event:login, count:1}}
{userid:<abc>, {event:adduser, count:1}}
where userid is the key and the remaining are the value for that key.
After the MapReduce function, I want to get the result in following format
{userid:<xyz>,{events: [{adduser:1},{login:2}], allEventCount:3}}
To acheive this I wrote the following reduce function
I know this can be achieved by group by.. both in aggregation framework and mapreduce, but we require a similar functionality for a complex scenario. So, I am taking this approach.
var reducefn = function(key,values){
var result = {allEventCount:0, events:[]};
values.forEach(function(value){
var notfound=true;
for(var n = 0; n < result.events.length; n++){
eventObj = result.events[n];
for(ev in eventObj){
if(ev==value.event){
result.events[n][ev] += value.allEventCount;
notfound=false;
break;
}
}
}
if(notfound==true){
var newEvent={}
newEvent[value.event]=1;
result.events.push(newEvent);
}
result.allEventCount += value.allEventCount;
});
return result;
}
This runs perfectly, when I run for 1000 records, when there are 3k or 10k records, the result I get is something like this
{ "_id" : {...}, "value" :{"allEventCount" :30, "events" :[ { "undefined" : 1},
{"adduser" : 1 }, {"remove" : 3 }, {"training" : 1 }, {"adminlogin" : 1 },
{"downgrade" : 2 } ]} }
Not able to understand where this undefined came from and also the sum of the individual events is less than allEventCount. All the docs in the collection has non-empty field event so there is no chance of undefined.
Mongo DB version -- 2.2.1
Environment -- Local machine, no sharding.
In the reduce function, why should this operation fail result.events[n][ev] += value.allEventCount; when the similar operation result.allEventCount += value.allEventCount; passes?
The corrected answer as suggested by johnyHK
Reduce function:
var reducefn = function(key,values){
var result = {totEvents:0, event:[]};
values.forEach(function(value){
value.event.forEach(function(eventElem){
var notfound=true;
for(var n = 0; n < result.event.length; n++){
eventObj = result.event[n];
for(ev in eventObj){
for(evv in eventElem){
if(ev==evv){
result.event[n][ev] += eventElem[evv];
notfound=false;
break;
}
}}
}
if(notfound==true){
result.event.push(eventElem);
}
});
result.totEvents += value.totEvents;
});
return result;
}
The shape of the object you emit from your map function must be the same as the object returned from your reduce function, as the results of a reduce can get fed back into reduce when processing large numbers of docs (like in this case).
So you need to change your emit to emit docs like this:
{userid:<xyz>, {events:[{adduser: 1}], allEventCount:1}}
{userid:<xyz>, {events:[{login: 1}], allEventCount:1}}
and then update your reduce function accordingly.