Mongoose $in operator only updating one document - javascript

I am having trouble with the "$in" operator in Mongoose. At a high level I have a User schema, and one of the fields is an array of a Card schema. Within the Card schema there is a 'score' field. I would like to update the 'score' field based on a list of Card ids. Here is what I am trying to use:
User.updateMany(
{
"_id": userId,
"cards._id": {
"$in": cardIds
}
},
{ $inc: {"cards.$.score": 1 }},
(err) => {console.log(err)}
)
When I run this code, only the first Card in the cardIds array is updated instead of all of them. Any idea why this isn't working? Thanks.

You need to use arrayFilters in .updateMany() , Try this query :
User.updateMany({
"_id": userId
},
{ $inc: { "cards.$[element].score": 1 } },
{ arrayFilters: [{ "element._id": { "$in": cardIds } }] },
(err) => {}
)

Related

Performing a complex query in mongoDB

**Hi guys, I really need your help. In mongoDB, I have a users collection where each user has a "friends" array, consisting of other users' _id s.
I am finding a user, getting the array of that user's friends and trying to find every other user with the _id s that are in that friends array;
Then I'm trying to check if the found users have "notifications" array's length more than or equal to 50. If that's the case, I want to remove the first element from that array and then push a document in it, else I want to just push a document in the "notifications" array.
But it doesn't work, I am trying to do this:**
User.updateMany(
{
'_id': {
$in: usersFriends.map(userId => new ObjectId(userId))
}
},
{
$cond: [
{ $gte: [ {$size: ['$notifications']}, 50 ] },
{
$pop: {'$notifications': 1},
$push: {"$notifications": NOTIF}
},
{ $push: {"$notifications": NOTIF} }
],
}
)
.then(DATA => {
res.status(201).json({
data: data,
DATA: DATA
})
})
Please someone help me.

Using Mongo Aggregation to Get Distinct Values Based on Field

So I have a collection called Cars that have some fields that are the same, but I want to be able to only get one of the documents based on that field.
[
{
_id:'12345',
model:'Honda'
},
{
_id:'12346',
model:'Honda'
},
{
_id:'12347',
model:'Honda'
},
{
_id:'12348',
model:'Toyota'
},
{
_id:'12349',
model:'Volkswagen'
},
{
_id:'12349',
model:'Volkswagen'
},
]
So here, I want to be able to get the distinct document based on the model field. I just want one document per model field.
first, you want to update mongoose v3 or up
the solution is to use the distinct function
Cars.find().distinct('model', function(error, models) {});
I hope it will help you :)
Use $first to pick a document in $group stage. Then do some wrangling with $unwind and $replaceRoot to retrieve the document.
db.collection.aggregate([
{
$group: {
_id: "$model",
doc: {
$first: "$$ROOT"
}
}
},
{
"$unwind": "$doc"
},
{
"$replaceRoot": {
"newRoot": "$doc"
}
}
])
Here is the Mongo Playground for your reference.

Return only a subdocument from document in mongoose

I am working on an app that uses MongoDB (I use Mongoose) as its database.
I have a question, suppose I have this kind of schema:
[{
"user_id":"2328292073"
"username":"Bob",
"subscriptions":[
{
"id":"38271281,
"payments":[
{
"id":"00001",
"amount":"1900"
},
{
"id":"00002",
"amount":"2000"
},
{
"id":"00003",
"amount":"3000"
}
]
}
]
}]
In my case I want to get the payments array for subscription with id = '38271281' of user with id '2328292073', but I just want to retrieve the payment array, nothing else
My query is the following:
Mongoose.findOne({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
"subscriptions.payments": 1
})
But I get the entire document of subscriptions. How can i get the payment array only?
you can try using unwind if you want filteration from db only.
Mongoose.aggregate([
{
'$match': {
'user_id': '2328292093'
}
}, {
'$unwind': {
'path': '$subscriptions'
}
}, {
'$match': {
'subscriptions.id': '38271281'
}
}
])
if you will have multiple documents having same subscription id then you have to group it .
using code level filter function can also be one another approach to do this .
You can try aggregation operators in projection in find method or also use aggregation method,
$reduce to iterate loop of subscriptions and check the condition if id matched then return payment array
db.collection.find({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
payments: {
$reduce: {
input: "$subscriptions",
initialValue: [],
in: {
$cond: [
{ $eq: ["$$this.id", "38271281"] },
"$$this.payments",
"$$value"
]
}
}
}
})
Playground

Issues Deleting a item in a user Model (array), not sure how to save the result

I have a user with various post ID's inside of my mongodb database, I am using mongoose to talk to it.
this is the user object
[
{
"premium": true,
"max_posts": 55,
"posts_made": 52,
"posts": [
"5e10046c0be4f92228f6f532",
"5e1005a9dceb1344241c74c5",
"5e100753a6cfcb44d8f1fa09",
"5e1007bea6cfcb44d8f1fa0a",
"5e1008149324aa1d002a43be",
"5e1009562826a308a0812e92",
"5e100a625e6fcb2c90a07bec",
"5e157143536d6e04a80651bd",
"5e1e320dc749f23b189ccef7",
"5e1e3273546d55384c3a975c",
"5e1e340183d0b0080816cedd",
"5e1e368bd921f3194c22b3d2",
"5e1e3732d921f3194c22b3d3",
"5e1e3a6f9b3017189cff0fe2",
"5e1e3c1a8c38f11c60354052",
"5e1e9ab208d0a5416828d0a3"
],
"_id": "5e0fe3f33c2edb2f5824ddf2",
"email": "user#gmail.com",
"createdAt": "2020-01-04T01:01:39.840Z",
"updatedAt": "2020-01-15T04:53:08.987Z",
"__v": 16
}
]
So, I make a request to the database using express, and I try to filter the array, using an id of one post, then I ask express to save that modified user model...
router.delete('/testing', (req,res,next) =>{
userModel.findOne({ email: req.body.author }, function(error, user) {
user.posts.filter(item => item != req.body.postid)
user.save((err) => {
console.log(err);
});
res.json(user)
});
});
my request in postman :
As you can see, the item is still there in that array... checking the console.log.
Please Advise as I am having doubts, thanks.
You no need to find & update the document of user, which makes two DB calls, plus .filter(), try this :
router.delete('/testing', (req, res, next) => {
userModel.findOneAndUpdate({ email: req.body.author},
{ $pull: { "posts": req.body.postid } }, { new: true }, function (error, user) {
res.json(user);
});
});
Here we're using .findOneAndUpdate() as it returns the updated user document which you need to send back.
This is how I would do it using $pull operator.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
userModel.update(
{email: req.body.author},
{ $pull: { posts: { $in: [ req.body.postid ] } } },
{ multi: true }
)
For now it seems like you are passing single postid in request body. In future if you needed to delete multiple posts at the same time, you can use the same query by just replacing { $in: [ req.body.postid ] } with { $in: [ ...req.body.postid ] }

Mongoose — Create new property with $sum using aggregation

I'm trying to add a new field in all documents that contain the sum of an array of numbers.
Here is the Schema (removed irrelevant fields for brevity):
var PollSchema = new Schema(
{
votes: [Number]
}
);
I establish the model:
PollModel = mongoose.model('Poll', PollSchema);
And I use aggregation to create a new field that contains the sum of the votes array.
PollModel.aggregate([
{
$project: {
totalVotes: { $sum: "$votes"}
}
}
]);
When I startup my server, I get no errors; however, the totalVotes field hasn't been created. I used this documentation to help me. It similarly uses the $sum operator and I did it exactly like the documentation illustrates, but no results.
MongoDb aggregation doesn't save its result into database. You just get the result of aggregation inline within a callback.
So after aggregation you would need to do multi update to your database:
PollModel.aggregate([
{
$project: { totalVotes: { $sum: "$votes"} }
}]).exec( function(err, docs){
// bulk is used for updating all records within a single query
var bulk = PollModel.collection.initializeUnorderedBulkOp();
// add all update operations to bulk
docs.forEach(function(doc){
bulk.find({_id: doc._id}).update({$set: {totalVotes: doc.totalVotes}});
});
// execute all bulk operations
bulk.execute(function(err) {
});
})
});
Unfortunately this does not work as you think it does because "votes" is actually an array of values to start with, and then secondly because $sum is an accumulator operator for usage in the $group pipeline stage only.
So in order for you to get the total of the array as another property, first you must $unwind the array and then $group together on the document key to $sum the total of the elements:
PostModel.aggregate(
[
{ "$unwind": "$votes" },
{ "$group": {
"_id": "$_id",
"anotherField": { "$first": "$anotherField" },
"totalVotes": { "$sum": "$votes" }
}}
],
function(err,results) {
}
);
Also noting here another accumulator in $first would be necessary for each additional field you want in results as $group and $project only return the fields you ask for.
Generally though this is better to keep as a property within each document for performance reasons, as it's faster than using aggregate. So to do this just increment a total each time you $push to an array by also using $inc:
PostModel.update(
{ "_id": id },
{
"$push": { "votes": 5 },
"$inc": { "totalVotes": 5 }
},
function(err,numAffected) {
}
);
In that way the "totalVotes" field is always ready to use without the overhead of needing to deconstruct the array and sum the values for each document.
You don't have totalVotes in your schema. Just try the below code.
var PollSchema = new Schema(
{
votes: [Number],
totalVotes: Number
}
);
PollModel.aggregate([
{
$project: {
totalVotes: { $sum: "$votes"}
}
}
]);
or
resultData.toJSON();
#Blakes Seven and #Volodymyr Synytskyi helped me arrive to my solution! I also found this documentation particularly helpful.
PollModel.aggregate(
[
{ '$unwind': '$votes' },
{ '$group': {
'_id': '$_id',
'totalVotes': { '$sum': '$votes' }
}}
],
function(err,results) {
// console.log(results);
results.forEach(function(result){
var conditions = { _id: result._id },
update = { totalVotes: result.totalVotes },
options = { multi: true };
PollModel.update(conditions, update, options, callback);
function callback (err, numAffected) {
if(err) {
console.error(err);
return;
} else {
// console.log(numAffected);
}
}
});
}
);

Categories

Resources