findOne use in mapreduce mongodb - mongodb

I want to use findOne in map reduce. What is wrong with my code? My error is:
Command 'mapreduce' failed: exception: map invoke failed: JS Error: TypeError: user has no properties nofile_b:3 (response: { "errmsg" : "exception: map invoke failed: JS Error: TypeError: user has no properties nofile_b:3", "code" : 9014, "ok" : 0.0 })
string map = #"
function() {
var movie = this;
var user = db.users.findOne({UserId : parseInt(movie.UserId)});
emit( movie.UserId, {Name:user.Name});
}";
string reduce = #"
function(key, values) {
var result =values;
return result;
}";
string finalize = #"
function(key, value){
return value;
}";
Under c# code
var collection = database.GetCollection("movies");
var options = new MapReduceOptionsBuilder();
options.SetFinalize(finalize);
options.SetOutput(MapReduceOutput.Inline);
var results = collection.MapReduce(map, reduce, options);
lbResultList.Items.Clear();
foreach (var result in results.GetResults())
{
lbResultList.Items.Add(result.ToJson());
}
I solve my problem map function change
function () {
var user = db.users.find({UserId:this.UserId});
var userName ='';
var userSurName ='';
user.forEach(function(u) {
userName = u.Name;
userSurName = u.SurName;
});
emit(
this._id,
{title: this.Title,category:this.Category,UserName: userName,UserSurName: userSurName}
);
}
I think it is not logical,like sub query this solve. What can I do this case?

It looks like you're trying to do an SQL JOIN. MapReduce is the wrong tool for that. You'll actually want to just break this into an aggregate operation to get an array of UserID's and a query operation to get the names of users:
Something like this in mongo shell:
var UserIDArray = [];
movieCollection.aggregate({$group: {_id: "$UserId"}}).forEach(function (v) {
UserIDArray.push(v._id);
});
UserIDNamePairs = userCollection.find({_id : {$in: UserIdArray}}, {_id: "$name"}).toArray();

Related

Not iterable when using find ObjectId

I'm trying to find a certain document in my mongodb then update the int value of it using find query, I'm using $in because I used an array to find each element inside it, but when I used ObjectId it gives me error:
bloodinventoryDocs is not iterable
Here is what I did
var mongoose = require('mongoose');
var id = mongoose.Types.ObjectId('5c014c999cc48c3b0057988b');
var newValue = 1;
var newBloodgroup = "A_positive";
var newGetbloodcomponent = "Whole Blood";
Bloodinventory.find({ blood_component : { $in : newGetbloodcomponent} , blood_group: { $in :newBloodgroup},chapter: { $in :id}}, function(err, bloodinventoryDocs) {
for(let bloodinventory of bloodinventoryDocs) {
bloodinventory.num_stock = bloodinventory.num_stock + newValue ;
bloodinventory.save(function(err) {
if (err) {
console.log(err);
} else {
console.log('success');
}
});
}
});
Just use chapter: { $in: [id] }

Get the original query object in Mongoose

I have a loop to perform multiple queries through Mongoose
"use strict";
var Mongoose = require("mongoose");
var User = Mongoose.model("User");
var Cache = {};
for (var index=0; index<usernames.length; index++) {
var query = {
username:usernames[index]
};
User.find(query).
exec(function(error,users){
//THIS IS A CALLBACK FUNCTION,
//HOW TO GET THE 'query' VARIABLE ABOVE?
//I WANT TO PUT THE RESULT INTO CACHE:
var username = users[0].username;
Cache[username] = users[0];
});
}
I need to know which result is of which query, in the callback function above.
It is for db query caching purpose. I can extract 'username' from 'users[0]', but when the array 'users' is empty, there's no such thing.
Put anonymous function inside your loop; and use .findOne() instead of .find() if you are only interested in the first user or if the username values are unique.
for (var index = 0; index < usernames.length; index++) {
(function () {
var query = {
username: usernames[index]
};
User.findOne(query).
exec(function (error, user) {
//use your query here
var username = user.username;
Cache[username] = user;
});
})()
}
However consider async for this kind of operations.

How should i update documents, each with different update data set, in mongodb collections

I have mongodb in which there is 3 huge collections say 'A', 'B' and 'C'
Each collection contains about 2 million documents.
There are certain properties for each of the document.
Each document need to be updated based on those values of certain properties, from which i can determine what should be the '$set' to that document.
currently i am using the same approach for each collection.
that to find all documents in batches. collection them in memory (which i think the culprit for the current approach), then one by one update them all.
For the first collection(that have similar data as in other collections), it takes 10 minutes to get completed. then the next two collections taking 2 hours approx to get the task done or mongodb client get crashed earlier.
There is something wrong and no desired in the current approach.
Model.collection.find({}).batchSize(BATCH).toArray(function(err, docs){
if(err || !docs || !docs.length)
return afterCompleteOneCollection(err);
var spec = function(index) {
if(index % 1000 === 0) console.log('at index : ' + index);
var toSet = { };
var toUnset = { };
var over = function(){
var afterOver = function(err){
if(err) return afterCompleteOneCollection(err);
if(index < docs.length - 1) spec(index+1);
else afterCompleteOneCollection(null);
};
var sb = Object.keys(toSet).length;
var ub = Object.keys(toUnset).length;
if(sb || ub) {
var all = {};
if(sb) all.$set = toSet;
if(ub) all.$unset = toUnset;
Model.collection.update({ _id : docs[index]._id }, all, {}, afterOver);
} else afterOver(null);
};
forEachOfDocument(docs[index], toSet, toUnset, over);
};
spec(0);
});
Is there any better solution for the same.?
The streaming approach from here http://mongodb.github.io/node-mongodb-native/api-generated/cursor.html#stream worked for me
This is what i am doing :
var stream = Model.collection.find().stream();
stream.on('data', function(data){
if(data){
var toSet = { };
var toUnset = { };
var over = function(){
var afterOver = function(err){
if(err) console.log(err);
};
var sb = Object.keys(toSet).length;
var ub = Object.keys(toUnset).length;
if(sb || ub) {
var all = {};
if(sb) all.$set = toSet;
if(ub) all.$unset = toUnset;
Model.collection.update({ _id : data._id }, all, {}, afterOver);
} else afterOver(null);
};
forEachOfDocument(data, toSet, toUnset, over);
}
});
stream.on('close', function() {
afterCompleteOneCollection();
});

in mongo need to join two collections using Identity columns and emit the needed columns from both collections

I have book and author collection.in this name and works_written are the same value column respectively.so i tried the following script but it emit only first map values,second map values not emitted.
book = function() {
emit(this.id, {name: this.name,editions:this.editions});
}
author = function() {
emit(this.id, {name:this.name,works_written: this.works_writtten,});
}
r_b = function(k, values) {
var result = {};
values.forEach(function(value) {
var name;
for (name in value) {
if (value.hasOwnProperty(name)) {
result[name] = value[name];
}
}
});
return result;
};
r_a = function(k, values) {
var result = {};
values.forEach(function(value) {
var works_written;
for (works_written in value) {
if (value.hasOwnProperty(works_written)) {
result[works_written] = value[works_written];
}
}
});
return result;
};
res = db.book.mapReduce(book, r_ja, {out: {reduce: 'joined'}})
res = db.author.mapReduce(author, r_jp, {out: {reduce: 'joined'}})
can someone help me out?
From looking at your code, it seems like you have two collections, "book" and "author". Each book is structured as
{
id: <some id>,
name: <some name>,
editions: <comma-separated string of editions>
}
and each author is structured as
{
id: <some id>,
name: <some name>,
works_written: <comma-separated string of works written>
}
It would be more reasonable to store both works_written and editions as arrays rather than comma-separated lists each packed into an individual string. This would make iterating over the array possible.
Additionally, do you have multiple documents for each author and each book? If not, you do not need a mapreduce to do what you are attempting to do - a simple find() should work.
In case I have misinterpreted, what exactly are you attempting to do?

MongoDB/Mongoose: Can't put simplest MapReduce to work

Hello all I'm trying to do is to get the count of each distinct departmentType:
fnMap = function() {
emit(this.departments.departmentType, {typeCount:1} );
}
fnReduce = function(key, values) {
var result = {typeCount: 0};
values.forEach(function(value) {
result.typeCount += value.brandCount;
});
return result;
};
var command = {
mapreduce : "clients",
query : {"departments.departmentType": {$exists: true}},
map : fnMap.toString(),
reduce : fnReduce.toString(),
//sort: {"departments.departmentType":1},
out: {inline: 1}
};
mongoose.connection.db.executeDbCommand(command, function(err, dbres) {
});
When executing the command, dbres.documents[0].results only contains 1 item with the total number of departmentTypes, instead of several items one for each departmentType with its count.
Any ideas what am I doing wrong?
Also, when I uncomment the SORT line, I get error "db assertion failure: could not create cursor over...", I believe the field name is written correctly.
Mongoose v3 has now a Model.mapreduce() function (see doc).
The full example shown is:
var o = {};
o.map = function () { emit(this.name, 1) }
o.reduce = function (k, vals) { return vals.length }
o.out = { replace: 'createdCollectionNameForResults' }
o.verbose = true;
User.mapReduce(o, function (err, model, stats) {
console.log('map reduce took %d ms', stats.processtime)
model.find().where('value').gt(10).exec(function (err, docs) {
console.log(docs);
});
})
The problem with count i believe is because in your fnReduce() function you are summit the results instead of displaying them in an array.
You can use:
db.clients.distinct("departments.departmentType")
That will give an array with all the distinct departmentType values.
There were two problems in your map/reduce. One is brandCount in reduce rather than typeCount. But more importantly, you are trying to emit once per document, when you need to emit once per department array element. Corrected (and slightly simplified) code:
> fnMap = function () {
this.departments.forEach(
function (d) {
emit(d.departmentType, 1);
}
);
}
> fnReduce = function (key, values) {
var result = 0;
values.forEach(
function (value) {result += value;});
return result;
}