How to aggregate mongoose collection? - node.js

In my mongoose model I have an invoiceHeader and invoiceLine collection both relating to account object.
In a view I want to display the total invoiceAmount for the account.
In SQL something like select sum(amount) from invoiceHeader group by account.
How can i achieve similar with nodeJS and mongoose?

Assuming amount is the property that you want to sum on, it would be like:
InvoiceHeaderModel.aggregate({
$match: {
account: '<Account_ID>'
}
}, {
$group: {
_id: null,
total: {
$sum: "$amount"
}
}
}, {
$project: {
_id: 0,
total: 1
}
}, function(err, res) {
// res contains the result
});
The $match operator is used to match certain documents. The $group operator is used to group the documents. The $project operator is used to select certain fields from the documents.

Related

MongoDB query an array of documents for specific match

I want to search the transactions array looking for a specific match. In this example, by pipedrive_id.
This is what I tried (as per mongodb instructions and this other stack overflow post)
const pipedrive_id = 1677;
const inner_pipedrive_id = 1838;
const result = await Transactions.find({
pipedrive_id,
'transactions': { $elemMatch: { 'pipedrive_id': inner_pipedrive_id } }
});
const result2= await Transactions.find({
'transactions': { $elemMatch: { 'pipedrive_id': inner_pipedrive_id } }
});
const result3 = await Transactions.find({
'transactions.pipedrive_id': inner_pipedrive_id
});
And each result itteration returns all transaction items (all 6 items, instead of 2 [that's how many Mark Smith has in the array).
You can use aggregate to filter out the array. Something like this
You can remove $project if you want all the fields
db.collection.aggregate([
{
$match: {
pipedrive_id: "1677"
}
},
{
$unwind: "$transactions"
},
{
$match: {
"transactions.pipedrive_id": "1838"
}
},
{
$project: {
_id: 0,
pipedrive_id: 1,
transactions: 1
}
}
])
You can check the Mongo playground here.
As the doc, $elemMatch matches documents that contain an array field with at least one element that matches the criteria.
To filter the result inside the array, you will need to use $filter from aggregation
Ref: https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/

Query from nested object fields - Mongoose

I don't know if my question was already asked, but I didn't find any.
I need to query an object using the fields from nested objects.
Let me explain this by using images.
In image 1, you can see an "object type" called dialogcontext which refers another collection in my database.
So, I know that with populate function I can get the tested object fields, but I need to create a query using those nested fields.
Something like this:
let detailWeb = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: start, $lt: end } } },
{
"$group": {
_id: { "dialogcontext.channel": "Web" },
data: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }]).exec();
Do you have any idea or suggestion ?
Thank you in advance.

Mongoose sort the aggregated result

I'm having a lot of difficulty in solving this mongodb (mongoose) problem.
There is this schema 'Recommend' (username, roomId, ll and date) and its collection contains recommendation of user.
I need to get a list of most recommended rooms (by roomId). Below is the schema and my tried solution with mongoose query.
var recommendSchema = mongoose.Schema({
username: String,
roomId: String,
ll: { type: { type: String }, coordinates: [ ] },
date: Date
})
recommendSchema.index({ ll: '2dsphere' });
var Recommend = mongoose.model('Recommend', recommendSchema);
Recommend.aggregate(
{
$group:
{
_id: '$roomId',
recommendCount: { $sum: 1 }
}
},
function (err, res) {
if (err) return handleError(err);
var resultSet = res.sort({'recommendCount': 'desc'});
}
);
The results returned from the aggregation pipeline are just plain objects. So you do the sorting as a pipeline stage, not as a separate operation:
Recommend.aggregate(
[
// Grouping pipeline
{ "$group": {
"_id": '$roomId',
"recommendCount": { "$sum": 1 }
}},
// Sorting pipeline
{ "$sort": { "recommendCount": -1 } },
// Optionally limit results
{ "$limit": 5 }
],
function(err,result) {
// Result is an array of documents
}
);
So there are various pipeline operators that can be used to $group or $sort or $limit and other things as well. These can be presented in any order, and as many times as required. Just understanding that one "pipeline" stage flows results into the next to act on.

Query and sum all with mongoose

I want to fetch all users user_totaldocs and user_totalthings and want to sum those variables.
How can it's possible? Here is user schema:
var user_schema = mongoose.Schema({
local : {
...
...
user_id : String,
user_totaldocs : Number,
user_totalthings : Number
....
}
});
You can use the Aggregation Pipeline to add calculated fields to a result. There are some examples below using the mongo shell, but the syntax in Mongoose's Aggregate() helper is similar.
For example, to calculate sums (per user document) you can use the $add expression in a $project stage:
db.user.aggregate(
// Limit to relevant documents and potentially take advantage of an index
{ $match: {
user_id: "foo"
}},
{ $project: {
user_id: 1,
total: { $add: ["$user_totaldocs", "$user_totalthings"] }
}}
)
To calculate totals across multiple documents you need to use a $group stage with a $sum accumulator, for example:
db.user.aggregate(
{ $group: {
_id: null,
total: { $sum: { $add: ["$user_totaldocs", "$user_totalthings"] } },
totaldocs: { $sum: "$user_totaldocs" },
totalthings: { $sum: "$user_totalthings" }
}}
)
You may want only the one total field; I've added in totaldocs and totalthings as examples of calculating multiple fields.
A group _id of null will combine values from all documents passed to the $group stage, but you can also use other criteria here (such as grouping by user_id).
You can use aggregation framework provided by mongodb. For your case --
if you want to fetch sum of user_totaldocs and sum of user_totalthings across the collection (meaning for all users), do --
db.user_schemas.aggregate(
[
{
$group : {
user_id : null,
user_totaldocs: { $sum: "$user_totaldocs"}, // for your case use local.user_totaldocs
user_totalthings: { $sum: "$user_totalthings" }, // for your case use local.user_totalthings
count: { $sum: 1 } // for no. of documents count
}
}
])
To sum user_totaldocs and user_totalthings for particular user in a collection(assuming there are multiple document for a user), this will return sum for each user, DO --
db.user_schemas.aggregate(
[
{
$group : {
user_id : "$user_id",
user_totaldocs: { $sum: "$user_totaldocs"}, // for your case use local.user_totaldocs
user_totalthings: { $sum: "$user_totalthings" }, // for your case use local.user_totalthings
count: { $sum: 1 } // for no. of documents count
}
}
])
No need to provide individual user id.
For more info read:
1. http://docs.mongodb.org/manual/reference/operator/aggregation/group/#pipe._S_group
2. http://docs.mongodb.org/manual/core/aggregation/

MongoDB aggregate query with a where in node.js

I have the following mongodb query in node.js which gives me a list of unique zip codes with a count of how many times the zip code appears in the database.
collection.aggregate( [
{
$group: {
_id: "$Location.Zip",
count: { $sum: 1 }
}
},
{ $sort: { _id: 1 } },
{ $match: { count: { $gt: 1 } } }
], function ( lookupErr, lookupData ) {
if (lookupErr) {
res.send(lookupErr);
return;
}
res.send(lookupData.sort());
});
});
How can this query be modified to return one specific zip code? I've tried the condition clause but have not been able to get it to work.
Aggregations that require filtered results can be done with the $match operator. Without tweaking what you already have, I would suggest just sticking in a $match for the zip code you want returned at the top of the aggregation list.
collection.aggregate( [
{
$match: {
zip: 47421
}
},
{
$group: {
...
This example will result in every aggregation operation after the $match working on only the data set that is returned by the $match of the zip key to the value 47421.
in the $match pipeline operator add
{ $match: { count: { $gt: 1 },
_id : "10002" //replace 10002 with the zip code you want
}}
As a side note, you should put the $match operator first and in general as high in the aggregation chain as you can.

Resources