Update on Aggregate in Mongodb - javascript

i am trying to toggle a boolean value inside a object, which is a subdocument, i am having hard time to update a particular object within an array.
Document:
"_id" : ObjectId("54afaabd88694dc019d3b628")
"Invitation" : [
{
"__v" : 0,
"ID" : ObjectId("54af6ce091324fd00f97a15f"),
"__t" : "USER",
"_id" : ObjectId("54b4ceb50fc380001bea1752"),
"Accepted" : false
},
{
"__v" : 0,
"ID" : ObjectId("54afac5412f5fdcc007a5c4d"),
"__t" : "USER",
"_id" : ObjectId("54b4cebe0fc380001bea1753"),
"Accepted" : false
}
],
Controller:
User.aggregate([{$match: {_id: ObjectId(54afaabd88694dc019d3b628)}},{$unwind: '$Invitation'},{$project: {_id: '$_id',Invitation: '$Invitation'}}],function(err,results){
function updateInvitation(_id){
var query = {'_id': _id, 'Invitation.ID': ObjectId("54af6ce091324fd00f97a15f")};
var operator = {$inc: {'Invitation.Accepted': 1}};
User.update(query,operator,{multi:true},function(err,updated){
if(err){
console.log(err);
}
console.log('updating'+updated);
});
}
res.jsonp(results);
updateInvitation(results[0]._id);
});
i tried using $set but it didnt worked out as whole Invitation array got replaced with 'Accepted = 1'
How can i toggle 'Accepted' field of the document with particular 'ID'.
Invitation.$.Accepted
Positional operator doesnot apply to field containing array so can't iterate to Accepted field
EDIT:
User.find({_id: req.user._id},'Invitation',function(err,docs){
if(err){
console.log(err);
}
console.log(docs);
var results = [];
async.each(docs,function(doc,err) {
if(err){
console.log('error'+ err);
}
async.each(docs.Invitation,function(invite,callback) {
console.log('second async');
User.update(
{ '_id': doc._id, 'Invitation._id': invite._id },
{ '$set': {'Invitation.$.Accepted': !invite.Accepted}},
function(err,doc) {
results.push(doc);
console.log('updated'+doc);
callback(err);
}
);
});
},function(err) {
if (err)
console.log(err);
console.log(results);
});
});
The control is not getting inside second async.each, error is thrown on first async here is the error:
error-function () {
if (called) throw new Error("Callback was already called.");
called = true;
fn.apply(root, arguments);
}

I really don't think that even as a feeder query the aggregation framework is the right operation to use here. All you are doing is "denormalizing" the array as individual documents. There really should be no need. Just fetch the document instead:
var query = {}; // whatever criteria
Users.find(query,"Invitation",function(err,docs) {
if (err) {
console.log(err);
var results = [];
async.each(docs,function(doc,callback) {
async.each(docs.Invitation,function(invite,callback) {
Users.findOneAndUpdate(
{ "_id": doc._id, "Invitation._id": invite._id },
{ "$set": { "Invitation.$.Accepted": !invite.Accepted } },
function(err,doc) {
results.push( doc );
callback(err);
}
);
},callback);
},function(err) {
if (err)
console.log(err);
console.log(results);
});
});
So there is no problem iterating the list of documents in a response for what you are doing, it's just that you also want to iterate the array members as well. The catch is when issuing any kind of .update() that you need to be aware then the asynchronous call is complete.
So I'm using async.each but you probably want async.eachLimit to control the looping. The matching of the element comes from the positional $ operator, corresponding to the matched array element in the query.
It's just JavaScript code, so simply "toggle" the value with !invite.accepted which will inverse it. For additional fun, return the "results" array by pushing the modified document from .findOneAndUpdate().

Use the positional update operator for this:
http://docs.mongodb.org/manual/reference/operator/update/positional/
User.update(
{
"_id": ObjectId("54afaabd88694dc019d3b628"),
"Invitation": {"$elemMatch": {"ID" : ObjectId("54af6ce091324fd00f97a15f"), "Accepted":false}}
},
{
"$set" : {
"Invitation.$.Accepted" : true
}
},{multi:false, upsert:false, safe:true}, function (err, numAffectedDocuments){
// TODO
}
);
Note : you don't need the aggregation step since it actually does nothing.

Related

I could not query with different condition in mongoose

This is my Operator Models:
const operatorSchema = new Schema({
operatorName: {
type: String
},
users:[{
email:String,
payment:Number,
paymentsData: Date,
product: String,
}],
});
I need to filter by operatorName and email in users block. But when I try with this I get all users in related OperatorName how can I query correctly ?
Operators.find( { $and: [{operatorName: operatorName}, {'users.email': 'super#m.com'}]}, function (err, docs) {
) {
if (err) console.log(err)
else {
docs.forEach(function(data){
console.log(data)
})
// res.render('total_earns_operator_tables', { operators: docs });
}
});
EDIT
I also try with aggregate method like this but again, I get same result and I gel all bunch of user data, but I want only demouser#mail.com
Operators.aggregate([
{ $match: {$and: [{ operatorName: operatorName},{'users.email':
'demouser#mail.com' }]}},
]
,function (err, docs) {
// console.log(Operators)
// Operators.find( { $and: [{operatorName: operatorName}, {users: {$elemMatch: {email:['super#m.com']}}}]}, function (err, docs) {
// Operators.find( {operatorName: operatorName, "users.email": "demouser#mail.com"}, function (err, docs) {
if (err) console.log(err)
else {
docs.forEach(function(data){
console.log(data)
})
// res.render('total_earns_operator_tables', { operators: docs });
}
});
It is very basic but I couldnt find solution.
Your query is doing exactly what it is supposed to do. It is returning all documents that satisfy you two criteria: 1. having a specified operatorName and 2. users array having at least one user matching the specified email.
If you want to reashape your documents by filtering the user array to only include the user matching your condition, you'll have to use an aggregation.
EDIT
As per your edit: Your aggregation only have a $match stage, which is identical to your query above. To change the shape of a document, the aggregation framework provides you with the $project stage, see the example below:
Operators.aggregate([
{
$match: {
operatorName: operatorName,
"users.email": "demouser#mail.com"
}
},
{
$project: {
operatorName: '$operatorName',
users: {
$filter: {
input: "$users",
as: "user",
cond: {
$eq: [
"$$user.email",
"demouser#mail.com"
]
}
}
}
}
}
]
Here, we first filter the collection to get only the documents you want, using the $match stage, then we use the $filteroperator in $project stage, to return only the matching users within the array.
See the working playground

Using an $unwind Just Prior to an updateMany() in MongoDB

In my MongoDB/Node backend, I have a function that updates a date on a field titled subscriptionEnd based on subscription _ids that are passed in. It looks like this:
try {
db.collection('clients').updateMany(
{ "subscription._id": { $in: mongoArrSubscription } },
{ $set : {"subscription.$.subscriptionEnd": lastDayOfMonth } },
function (err, res) {
if (err) throw err;
});
res.sendStatus(200);
} catch (e) {
console.log(e);
}
}
This works as is. However, as is, this will only target the first element in the "subscription" array. I realize now we have instances where there are two elements within the same array that may be passed in. So, my thought is to use $unwind within this function, to first $unwind the "subscription" array. I'm a little unclear on the syntax however, and if this is doable.
My unwind aggregation would look like this:
Client.aggregate( [ { $unwind : "$subscription" } ] );
Is there a way I can chain together this $unwind aggregation, so it happens just prior to the updateMany() within my try/catch block? What does that syntax look like? Do I chain the operations together, or do I pass the $unwind in as an argument to the updateMany() ?
I tried this, passing the $unwind operation in as the first parameter:
try {
db.collection('clients').updateMany( { $unwind : "$subscription" },
{ "subscription._id": { $in: mongoArrSubscription } },
{ $set : {"subscription.$.subscriptionEnd": lastDayOfMonth } },
function (err, res) {
if (err) throw err;
});
res.sendStatus(200);
} catch (e) {
console.log(e);
}
}
... but it errors out:
MongoError: Unknown modifier: $unwind
So maybe I need to do the aggregation $unwind first, as a separate operation, and then run the updateMany() ?
UPDATE: Someone pointed out that $unwind is not useable with updateMany(), so perhaps I need to do the $unwind operation first and separately, and then run my updateMany() operation?
You can mimic the 3.6 multi update using aggregation and bulk write for lower versions.
Something like
var bulk = db.getCollection('clients').initializeUnorderedBulkOp();
var count = 0;
var batch = 1;
db.getCollection('clients').aggregate([
{$match:{"subscription._id":{$in:mongoArrSubscription}}},
{$unwind:"$subscription"},
{$match:{"subscription._id":{$in:mongoArrSubscription}}},
{$project: {_id:0, subscription_id:"$subscription._id"}}
]).forEach(function(doc){
var subscription_id = doc.subscription_id;
bulk.find({"subscription._id":subscription_id}).updateOne(
{$set:{"subscription.$.subscriptionEnd": lastDayOfMonth}}
);
count++;
if (count == batch) {
bulk.execute();
bulk = db.getCollection('clients').initializeUnorderedBulkOp();
count = 0;
}
});
if (count > 0) {
bulk.execute();
}

Merge and aggregate some fields of two or more collections with identical schema mongodb

I have some collections with identical schema and I want to perform a merge + aggregation on them. The schemas are simple and look like this:
{ 'fr': 1, 'to': 1, 'wg': 213}
{ 'fr': 1, 'to': 2, 'wg': 53}
{ 'fr': 2, 'to': 2, 'wg': 5521}
The following code works for merging two collections, but I am wondering if there is a faster solutions and/or one that could merge multiple collections in a similar way without creating nested calls:
var c = db.collection('first').find()
c.each(function(err, doc) {
if (err) throw err
if (doc == null) {
console.log('done')
return
}
db.collection('second').findOne({
'fr': doc['fr'],
'to': doc['to']
}, function(err, doc2) {
if (err) throw err
db.collection('my_results').save({
'fr': doc['fr'],
'to': doc['to'],
'wg': doc['wg'] + doc2['wg']
}, function(err) {
if (err) throw err
})
})
})
There are no absolute free operations here since you cannot do joins with MongoDB. But you can get the output you want using mapReduce and some of its features.
So first create a mapper:
var mapper = function () {
emit( { fr: this.fr, to: this.to }, this.wg )
};
And then a reducer:
var reducer = function (key,values) {
return Array.sum( values );
};
Then you run the mapReduce operation with the output set to a different collection:
db.first.mapReduce(mapper,reducer,{ "out": { "reduce": "third" } })
Note the "out" options there which are explained in this manual section. The point is, despite possibly misleading statistics output in the console, that "reduce" statement is very important. This is so when we run the same code against the other collection:
db.second.mapReduce(mapper,reducer,{ "out": { "reduce": "third" } })
What actually happens in the result, is the output from the first operation is also passed into the "reduce" phase of the second operation.
The end result is that all the values from both collections with the same key values will be added together in the "third" collection:
{ "_id" : { "fr" : 1, "to" : 1 }, "value" : 426 }
{ "_id" : { "fr" : 1, "to" : 2 }, "value" : 106 }
{ "_id" : { "fr" : 2, "to" : 2 }, "value" : 11042 }
You can make that a little fancier if you wanted your fr and to to be the unique combination of two possibles in either order, or even run another mapReduce or aggregate over those results.

MongoDB : adding element to inner json object while both key-value stored in variables

i am trying to update a document in mongo db with nodejs native driver.
initially it was inserted like:
matches {
_id:2001,
requester:"MITH",
accepter:"NIKK",
toss:"MITH",
bat:"NIKK",
scores:{"MITH":220},
status:0,
won:"MITH"
}
now i need to update the document where i need to insert a new element "NIKK":198 to scores object to make it scores:{"MITH":220,"NIKK":198}
problem is the key comes in a variable only. and when i update it is not updating
Below is the code with which i am trying
var _jsonMatch = {status:4};
var _scorepush = {}
_scorepush[variablevalue] = 198; // variablevalue in reference above is NIKK
var data = {"$set": _jsonMatch,"$push": {"scores":_scorepush} }
mith.findAndModify({_id:mith.db.bson_serializer.ObjectID.createFromHexString(matchId)},
[],
data,
{ upsert: true,new:true },
function(error, match){
if( error ) callback(error);
else callback(null, match);
});
EDIT :
I tried $addToSet instead of $push and i got the below error in callback with data undefined.
{ [MongoError: Cannot apply $addToSet modifier to non-array] name:
'MongoError', lastErrorObject: { err: 'Cannot apply $addToSet
modifier to non-array',
code: 12591,
n: 0,
connectionId: 56,
ok: 1 }, errmsg: 'Cannot apply $addToSet modifier to non-array', ok: 0 } undefined
You need to build up your $set object programmatically to use dot notation in the key that sets 'scores.NIKK'. So to update the doc you've shown above:
variablevalue = 'NIKK';
var set = {
status: 4
};
set['scores.' + variablevalue] = 198;
mith.findAndModify({_id: 2001},
[],
{ $set: set },
{ upsert: true, new: true },
function(error, match){
if( error ) callback(error);
else callback(null, match);
}
);
Note: the awkwardness of this is because you're using dynamic keys that you need to build up at run-time. Consider re-working your schema so that scores is an array that looks something like this instead:
scores: [{name: 'MITH', value: 220}, {name: 'NIKK', value: 198}]
I think you want $set instead of $push:
var _jsonMatch = {status:4};
var _scorepush = {}
_scorepush[variablevalue] = 198; // variablevalue in reference above is NIKK
_jsonMatch["scores"] = _scorepush;
var data = {"$set": _jsonMatch };
mith.findAndModify({_id:mith.db.bson_serializer.ObjectID.createFromHexString(matchId)},
[],
data,
{ upsert: true,new:true },
function(error, article){
if( error ) callback(error);
else callback(null, article);
});

How to sort in mongoose?

I find no doc for the sort modifier. The only insight is in the unit tests:
spec.lib.query.js#L12
writer.limit(5).sort(['test', 1]).group('name')
But it doesn't work for me:
Post.find().sort(['updatedAt', 1]);
In Mongoose, a sort can be done in any of the following ways:
Post.find({}).sort('test').exec(function(err, docs) { ... });
Post.find({}).sort([['date', -1]]).exec(function(err, docs) { ... });
Post.find({}).sort({test: 1}).exec(function(err, docs) { ... });
Post.find({}, null, {sort: {date: 1}}, function(err, docs) { ... });
This is how I got sort to work in mongoose 2.3.0 :)
// Find First 10 News Items
News.find({
deal_id:deal._id // Search Filters
},
['type','date_added'], // Columns to Return
{
skip:0, // Starting Row
limit:10, // Ending Row
sort:{
date_added: -1 //Sort by Date Added DESC
}
},
function(err,allNews){
socket.emit('news-load', allNews); // Do something with the array of 10 objects
})
As of Mongoose 3.8.x:
model.find({ ... }).sort({ field : criteria}).exec(function(err, model){ ... });
Where:
criteria can be asc, desc, ascending, descending, 1, or -1
Note: Use quotation marks or double quote
use "asc", "desc", "ascending", "descending", 1, or -1
UPDATE:
Post.find().sort({'updatedAt': -1}).all((posts) => {
// do something with the array of posts
});
Try:
Post.find().sort([['updatedAt', 'descending']]).all((posts) => {
// do something with the array of posts
});
Mongoose v5.x.x
sort by ascending order
Post.find({}).sort('field').exec(function(err, docs) { ... });
Post.find({}).sort({ field: 'asc' }).exec(function(err, docs) { ... });
Post.find({}).sort({ field: 'ascending' }).exec(function(err, docs) { ... });
Post.find({}).sort({ field: 1 }).exec(function(err, docs) { ... });
Post.find({}, null, {sort: { field : 'asc' }}), function(err, docs) { ... });
Post.find({}, null, {sort: { field : 'ascending' }}), function(err, docs) { ... });
Post.find({}, null, {sort: { field : 1 }}), function(err, docs) { ... });
sort by descending order
Post.find({}).sort('-field').exec(function(err, docs) { ... });
Post.find({}).sort({ field: 'desc' }).exec(function(err, docs) { ... });
Post.find({}).sort({ field: 'descending' }).exec(function(err, docs) { ... });
Post.find({}).sort({ field: -1 }).exec(function(err, docs) { ... });
Post.find({}, null, {sort: { field : 'desc' }}), function(err, docs) { ... });
Post.find({}, null, {sort: { field : 'descending' }}), function(err, docs) { ... });
Post.find({}, null, {sort: { field : -1 }}), function(err, docs) { ... });
For Details: https://mongoosejs.com/docs/api.html#query_Query-sort
Update
There is a better write up if this is confusing people; check out finding documents and how queries work in the mongoose manual. If you want to use the fluent api you can get a query object by not providing a callback to the find() method, otherwise you can specify the parameters as I outline below.
Original
Given a model object, per the docs on Model, this is how it can work for 2.4.1:
Post.find({search-spec}, [return field array], {options}, callback)
The search spec expects an object, but you can pass null or an empty object.
The second param is the field list as an array of strings, so you would supply ['field','field2'] or null.
The third param is the options as an object, which includes the ability to sort the result set. You would use { sort: { field: direction } } where field is the string fieldname test (in your case) and direction is a number where 1 is ascending and -1 is desceding.
The final param (callback) is the callback function which receives the collection of docs returned by the query.
The Model.find() implementation (at this version) does a sliding allocation of properties to handle optional params (which is what confused me!):
Model.find = function find (conditions, fields, options, callback) {
if ('function' == typeof conditions) {
callback = conditions;
conditions = {};
fields = null;
options = null;
} else if ('function' == typeof fields) {
callback = fields;
fields = null;
options = null;
} else if ('function' == typeof options) {
callback = options;
options = null;
}
var query = new Query(conditions, options).select(fields).bind(this, 'find');
if ('undefined' === typeof callback)
return query;
this._applyNamedScope(query);
return query.find(callback);
};
HTH
you can sort your query results by
Post.find().sort({createdAt: "descending"});
This is how I got sort to work in mongoose.js 2.0.4
var query = EmailModel.find({domain:"gmail.com"});
query.sort('priority', 1);
query.exec(function(error, docs){
//...
});
Chaining with the query builder interface in Mongoose 4.
// Build up a query using chaining syntax. Since no callback is passed this will create an instance of Query.
var query = Person.
find({ occupation: /host/ }).
where('name.last').equals('Ghost'). // find each Person with a last name matching 'Ghost'
where('age').gt(17).lt(66).
where('likes').in(['vaporizing', 'talking']).
limit(10).
sort('-occupation'). // sort by occupation in decreasing order
select('name occupation'); // selecting the `name` and `occupation` fields
// Excute the query at a later time.
query.exec(function (err, person) {
if (err) return handleError(err);
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) // Space Ghost is a talk show host
})
See the docs for more about queries.
app.get('/getting',function(req,res){
Blog.find({}).limit(4).skip(2).sort({age:-1}).then((resu)=>{
res.send(resu);
console.log(resu)
// console.log(result)
})
})
Output
[ { _id: 5c2eec3b8d6e5c20ed2f040e, name: 'e', age: 5, __v: 0 },
{ _id: 5c2eec0c8d6e5c20ed2f040d, name: 'd', age: 4, __v: 0 },
{ _id: 5c2eec048d6e5c20ed2f040c, name: 'c', age: 3, __v: 0 },
{ _id: 5c2eebf48d6e5c20ed2f040b, name: 'b', age: 2, __v: 0 } ]
with the current version of mongoose (1.6.0) if you only want to sort by one column, you have to drop the array and pass the object directly to the sort() function:
Content.find().sort('created', 'descending').execFind( ... );
took me some time, to get this right :(
This is how I managed to sort and populate:
Model.find()
.sort('date', -1)
.populate('authors')
.exec(function(err, docs) {
// code here
})
Post.find().sort({updatedAt: 1});
As of October 2020, to fix your issue you should add .exec() to the call. don't forget that if you want to use this data outside of the call you should run something like this inside of an async function.
let post = await callQuery();
async function callQuery() {
return Post.find().sort(['updatedAt', 1].exec();
}
Others worked for me, but this did:
Tag.find().sort('name', 1).run(onComplete);
Post.find().sort({updatedAt:1}).exec(function (err, posts){
...
});
Starting from 4.x the sort methods have been changed. If you are using >4.x. Try using any of the following.
Post.find({}).sort('-date').exec(function(err, docs) { ... });
Post.find({}).sort({date: -1}).exec(function(err, docs) { ... });
Post.find({}).sort({date: 'desc'}).exec(function(err, docs) { ... });
Post.find({}).sort({date: 'descending'}).exec(function(err, docs) { ... });
Post.find({}).sort([['date', -1]]).exec(function(err, docs) { ... });
Post.find({}, null, {sort: '-date'}, function(err, docs) { ... });
Post.find({}, null, {sort: {date: -1}}, function(err, docs) { ... });
This is what i did, it works fine.
User.find({name:'Thava'}, null, {sort: { name : 1 }})
// Ascending with updatedAt field
Post.find().sort('updatedAt').exec((err, post) => {...});
// Descending with updatedAt field
Post.find().sort('-updatedAt').exec((err, post) => {...});
Refer here: https://mongoosejs.com/docs/queries.html
you can also use aggregate() for sorting
const sortBy = req.params.sort;
const limitNum = req.params.limit;
const posts = await Post.aggregate([
{ $unset: ['field-1', 'field-2', 'field-3', 'field-4'] },
{ $match: { field-1: value} },
{ $sort: { [sortBy]: -1 } }, //-------------------> sort the result
{ $limit: Number(limitNum) },
]);
Solution :
posts.find().sort({field:1})
// for ascending and for descending order just use -1 instead of 1

Categories