So I have a schema, and in that schema there is an object called "caseCount" with a Number Value. I simply want to fetch every value for each object of "caseCount" and add it up. Could somebody point me in the right direction? If I need to explain some more, I'm happy to do so. Thank you!
Schema:
const Profile = Schema({
userID: String,
messageCount: Number,
caseCount: Number,
Skins: Array,
});
code:
const data = Profiles.collection.aggregate([{
$group: {
_id: '$id',
totalCaseCount: { $sum: '$caseCount' }
}
}]);
Demo - https://mongoplayground.net/p/7TnpQl3tJHt
Use $sum in aggregation query.
$group
db.collection.aggregate({
$group: {
_id: null,
toatalCaseCount: { $sum: "$caseCount" }
}
})
Profiles.collection.aggregate([{
$group: {
_id: null,
totalCaseCount: { $sum: '$caseCount' }
}
}], function(err, data) {
console.log(data); // here you'll get your data from the query
});
Related
I have recently shifted to MongoDB and Mongoose with Node.js. And I am wrapping my head around it all coming from SQL.
I have a collection where documents have a similar structure to the following:
{
name: String
rank: Number
}
Sometimes the name might be the same, but the rank will always be different.
I would like to remove all duplicates of name, but retain the object that has the LOWEST rank.
For instance, if my collection looked like this:
{
name: "name1"
rank: 3
},
{
name: "name1"
rank: 4
},
{
name: "name1"
rank: 2
}
I would like to remove all objects where name is the same except for:
{
name: "name1"
rank: 2
}
Is this possible to do with mongoose?
Here is my approach:
const found = await db.collection.aggregate([
{
$group: {
_id: "$name",
minRank: {
$min: "$rank"
}
}
},
])
await db.collection.deleteMany({
$or: found.map(item => ({
name: item._id,
rank: { $ne: item.minRank }
}))
})
Explanation:
From my point of view your solution would result in many unnecessary calls being made, which would result in a terrible time of execution. My solution exactly contains two steps:
find for each document's property name the corresponding lowest rank available.
delete each document, where the name is equal to one of those names and the rank is not equal to the actual lowest rank found.
Additional notes:
If not already done, you should probably define an index on the name property of your schema for performance reasons.
Okay, I figured it out using aggregate:
const duplicates = await collectionName.aggregate([
{
$group: {
_id: "$name",
dups: { $addToSet: "$_id" },
count: { $sum: 1 }
}
},
{
$match: {
count: { $gt: 1 }
}
}
]);
duplicates.forEach(async (item) => {
const duplicate_names = item.dups;
const duplicate_name = await collectionName.find({ _id: { $in: duplicate_names } }).sort({ rank: 1 });
duplicate_name.shift();
duplicate_name.forEach(async (item) => {
await collectionName.deleteOne({ _id: item._id });
});
});
I am an extreme newbie to MongDB - using mongoose npm module in my node.js app
given the following data:
_id:622e41bf09c90e08689eb0aa
title:"amazon"
start:"2022-03-13 12:10:55"
amount:"14.11"
_id:622e41bf09c90e08689eb0ab
title:"amazon"
start:"2022-03-13 12:10:55"
amount:"14.11"
const db = mongoose.model('upgrade', schema);
const sum = db.aggregate(
[{
$project : {
_id: '$_id',
total : {
$sum: "amount"
}
}
}]);
console.log(__line__, sum._pipeline[0].$project);
// 143 { _id: '$_id', total: { '$sum': '$amount' } }
what I would like to see in the results is the total of the 2 amounts (28.22)
Can someone please help me get the right syntax to get the desired result?
I really like the idea of working with JSON as opposed to RDBMS
Thank you!
UPDATE
const sum = db.aggregate([{ $group : { _id: null, total : { $sum: { $toDouble : "$amount" }}}}]);
still logging: 143 { _id: null, total: { '$sum': { '$toDouble': '$amount' } } }
UPDATE 2
I have just added things to the schema definition:
const schema = new mongoose.Schema({
name: String,
title: String,
amount: mongoose.Decimal128,
start: { type: Date, default: Date.now },
});
but I am still just getting an empty array in the log
UPDATE 3
console.log(line, mongoose.connection.readyState); shows connecting (2) - so there must be something wrong with my connection string
db.aggregate is asynchronous. You have to wait for the response. This can be done with the await keyword if you are inside an async function or with the .then function.
Using await:
const sum = await db.aggregate([{ $group : { _id: null, total : { $sum: { $toDouble : "$amount" }}}}]);
console.log(sum);
Using .then
db.aggregate([{ $group : { _id: null, total : { $sum: { $toDouble : "$amount" }}}}])
.then((sum) => console.log(sum));
I'm trying to make trivial SUM on mongoDB to count number of prices for single client.
My collection:
{"_id":"5d973c71dd93adfbda4c7272","name":"Faktura2019006","clientId":"5d9c87a6b9676069c8b5e15b","expiration":"2019-10-02T01:11:18.965Z","price":999999,"userId":"123"},
{"_id":"5d9e07e0b9676069c8b5e15d","name":"Faktura2019007","clientId":"5d9c87a6b9676069c8b5e15b","expiration":"2019-10-02T01:11:18.965Z","price":888,"userId":"123"}
What I tried:
// invoice.model.js
const mongoose = require("mongoose");
const InvoiceSchema = mongoose.Schema({
_id: String,
name: String,
client: String,
userId: String,
expiration: Date,
price: Number
});
module.exports = mongoose.model("Invoice", InvoiceSchema, "invoice");
and
// invoice.controller.js
const Invoice = require("../models/invoice.model.js");
exports.income = (req, res) => {
console.log("Counting Income");
Invoice.aggregate([
{
$match: {
userId: "123"
}
},
{
$group: {
total: { $sum: ["$price"] }
}
}
]);
};
What happen:
When I now open a browser and code above is being called, I get console log 'Counting Income' in terminal however in browser it's just loading forever and nothing happen.
Most likely I just miss some stupid minor thing but I'm trying to find it out for quite a long time without any success so any advise is welcome.
The reason that the controller never finishes is because you are not ending the response process (meaning, you need to use the res object and send something back to the caller).
In order to get the aggregate value, you also need to execute the pipeline (see this example).
Also, as someone pointed out in the comments, you need to add _id: null in your group to specify that you are not going to group by any specific field (see the second example here).
Finally, in the $sum operator, for what you're trying to do, you just need to remove the array brackets since you only want to sum on a single field (see a few examples down here).
Here is the modified code:
// invoice.controller.js
const Invoice = require("../models/invoice.model.js");
exports.income = (req, res) => {
console.log("Counting Income");
Invoice.aggregate([
{
$match: {
userId: "123"
}
},
{
$group: {
_id: null,
total: { $sum: "$price" }
}
}
]).then((response) => {
res.json(response);
});
};
Edit for your comment about when an empty array is returned.
If you want to always return the same type of object, I would control that in the controller. I'm not sure if there is a fancy way to do this with the aggregate pipeline in mongo, but this is what I would do.
Invoice.aggregate([
{
$match: {
userId: "123"
}
},
{
$group: {
_id: null,
total: { $sum: "$price" }
}
},
{
$project: {
_id: 0,
total: "$total"
}
}
]).then((response) => {
if (response.length === 0) {
res.json({ total: 0 });
} else {
// always return the first (and only) value
res.json(response[0]);
}
});
Here, if you find a userId of 123, then you would get this as the return:
{
"total": 1000887
}
But if you change the userId to, say, 1123 which doesn't exist in your db, the result will be:
{
"total": 0
}
This way, your client can always consume the same type of object.
Also, the reason I put the $project pipeline stage in there was to suppress the _id field (see here for more info).
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.
I have a User collection with schema
{
name: String,
books: [
id: { type: Schema.Types.ObjectId, ref: 'Book' } ,
name: String
]
}
Is it possible to get an array of book ids instead of object?
something like:
["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]
Or
{ids: ["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]}
and not
{
_id: ObjectId("53eb79d863ff0e8229b97448"),
books:[
{"id" : ObjectId("53eb797a63ff0e8229b4aca1") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acac") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acad") }
]
}
Currently I am doing
User.findOne({}, {"books.id":1} ,function(err, result){
var bookIds = [];
result.books.forEach(function(book){
bookIds.push(book.id);
});
});
Is there any better way?
It could be easily done with Aggregation Pipeline, using $unwind and $group.
db.users.aggregate({
$unwind: '$books'
}, {
$group: {
_id: 'books',
ids: { $addToSet: '$books.id' }
}
})
the same operation using mongoose Model.aggregate() method:
User.aggregate().unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
Note that books here is not a mongoose document, but a plain js object.
You can also add $match to select some part of users collection to run this aggregation query on.
For example, you may select only one particular user:
User.aggregate().match({
_id: uid
}).unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
But if you're not interested in aggregating books from different users into single array, it's best to do it without using $group and $unwind:
User.aggregate().match({
_id: uid
}).project({
_id: 0,
ids: '$books.id'
}).exec(function(err, users) {
// use users[0].ids
})