Bulk deleting documents from aggregate - mongodb

I am trying to use a bulk delete on the results of a mongoose aggregate query.
var bulk = Collection.collection.initializeUnorderedBulkOp();
var cursor = Collection.aggregate(query).cursor({batchSize: 1000}).exec();
cursor.each(function(error, doc){
if(doc){
console.log(doc);
bulk.find({_id : doc._id}).removeOne();
}
});
if(bulk.length > 0) {
bulk.execute(function(error){
if(error){
console.error(error);
callback(error);
}else{
console.log(bulk.length + " documents deleted");
callback(null);
}
});
} else {
console.log("no documents to delete");
callback(null);
}
This results in the "no documents to delete" being printed before the results of the aggregate in the each loop. Normally I would expect there to be a callback function for a database operation. I have tried adding a callback function to the params of exec, but the function never gets hit:
var cursor = Collection.aggregate(query).cursor({batchSize: 1000}).exec(function(error, result){
console.log(error);
console.log(result);
callback();
});

Listen to the data and end events on the cursor:
cursor.on( 'data', function( data ) {
bulk.find( { "_id" : data._id } ).removeOne();
});
cursor.on( 'end', function() {
if ( bulk.length === 0 ) {
callback();
} else {
bulk.execute(function (error) {
if (error) {
callback(error);
} else {
callback();
}
});
}
});

What version of Mongoose? There's an issue on github that might be relevant. So maybe try:
var stream = Model
.aggregate(pipeline)
.cursor({ batchSize: 1000 })
.exec().stream();
stream.on('data', function(doc) {
// ...
});

Related

How to properly add two conditions to mongodb find()?

Having trouble with finding data. I have to conditions:
1. Matching ID
2. Time range
It works fine when use find() only by id, but there is a polling error when I try to use both or only time.Here what I have so far:
findRange: function (dateRange, callback) {
ExpenseModel.find(
{telegramId: dateRange.telegramId,
time:{
$gt: ISODate(dateRange.from),
$lte: ISODate(dateRange.to)
}},
function (err, existingSequence) {
if (err) {
callback(err, null);
return;
}
if (existingSequence) {
callback(null, existingSequence);
} else {
console.log("not found")
callback(null, false);
}
}
);
}
You have to use $and property
findRange: function (dateRange, callback) {
ExpenseModel.find({$and : [
{ telegramId: dateRange.telegramId},
{ time:{
$gt: ISODate(dateRange.from),
$lte: ISODate(dateRange.to)
}
}
]
},
function (err, existingSequence) {
if (err) {
callback(err, null);
return;
}
if (existingSequence) {
callback(null, existingSequence);
} else {
console.log("not found")
callback(null, false);
}
}
);
}

Mongoose update only the values that have changed

I have a PUT route to update value. I am hitting this route from two places. One is sending information about details and one about completed. The problem is that mongoose is updating booth even though it gets value from only one.
So if I send information about completed that it is true and latter I hit this route with new details (that dont have completed value) it will update completed also to false. How do I update just the value that was changed?
router.put('/:id', (req, res) => {
Todo.findOne({_id:req.body.id}, (err, foundObject) => {
foundObject.details = req.body.details
foundObject.completed = req.body.completed
foundObject.save((e, updatedTodo) => {
if(err) {
res.status(400).send(e)
} else {
res.send(updatedTodo)
}
})
})
})
EDIT:
Thanks to Jackson hint I was managed to do it like this.
router.put('/:id', (req, res) => {
Todo.findOne({_id:req.body.id}, (err, foundObject) => {
if(req.body.details !== undefined) {
foundObject.details = req.body.details
}
if(req.body.completed !== undefined) {
foundObject.completed = req.body.completed
}
foundObject.save((e, updatedTodo) => {
if(err) {
res.status(400).send(e)
} else {
res.send(updatedTodo)
}
})
})
})
const updateQuery = {};
if (req.body.details) {
updateQuery.details = req.body.details
}
if (req.body.completed) {
updateQuery.completed = req.body.completed
}
//or
Todo.findOneAndUpdate({id: req.body.id}, updateQuery, {new: true}, (err, res) => {
if (err) {
} else {
}
})
//or
Todo.findOneAndUpdate({id: req.body.id}, {$set: updateQuery}, {new: true}, (err, res) => {
if (err) {
} else {
}
})
Had a function similar to this my approach was this
const _ = require('lodash');
router.put('/update/:id',(req,res, next)=>{
todo.findById({
_id: req.params.id
}).then(user => {
const obj = {
new: true
}
user = _.extend(user, obj);
user.save((error, result) => {
if (error) {
console.log("Status not Changed")
} else {
res.redirect('/')
}
})
}).catch(error => {
res.status(500);
})
};
Taking new : true as the value you updating
It gets kinda ugly as the fields to be updated get increased. Say 100 fields.
I would suggest using the following approach:
try {
const schemaProperties = Object.keys(Todo.schema.paths)
const requestKeys = Object.keys(req.body)
const requestValues = Object.values(req.body)
const updateQuery = {}
// constructing dynamic query
for (let i = 0; i < requestKeys.length; i++) {
// Only update valid fields according to Todo Schema
if ( schemaProperties.includes(requestKeys[i]) ){
updateQuery[requestKeys[i]] = requestValues[i]
}
}
const updatedObject = await TOdo.updateOne(
{ _id:req.params.idd},
{ $set: updateQuery }
);
res.json(updatedObject)
} catch (error) {
res.status(400).send({ message: error });
}

Mongoose update array of Object id's using Populate?

I am trying to populate my array of an object id's how can i do ??
Function
$scope.assignEmployees = function () {
var chkArray = [];
var companyName = $scope.selectedComapny.companyName;
var Indata = {chkvalue:chkArray,company_name:companyName};
$("#employee_name:checked").each(function() {
chkArray.push($(this).val());
});
$http({
method : 'PUT',
url : '/api/projects',
data : Indata
})
.success(function (data){
console.log(data);
});}
Mongoose api
Population code:-
Project.findOne({client : company_name})
.populate('assignedTo')
.exec(function(err, project) {
if (err) return;
while(i<employee_id.length){
project.assignedTo.push(employee_id[i]);
project.save(function(err) {
if (err) return;
})
i++;
}
});
This code is work but it insert value 4 times any idea guys.
You can use this code to push all elements of Array to an Array in mongoose.
Project.update(
{ client: company_name },
{ "$pushAll": { "assignedTo": employee_id } },
function (err, raw) {
if (err) return handleError(err);
console.log('The raw response from Mongo was ', raw);
}
);

With a mongoose .update query, can you refer to the fetched record in the update parameter?

Is it possible to something like this with Mongoose or even just Mongo?
Center.update({ghostBuster:{$exists}},{$set:{ectoplasm: this.exoplasm},$unset: {exoplasm:""}}, function(err, result){ })
I would like to update a number of records and move one field into another field, so if I could refer to that fetched record as I update it. In this case I'm making the ectoplasm field have the value of exoplasm
Is it possible to do this without defining a hook on the Mongoose Schema?
With mongo you can do this by iterating the cursor returned from the find query (using forEach() method) and update the collection within the loop. For example:
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc){
db.centers.update(
{ "_id": doc._id },
{
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
)
});
This "back and forth" to the server is going to cost in IO, so you would try to minimize it. Use bulkWrite() method (if using MongoDB version 3.2) to do the updates in bulk:
var ops = [];
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc) {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
}
});
if (ops.length === 1000) {
db.centers.bulkWrite(ops);
ops = [];
}
})
if (ops.length > 0) db.centers.bulkWrite(ops);
Or for MongoDB 2.6.x and 3.0.x releases use this version of Bulk operations:
var bulk = db.centers.initializeUnorderedBulkOp(),
counter = 0;
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc) {
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
});
if (counter % 1000 === 0) {
bulk.execute();
bulk = db.centers.initializeUnorderedBulkOp();
}
});
if (counter % 1000 !== 0 ) bulk.execute();
The Bulk operations API in both cases will help reduce the IO load on the server by sending the requests only once in every 1000 documents in the collection to process.
For the Mongoose equivalent, you can implement something like the following which uses Promises to handle the async nature of the bulk API in node.js.
In order to use the underlying bulk operations API, you should access it via the .collection property from the mongoose model. Before using the API, wait for mongoose to successfully connect to the db since Mongoose doesn't really support the "initializeOrderedBulkOp()" function yet, because it doesn't work with mongoose's internal buffering system.
var mongoose = require('mongoose'),
express = require('express'),
Promise = require('bluebird'),
Schema = mongoose.Schema;
function connect(uri, options){
return new Promise(function(resolve, reject){
mongoose.connect(uri, options, function(err){
if (err) return reject(err);
resolve(mongoose.connection);
});
});
}
var centerSchema = new Schema({
exoplasm: Number,
ghostBuster: Number,
time: Date
}, { collection: "centers" });
var Center = mongoose.model("Center", centerSchema);
/*
function bulkUpdate(Model){
return new Promise(function(resolve, reject){
var bulk = Model.collection.initializeUnorderedBulkOp(),
counter = 0;
Model.find({ "ghostBuster" : { "$exists": true } })
.lean().exec(function (err, docs) {
if (err) return reject(err);
docs.forEach(function (doc){
counter++;
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
});
if (counter % 500 == 0 ) {
bulk.execute(function(err, result) {
if (err) return reject(err);
bulk = Model.collection.initializeUnorderedBulkOp();
resolve(result);
});
}
});
if (counter % 500 != 0 ) {
bulkUpdateOps.execute(function(err, result) {
if (err) return reject(err);
resolve(result);
});
}
});
});
}
*/
function bulkUpdate(Model){
return new Promise(function(resolve, reject){
var ops = [],
collection = Model.collection;
Model.find({ "ghostBuster" : { "$exists": true } })
.lean().exec(function (err, docs) {
if (err) return reject(err);
docs.forEach(function (doc){
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
}
});
if (ops.length === 1000) {
collection.bulkWrite(ops, function(err, result) {
if (err) return reject(err);
ops = [];
resolve(result);
});
}
});
if (ops.length > 0) {
collection.bulkWrite(ops, function(err, result) {
if (err) return reject(err);
resolve(result);
});
}
});
});
}
connect('mongodb://localhost/test', {}).then(function(db){
bulkUpdate(Center).then(function(res){
console.log('Bulk update complete.', res);
}, function(err){
console.log('Bulk Error:', err);
db.close();
});
}, function(err){
console.log('DB Error:', err);
});

MEAN: Getting total value from mongodb

Im new to mean stack and Im using mongoskin to connect to mongodb..Im trying to get total value present in database
function getTotal() {
var deferred = Q.defer();
var dashboard = db.collection('dashboard');
db.collection('dashboard').find({"iscorrect" : ""}).count(),
function (err, doc) {
if (err){
deferred.reject(err);
} else{
deferred.resolve();
}
};
return deferred.promise;
}
my main controller has
function gettotal(req, res) {
userService.getTotal()
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
The following code does not return any value...Any help in getting total value is helpful
Because count() method is asynchronous and returns a promise, you can restructure your function as either using a callback function
function getTotal() {
var deferred = Q.defer();
db.collection('dashboard').count({"iscorrect" : ""}, function (err, result) {
if (err){
deferred.reject(err);
} else{
deferred.resolve(result);
}
});
return deferred.promise;
}
or since count() returns a Promise, just return it
function getTotal() {
// just return a Promise
return db.collection('dashboard').count({"iscorrect" : ""});
}
and in your controller:
function gettotal(req, res) {
userService.getTotal()
.then(function (count) {
res.status(200).json({ 'count': count });
})
.catch(function (err) {
res.status(400).send(err);
});
}