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.
Related
the $sort method in this query does not work properly, I want to list aggregations and they should be listed depending on $sort query I defined
const newConverSation = await Messenger.Messenger.aggregate([
{
$sort: { 'createdAt': -1 }
},
{ $match: {
users: mongoose.Types.ObjectId(req.body.userid)}},
{
$group: {
_id: { $setUnion: "$users" },
message: { $first: "$$ROOT" },
},
},
])
It sometimes sort correctly, sometimes does not, randomly...
--------------------------------edited for another question
this is my toJSON method which normally works properly
messengerScheme.methods.toJSON = function(){
const messenger = this
const messengerObject = messenger.toObject()
messengerObject.createdAt = moment(messengerObject.createdAt).fromNow()
return messengerObject
}
when I query my model directly I get createdAt : "7 hours", However when I query it with such an aggregation I get createdAt : "2021-05-17T11:34:47.475Z", so toJSON method does not work in my aggregation
I am finding it defficult to add up all amount paid by customers that ordered items
Order Schema
const orderschema = new Mongoose.Schema({
created: { type: Date, default: Date.now() },
amount: { type: Number, default: 0 }
User: [{ type: Mongoose.Schema.ObjectId, ref: 'Users'}]
...
})
Route
Get('/total-amount', total-amount)
Controller
Exports.total-amount = () => {
Order.find()...
}
I don't know what to add here to get the total amount made by all customers.
Using NodeJS and MongoDB.
Thank you for you help
You can use $sum in an aggregation stage like this:
First $group all (without _id is to group all values)
Then create field total which is the sum of al amount.
And an optional stage, $project to output only total field.
db.order.aggregate({
"$group": {
"_id": null,
"total": {
"$sum": "$amount"
}
}
},
{
"$project": {
"_id": 0
}
})
Example here
To add into a controller using nodeJS and Mongoose you can use something like this piece of code:
Exports.total - amount = (req, res) => {
Order.aggregate({
"$group": {
"_id": null,
"total": {
"$sum": "$amount"
}
}
}, {
"$project": {
"_id": 0
}
}).then(response => {
res.status(200).send(response)
}).catch(e => res.status(400).send())
}
Note hoy the operation is done using your mongoose model (in this case Order). You are calling aggregate method in the same way you call find method for example: Instead of doing
yourModel.find()
Is
yourModel.aggregate()
And the response will be:
[
{
"total": 6
}
]
So even you can update your controller to add a if/else block like this:
if(response[0].total)
res.status(200).send(response[0].total)
else
res.status(404).send()
I want to return the average of a Number field by another field(the document ID field):
Comments.aggregate([
{$group:
{
_id: ($nid: req.adID), // here is the field I want to set to req.adID
adAvg:{$avg:"$stars"}
}
}
], function(err, resulat){
if(err) {
res.send(String(err));
}
res.send(resulat);
}
)
The ID field is in the request object, req.adID, I didnt find an example for grouping by a query (_id : 'req.adID').
My schema looks like:
var CommentSchema = new Schema(
{
_id: Schema.Types.ObjectId, //scheme
nid:Number, // ad ID
posted: Date, // Date
uid: Number, // user ID
title: String, //String
text: String, // String
stars: Number //String
}
);
Also if someone can write the return data for this query it will be great!
From your follow-up comments on the question, looks like your aggregation needs the $match pipeline to query the documents that match the req.adID on the nid field, your $group pipeline's _id field should have the $nid field expression so that it becomes your distinct group by key. The following pipeline should yield the needed result:
var pipeline = [
{ "$match": { "nid": req.adID } },
{
"$group": {
"_id": "$nid",
"adAvg": { "$avg": "$stars" }
}
}
]
Comments.aggregate(pipeline, function(err, result){
if(err) {
res.send(String(err));
}
res.send(result);
})
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
})
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.