Mongoose - Remove duplicates from query results - node.js

Suppose I have the two following schemas:
var SchemaOne = new mongoose.Schema({
created_at: { type: Date },
schemaTwo: { type: mongoose.Schema.Types.ObjectId, ref: 'SchemaTwo' },
ancestor: { type: mongoose.Schema.Types.ObjectId, ref: 'SchemaOne' }
});
var SchemaTwo = new mongoose.Schema({
headline: { type: String, required: true }
});
What I would like to do is: for every SchemaOne document with the same ancestor as one provided, return the SchemaTwo's headline to which they are associated (if any), taking into account that the results from the query should not return any duplicates and should be limited to 15 results sorted by descending order of SchemaOne's created_at field.
I started doing the following:
SchemaOne
.find({ 'ancestor': ancestor })
.sort({ 'created_at': -1 })
.populate({ path: 'schemaTwo', select: 'headline', options: { limit: 15 } })
.exec(function(err, docs) {
// do something with the results
});
But by doing so, I will still get duplicated results, i.e., I will have multiple SchemaOne documents associated with the same SchemaTwo document.
Can you give me a helping hand on how to solve this?

Using mongoose aggregate method and the async library, I managed to get it to work the way I want and need:
SchemaOne.aggregate([
{ $match: { $and: [{'ancestor': ancestor }, { 'schemaTwo': { $exists: true } }] } },
{ $group: { _id: '$schemaTwo' } },
{ $limit: 15 },
{ $sort : { 'created_at': -1 } }
],
function(err, results) {
// do something with err
async.map(results, function(doc, callback) {
SchemaTwo.findById(doc, function(err, result) {
if(err) { return callback(err, null); }
return callback(null, result);
});
}, function(err, docs) {
// do something with err
// here I have my results the way I want
});
});

Related

How do I return all documents that contain a relationship of a particular ID in Mongoose?

I want to search and return all 'plays' based on if a relationship exists in the 'payees' array using mongoose and Node.js.
Here is the schema:
const playSchema = new Schema({
streamer: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
song: { type: mongoose.Schema.Types.ObjectId, ref: 'Song' },
revenue: { type: Number },
createdAt: { type: Date },
payees: [ { type: mongoose.Schema.Types.ObjectId, ref: 'User' } ]
});
And here is what I am trying to do (just an example):
Play.aggregate([{ $match: { payees: { req.user.id } } }]);
You can use the $elemMatch operator.
The $elemMatch operator matches documents that contain an array field
with at least one element that matches all the specified query
criteria.
Playground
Sample route:
router.get("/plays", async (req, res) => {
const userId = req.user.id;
try {
let docs = await Play.find({
payees: {
$elemMatch: {
$eq: userId
}
}
});
res.send(docs);
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});

how to calculate the sum using aggregate, match and group in mongoose

I have this model, and I want to calulate the sum of the amounts with selecting status and eventId.
I use $aggregate, $match and $group
Model
var moneySchema = new mongoose.Schema({
Id:Number,
Amount: {type: Float},
Status: Number,
EventId: Number
}, {
timestamps: true
}, {
collection: 'e_money'
});
moneySchema.plugin(autoIncrement.plugin, 'e_money');
module.exports = mongoose.model('e_money', moneySchema);
Function
getTotalAmount: function (id,callback) {
var model = require("./Schema/e_money");
model
.aggregate([{
$match: {
Status: 1,EventId:id
}
}, {
$group:{
_id: null,
sum: {
$sum: "$Amount"
}
}
}])
.exec(function (err, doc) {
if (err) {
callback(err);
} else {
callback(doc);
}
})
};
But my function returns an empty array. Is there any solution ?

Error:Can't canonicalize query: BadValue Unsupported projection option

User Schema
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false},
favouriteid:[{eventid:String}]
});
Event Schema
var EventSchema=new Schema({
name:String,
location:{ type:String },
description:{type:String },
price: String,
rating: {
value: String,
count: {type: String, default: 10},
userrating: [{
uservalue: String,
userid: String
}]
},
imageurl:[String],
userimageurl:[String],
reviews:[{ userid:String,
username: String,
comment:String}]
});
POST METHOD to push the value of userid and uservalue in Event Schema.
api.post('/rateevent', function (req, res) {
var userid = req.body.userid;
var uservalue = req.body.uservalue;
var eventid = req.body.eventid;
Event.findById({_id: eventid},
{$push: {rating: {userrating: {uservalue: uservalue, userid: userid}}}},
{upsert: true},
function (err, events) {
if (err) {
res.send(err);
return;
}
else {
calculaterating(events);
}
});
function calculaterating(event) {
event.rating.count++;
event.rating.value = (event.rating.value * (event.rating.count - 1) + uservalue) / event.rating.count;
res.json("rating updated");
}
});
It is showing the following error:
{
"name": "MongoError",
"message": "Can't canonicalize query: BadValue Unsupported projection option: $push: { rating: { userrating: { uservalue: \"5\", userid: \"56593f3657e27af8245735d7\" } } }",
"$err": "Can't canonicalize query: BadValue Unsupported projection option: $push: { rating: { userrating: { uservalue: \"5\", userid: \"56593f3657e27af8245735d7\" } } }",
"code": 17287
}
Is the post method not correct? I have seen other mongodb documents but not able to find this type of thing. I am new to node js. Help me.
It should be Event.update instead of Event.findById, Also your push operation looks wrong. It should be like this:
Event.findOneAndUpdate(
{_id: eventid},
{$push: {'rating.userrating': {uservalue: uservalue, userid: userid}}},
{new: true},
function (err, events) {
if (err) {
res.send(err);
return;
}
else {
if(events.length > 0){
calculaterating(events);
}
else {
res.json({msg: "Nothing to update"});
}
}
});
function calculaterating(event) {
event = event[0]; //get the object from array
event.rating.count++;
event.rating.value = (event.rating.value * (event.rating.count - 1) + uservalue) / event.rating.count;
Event.update(
{_id: eventid},
{$set: {
'rating.count': event.rating.count,
'rating.value': event.rating.value
}},
function(err, response){
if (err) {
res.send(err);
return;
}
else {
res.json({msg: "rating updated"});
}
});
}
In events variable you will get the document that was updated in the new state. If you had passed {new: false} you will get the document as it was before the update.
in MY case
i was using the wrong method like below i was updating the record by
findOne , that can`t be possible , in my case , thats why issues
occurs
Solution: if you want to update the record , use .update() method,
and if you want to find records , then you can use .find() , .findOne() , don`t mismatch
domain.Cart.findOne({
UserId: req.body.UserId,
shopId: req.body.shopId,
},
{ $addToSet: { "productDetails": _productDetails } }
).exec(function (err, results) {
console.log(err, results)
callback(null, {
result: results,
msg: "productCount has been updated"
})
})

Mongodb - aggregate by embedded document id overwrite the outer document _id

I have this kind of 'comment' model:
{ _id: <comment-id>,
user: {
id: { type: Schema.ObjectId, ref: 'User', required: true },
name: String
},
sharedToUsers: [{ type: Schema.ObjectId, ref: 'User' }],
repliedToUsers: [{ type: Schema.ObjectId, ref: 'User' }],
}
And I want to query for all comments which pass the following conditions:
sharedToUsers array is empty
repliedToUsers array is empty
But also, I want the result to contain only 1 comment (the latest comment) per user by the user id.
I've tried to create this aggregate (Node.js, mongoose):
Comment.aggregate(
{ $match: { "sharedToUsers": [], "repliedToUsers": [] } },
{
$group: {
_id: "$user.id",
user: { $first: "$user" },
}
},
function (err, result) {
console.log(result);
if (!err) {
res.send(result);
} else {
res.status(500).send({err: err});
}
});
It is actually working, but the serious problem is that the results comments _id field is been overwritten by the nested user _id.
How can I keep the aggregate working but not to overwrite the original comment _id field?
Thanks
Ok, I have a solution.
All I wanted is to group by _id but to return the result documents with their _id field (which is been overwritten when using $group operator).
What I did is like wdberkley said, I've added comment_id : { "$first" : "$_id" } but then I wanted not to return the comment_id field (because it doesn't fit my model) so I've created a $project which put the comment_id in the regular _id field.
This is basically how it looks:
Comment.aggregate(
{
$match: {
"sharedToUsers": [], "repliedToUsers": []
}
},
{
$group: {
comment_id: { $last: "$_id" },
_id: "$user.id",
content: { $last: "$content" },
urlId: { $last: "$urlId" },
user: { $last: "$user" }
}
},
{
$project: {
_id: "$comment_id",
content: "$content",
urlId: "$urlId",
user: "$user"
}
},
{ $skip: parsedFromIndex },
{ $limit: (parsedNumOfComments - parsedFromIndex) },
function (err, result) {
console.log(result);
if (!err) {
Comment.populate(result, { path: "urlId"}, function(err, comments) {
if (!err) {
res.send(comments);
} else {
res.status(500).send({err: err});
}
});
} else {
res.status(500).send({err: err});
}
});
thanks wdbkerkley!

Updating mongoose schema fields concurrently

I'm trying to update a mongoose schema. Basically I have two api's '/follow/:user_id' and '/unfollow/:user_id'. What I'm trying to achieve is whenever user A follows user B , user B followers field in mongoose will increment as one.
As for now I managed to get only following fields increment by one but not the followers fields.
schema.js
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
followersCount: Number,
followingCount: Number
});
Updated version: I tried my solution, but whenever I post it, it just fetching the data ( I tried the api's on POSTMAN chrome app ).
api.js
// follow a user
apiRouter.post('/follow/:user_id', function(req, res) {
// find a current user that has logged in
User.update(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err) {
if (err) {
res.send(err);
return;
}
User.update(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}
), function(err) {
if(err) return res.send(err);
res.json({ message: "Successfully Followed!" });
}
});
});
These codes only manage to increment the user's following fields, and without duplication. How do I update logged in user's following fields and as well as other user's followers fields at the same time?
Updated version: it keeps fetching the data.
May be this is how you want to. Instead of using update, you can also use findOneAndUpdate from Mongoose queries.
apiRouter.post('/follow/:user_id', function(req, res) {
User.findOneAndUpdate(
{
_id: req.decoded.id
},
{
$push: {following: req.params.user_id},
$inc: {followingCount: 1}
},
function (err, user) {
if (err)
res.send(err);
User.findOneAndUpdate(
{
_id: req.params.user_id
},
{
$push: {followers: req.decoded.id},
$inc: {followersCount: 1}
},
function (err, anotherUser) {
if (err)
res.send(err);
res.json({message: "Successfully Followed!"})
});
});
}
If you are not sure about it is updated or not, you can just use console.log() for both user and anotherUser variables to see changes.

Resources