Aggregate an array in a document in mongoose - node.js

I have a voting app and I have a mongoose scheme as follows:
var optionSchema=new mongoose.Schema({
option:{type:String,required:true},
count:{type:Number,required:true}
});
var pollSchema = new mongoose.Schema({
creator:{type:String,required:true},
title:{type:String,required:true},
options:[optionSchema]
});
I am trying to figure out if I can pull the top X documents by the aggregate count of all the options for each document in node

Try something like this:
db.polls.aggregate([
{
$project: {
totalOptionCount: { $sum: "$options.count"}
},
},
{ $sort : { totalOptionCount : 1 }},
{ $limit : 5 }
])

Related

Mongodb (mongoose) aggregate is returning the JSON given instead of the sum asked for

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));

Can't push items in mongo array

I can't push items into MongoDB array every time that i try to push a new element it creates an empty object and i cant figure out why,
I already used the
Collection.Array.push({element})&
Collection.save()
but i cant figure out a solution
This is My Schema
const Schema = mongoose.Schema;
var ParticipantSchema = new Schema({
nom:{Type:String},
prenom:{Type:String},
email:{Type:String}
})
var CompetitionSchema = new Schema({
nom:String,
date:Date,
place:String,
participant :[ParticipantSchema]
})
module.exports = mongoose.model("Competition",CompetitionSchema);
This is my funtion
exports.addParticipant=function(req,res){
var newParticipant={
"nom":req.body.nom,
"prenom":req.body.prenom,
"email":req.body.email
}
Competition.updateOne(
{ _id:req.body.id},
{ $push: { participant: newParticipant } },
(err,done)=>{
return res.json(done)
}
);
}
the result is always an empty object like below
{
"_id": "5ded0eeb85daa100dc5e57bf",
"nom": "Final",
"date": "2019-01-01T23:00:00.000Z",
"place": "Sousse",
"participant": [
{
"_id": "5ded0eeb85daa100dc5e57c0"
},
{
"_id": "5dee3c1b08474e27ac70672e"
}
],
"__v": 0
}
There is no problem in your code, the only problem is that in schema definition you have Type, but it must be type.
If you update your ParticipantSchema like this, it will work:
var ParticipantSchema = new Schema({
nom: { type: String },
prenom: { type: String },
email: { type: String }
});
You are using another Schema in the Array. This results in so-called subdocuments (https://mongoosejs.com/docs/subdocs.html). Mongoose does not populate subdocuments by default. So all you see is just the _id. You can use the populate method to see all subdocuments in detail. ( https://mongoosejs.com/docs/populate.html ) .
Example :
Competition.
find({}).
populate('participant').
exec(function (err, comps) {
//
});
You can either use populate on the Model or on the Document. For populating a document, take a look at https://mongoosejs.com/docs/api.html#document_Document-populate . There is also a auto-populate plugin available via npm but in most cases it's not necessary : https://www.npmjs.com/package/mongoose-autopopulate .

Mongoose count certain element in an embedded documents array

I am using mongoose 4.6.3.
I have the following schema :
var mongoose = require('mongoose');
var User = require('./User');
var TicketSchema = new mongoose.Schema({
user : { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
},
{
timestamps: true
});
var DrawSchema = new mongoose.Schema({
...
max_ticket_per_user : { type : Number, required: true },
tickets: [TicketSchema]
});
module.exports = mongoose.model('Draw', DrawSchema);
How can I count the embedded documents of a certain User ObjectId(user field in TicketSchema) in a Draw's tickets(tickets field in DrawSchema) ?
I want to count the tickets of a user for a single draw.
Would it be better to change my schema design ?
Thanks
You can use the aggregation framework taking advantage of the $filter and $size operators to get the filtered array with elements that match the user id and its size respectively which will subsequently give you the count.
For an single draw, consider adding a $match pipeline operator as your initial step with the _id query to filter the documents in the collection.
Consider running the following aggregation pipeline to get the desired result:
Draw.aggregate([
{ "$match": { "_id": drawId } },
{
"$project": {
"ticketsCount": {
"$size": {
"$filter": {
"input": "$tickets",
"as": "item",
"cond": { "$eq": [ "$$item.user", userId ] }
}
}
}
}
}
]).exec(function(err, result) {
console.log(result);
});
You can pass the .count() deep parameters like any other query object:
Draw.count({'tickets.user._id' : userId}, console.log);
Make sure the userId variable is an ObjectId. If it's a string, do this:
const ObjectId = require('mongoose').Types.ObjectId;
let userId = new ObjectId(incomingStringId);

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.

Get result as an array instead of documents in mongodb for an attribute

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
})

Resources