Mongoose loop through findOneAndUpdate condition statement - mongodb

I want to know if this part of code can be written differently, only with Mongoose helper methods of models ? Can I return a success and error if no stock are greater then 0 ?
ProductSchema.statics.substractStock = function (products) {
_.map(products, updateStock)
function updateStock(o) {
mongoose.model('Product').findById(o._id, function (err, product) {
return product
}).then(function(productDB){
if(productDB.stock > o.stock && productDB.stock > 0){
mongoose.model('Product').findOneAndUpdate(o._id, {$inc: {stock: -(o.stock)}}, {},
function (err, doc) {
//return success ??
}
);
} else {
//return 'no update'
}
});
}
};

This could be done with an atomic update where you can ditch the initial findById() call and include the comparison logic
if (productDB.stock > o.stock && productDB.stock > 0) { ... }
within the query as in the following:
function updateStock(o) {
mongoose.model('Product').findOneAndUpdate(
{
"_id": o._id,
"$and": [
{ "stock": { "$gt": o.stock } } ,
{ "stock": { "$gt": 0 } }
]
},
{ "$inc": { "stock": -(o.stock) } },
{ "new": true }, // <-- returns modified document
function (err, doc) {
// check whether there was an update
}
);
}

Related

Update field using previous value (mongodb) [duplicate]

Is it possible, using mongoose middleware, to increment two fields one with a condition and the other without? In this case i want to increment "stats.ratings" by one, if the user inserts an input greater than 0, else increment zero.
"stats.answered" always increments one
See code below
module.exports.updateStats = function (req, res) {
var rating = parseInt(req.body.rating, 10);
var wasRated;
if (rating > 0) {
wasRated = true;
} else wasRated = false
Collection.findOneAndUpdate({
_id: req.body._id
}, {
$cond: {
if: wasRated,
then: {
$inc: {
"stats.answered": 1,
"stats.ratings": 1
}
},
else: {
$inc: {
"stats.answered": 1,
"stats.ratings": 0
}
}
}
},
function (err, doc) {
if (err)
throw err;
res.status(200);
})
}
What you can do is this:
// define the default case
var update = {
$inc: {
"stats.answered": 1
}
};
if(parseInt(req.body.rating, 10) > 0) {
// override default in some cases
update = {
$inc: {
"stats.answered": 1,
"stats.ratings": 1
}
}
}
and then
Collection.findOneAndUpdate({
_id: req.body._id
}, update,
function (err, doc) {
if (err)
throw err;
res.status(200);
})
}

How to user inner query with mongoDB update operation?

i'm trying to write an update query to update a column/filed of a collection. My collection has 4 fields: _id, name, date1, date2.
I'm trying to write an update query to set date1 = date2 and i tried using inner query as shown below but didn't work.
db.myCollection.updateMany(
{},
{
'$set':
{
'date2': db.myCollection.find({},{date1:1, _id:0}).limit(1)
}
}
);
Maybe something like this( version 4.2+):
db.collection.update({},
[
{
$addFields: {
date1: "$date2"
}
}
],
{
multi: true
})
playground
For your case ( Version 3.6 ):
db.collection.find().forEach(
function (elem) {
db.collection.update(
{
_id: elem._id
},
{
$set: {
date1: elem.date2
}
}
);
}
);
var cursor = db.collection.find()
var requests = [];
cursor.forEach(document => {
requests.push( {
'updateOne': {
'filter': { '_id': document._id },
'update': { '$set': { 'date1': document.date2 } }
}
});
if (requests.length === 500) {
//Execute per 500 operations and re-init
db.collection.bulkWrite(requests);
requests = [];
}
});
if(requests.length > 0) {
db.collection.bulkWrite(requests);
}

Statistics with Mongo DB

I have the following DB structure :
{
"uploadedAt": "2021-09-22T22:09:12.133Z",
"paidAt: "2021-09-30T22:09:12.133Z",
"amount": {
"currency": "EUR",
"expected": 70253,
"paid": 0
},
}
I would like to know how do I calculate the total amount that still need to be paid (expected - paid), and the average date between uploadedAt and paidAt. This for multiple records.
My function for getting the data is (the criteria should be updated to get this data).
const invoiceParams = new FindParams();
invoiceParams.criteria = { company: company._id }
const invoices = await this.findAll(invoiceParams);
FindAll function looks like:
async findAll(
params: FindParams,
ability?: Ability,
includeDeleted: boolean = false,
): Promise<Entity[]> {
let queryCriteria: Criteria = params.criteria;
let query: DocumentQuery<Entity[], Entity> = null;
if (!includeDeleted) {
queryCriteria = {
...queryCriteria,
deleted: { $ne: true },
};
}
try {
if (ability) {
ability.throwUnlessCan('read', this.entityModel.modelName);
queryCriteria = {
...toMongoQuery(ability, this.entityModel.modelName),
...queryCriteria,
};
}
query = this.entityModel.find(queryCriteria);
if (params.populate) {
query = query.populate(params.populate);
}
if (params.sort) {
query = query.sort(params.sort);
}
if (params.select) {
query = query.select(params.select);
}
return query.exec();
} catch (error) {
if (error instanceof ForbiddenError) {
throw new ForbiddenException(error.message);
}
throw error;
}
}
Update:
const paymentTime = await this.invoiceModel.aggregate([
{
$group: {
_id: "$account",
averageSpread: { $avg: { $subtract: ["$paidAt", "$uploadedAt"] } },
count: { $sum: 1 }
}
}
]);
Try this aggregation pipeline:
db.invoiceParams.aggregate([
{
$set: {
expectedPaid: { $subtract: ["$amount.expected", "$amount.paid"] },
averageDate: { $toDate: { $avg: [{ $toLong: "$uploadedAt" }, { $toLong: "$paidAt" }] } }
}
}
])

Meteor mongo get this value

I have a meteor game
On the server i have a timer that calls meteor method moveFish.
Meteor.startup(() => {
Meteor.setInterval(function(){
Meteor.call("moveFish")
}, 40);
});
That method selects all fishes are alive and make them move
Meteor.methods({
moveFish: function(id, speed) {
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": 2 } } )
}
})
How do I move fish using this.fish.speed instead value 2
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": 2 } } )
*Notice that doesn't work
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": "fish.speed" } } )
That's works
Meteor.users.find().map( function(user) { x = user.fish.speed Meteor.users.update(user, {$inc: {"fish.positionX": x} }) })
Unfortunately document can't use the reference to itself on update operation.
You need to find it first and iterate over all documents manually in this case:
Meteor.users.findAll({ "fish.alive": true }).fetch().forEach(function(fish) {
fish.positionX += fish.speed;
fish.save();
});

MongoDB - addToSet/push only on upsert

I would like to add a subdocument to an array if it doesn't already exist and then return the newly added subdocument (or at least the array of subdocuments) within one query. Here is an example document structure:
{
"name": "John Smith",
"folders": [
{
"folderName": "Breweries"
"updatedAt": 1450210046338,
"checkins": [
{
"facebookID": "123",
"checkinID": "3480809",
"addedOn": 1450210046338
},
{
"facebookID": "234",
"checkinID": "345254",
"addedOn": 1450210046339
}
],
},
{
"folderName": "Food"
"updatedAt": 1450210160277,
"checkins": [
{
"facebookID": "432",
"checkinID": "123545426",
"addedOn": 1450210160277
}
],
}
],
}
The nested query below checks to see if the new folder's name already exists in the folders array. If it doesn't already exist, it adds the new folder to the folders array:
(using mongoskin here)
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findAndModify(
{facebookID: facebookID},
[],
{$addToSet: {folders: newFolder}},
{fields:{'folders': 1}, new: true},
function (err, result) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(result);
}
});
}
});
It seems like you should be able to do this in one query - but I couldn't find a way to achieve $setOnInsert functionality with array operators ($addToSet/$push).