How to iterate a mongo query through a for loop - mongodb

What I'm trying to achieve is the following which doesn't work:
var names = ["MATT", "GABE", "SAM"];
var students = [];
for (var i = 0; i < 3; i++) {
students[i] = Programs.find({ CampYear: 2016, 'Teachers.Week1.Sunday': names[i] }).fetch();
}
I would expect that it would return an array of student names for each iteration, but I keep getting an empty array when the array should have names.
If I remove the for loop and just do:
students[0] = Programs.find({ CampYear: 2016, 'Teachers.Week1.Sunday': listOfSundayTeacherNames[2] }).fetch();
It will return the student name(s) expected. Is a for-loop the right way to do this?

Figured it out. For whatever reason the for-loop won't work, but using a .map will.
var names = _.map(names, function(num){ return Programs.find({ CampYear: 2016), 'Teachers.Week1.Sunday': num }).fetch(); });

A much better approach would be to use the $in operator with your query:
var students = Programs.find({
'CampYear': 2016,
'Teachers.Week1.Sunday': { '$in': names }
}).fetch();

Related

Mongo - Replace references with embedded documents

I have a Collection with a nested attribute that is an array of ObjectId References. These refer to documents in another Collection.
I'd like to replace these references with the documents themselves, i.e. embed those documents where the references are now. I've tried with and without the .snapshot() option. This may be caused because I'm updating a document while in a loop on that doc, and .snapshot() isn't available at that level.
My mongo-fu is low and I'm stuck on a call stack error. How can I do this?
Example code:
db.CollWithReferences.find({}).snapshot().forEach( function(document) {
var doc_id = document._id;
document.GroupsOfStuff.forEach( function(Group) {
var docsToEmbed= db.CollOfThingsToEmbed.find({ _id: { $in: Group.ArrayOfReferenceObjectIds }});
db.CollWithReferences.update({"_id": ObjectId(doc_id) },
{$set: {"Group.ArrayOfReferenceObjectIds ":docsToEmbed}} )
});
});
Gives this error:
{
"message" : "Maximum call stack size exceeded",
"stack" : "RangeError: Maximum call stack size exceeded" +
....}
I figure this is happening for one of two reasons. Either you are running out of memory by executing two queries in a for loop, or the update operation is being executed before the find operation has finished.
Either way, it is not a good idea to execute too many queries in a for loop as it can lead to this type of error.
I can't be sure if this will fix your problem as I don't know how many documents are in your collections, but it may work if you first get all documents from the CollWithReferences collection, then all you need from the CollOfThingsToEmbed collection. Then build a map of an _id from the CollOfThingsToEmbed collection to the actual document that corresponds to that. You can then loop through each document you got from the CollWithReferences collection, and mutate the groupsOfStuff array by accessing each ArrayOfReferenceObjectIds array and setting the ObjectId to the value that you have in the map you already built up, which will be the whole document. Then just update that document by setting GroupsOfSuff to its mutated value.
The following JavaScript code will do this (it could be organised better to have no logic in the global scope etc.):
var references = db.CollWithReferences.find({});
function getReferenceIds(references) {
var referenceIds = [];
for (var i = 0; i < references.length; i++) {
var group = references[i].GroupsOfStuff;
for (let j = 0; j < group.ArrayOfReferenceObjectIds; j++) {
referenceIds.push(group.ArrayOfReferenceObjectIds[j]);
}
}
return referenceIds;
}
function buildIdMap(docs) {
var map = {};
for (var i = 0; i < docs.length; i++) {
map[docs[i]._id.toString()] = docs[i];
}
return map;
}
var referenceIds = getReferenceIds(references);
var docsToEmbed = db.CollOfThingsToEmbed.find({_id: {$in: referenceIds}});
var idMap = buildIdMap(docsToEmbed);
for (var i = 0; i < references.length; i++) {
var groups = references[i].GroupsOfStuff;
for (var j = 0; j < groups.length; j++) {
refs = groups[j].ArrayOfReferenceObjectIds;
refs.forEach(function(ref) {
ref = idMap[ref.toString()];
});
}
db.CollWithReferences.update({
_id: ObjectId(ref._id)
}, {
$set: {GroupsOfStuff: groups}
});
}
It would be better if it was possible to just do one bulk update, but as each document needs to be updated differently, this is not possible.

Meteor, ChartsJS and MongoDB

I want to iterate through the MongoDB collection to get the chart labels but I get TypeError: undefined is not an object (evaluating 'teams[i].name') here is my code:
var teams = Teams.find();
var teamNames = [10];
for(i = 0; i < 10; i++)
{
teamNames.push(teams[i].name);
}
var chart = new Chart(canvas, {
type: 'bar',
data: {
labels: [teamNames]
....
Anyone any suggestions? I am running out of ideas.
Thank you in advance.
You can do this
var teamNames = Teams.find().map(
function(team){
return team.name;
}
)
teams must have a length of less than 10 items. If teams is [{name: "first"}], then teams[1] will return undefined and you will get that error. You can use:
for (let i = 0; i < teams.length; i++)
to solve this problem.
You can also map over the array to get specific properties:
labels: teams.map(team => team.name),
In Meteor, the Collection .find() function returns a cursor that you can then use to perform operations on collection items. In your case, you are treating the cursor as if it were an array which is incorrect. There are a few different ways that you can approach this.
1) Use .forEach() to iterate over the cursor.
var teamNames = [];
Teams.find().forEach(function (e) {
teamNames.push(e.name);
});
2) Use .fetch() to return all matching documents in an array, then iterate over that.
var teams = Teams.find().fetch();
var teamNames = [];
for(i = 0; i < teams.length; i++) {
teamNames.push(teams[i].name);
}
3) Use .map() to iterate over the collection calling the callback on all items and returning an array.
var teamNames = Teams.find().forEach(function (e) {
return e.name;
});

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

meteor, using _.map when joining collections

Here is my helpers to diplay data from two collections
Template.Lirescategorie.helpers({
scategories: function () {
var cursor = Scategories.find();
var data = [];
cursor.forEach(function(somewhat) {
var categories = Categories.findOne({_id : somewhat.categorieID}, {categorie:1});
data.push({cat : categories.categorie, scat : somewhat.scategorie });
});
return data;
}
});
Here are my collections
categorie :
{
"_id": "LBKZQfZZSf4DRdeXo",
"categorie": "Citoyenneté"
}
scategorie
{
"_id": "cNHYpAEvC9ffjWkf5",
"categorieID": "LBKZQfZZSf4DRdeXo",
"scategorie": "Etat-Civil"
}
I'm pretty sure my helpers' code is not optimal. And i think by using _.map or something like that i can reduce the code.
Since i'm not really familiar to it, i'm looking for help about this.
Welcome to client-side joins. You can use .map() to get the list of categorieIDs and do the second find in one go with $in::
var cursor = Scategories.find();
var arrayOfCategorieIDs = cursor.map(function(s){return s.categorieId});
var categories = Categories.find({_id : {$in: arrayOfCategorieIDs}});
Then:
var sCategories = cursor.map(function(s){return s.scategorie}); // array of scategorie names
var categoryNames = categories.map(function(c){return s.categorie}); // array of categorie names
Then assemble these two arrays of strings into the array of objects you're looking for.
But there's a far simpler pattern for a client-side join: just iterate over one cursor and do the lookup into the related collection in a helper. For example:
html:
{{#each sCategories}}
{{sCategorie}}
{{categorie}}
{{/each}}
js:
Lirescategorie.helpers({
sCategories:(){
return Scategories.find();
},
categorie:(){
return Categories.findOne(this.categorieID).categorie;
}
});

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.