I want to pull the whole nested array object if the object contains a specified string for a specific key. I'm using mongoose with nodejs:
DB before deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "theValue" }, { arrayKey: "anotherValue" } ]
}
}
DB after deletion:
{ _id : 1234
fallBackData: {
nestedKey: [ { arrayKey: "anotherValue" } ]
}
}
I took a look at How can I pull nested object value in mongodb and $pullAll Mongo DB docs ,tried the following, but none worked:
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
fallbackData: { nestedKey: [{ arrayKey: "theValue"}] },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : { arrayKey: "theValue" },
},
}
);
const ad = await Ad.updateOne(
{ _id: 1234 },
{
$pullAll: {
"fallbackData.$.nestedKey" : [{ arrayKey: "theValue"}],
},
}
);
The query return value is the following, but the object in the array is not deleted:
{
acknowledged: true,
modifiedCount: 1,
upsertedId: null,
upsertedCount: 0,
matchedCount: 1
}
You can achieve this by changing a little
playground
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
"$unset": {
"fallBackData": "nestedKey"
}
})
You cannot add matching conditions with $pullAll related to Array
$pullAll expects an array of matching values to be removed
You can do $pull instead of $unset but $pull results empty array
The answer from #Gibbs removes the array completely, while you asked only to pull the specific object from it.
Here is the simple solution with $pull command:
db.collection.update({
_id: 1234,
"fallBackData.nestedKey": {
$elemMatch: {
"arrayKey": "theValue"
}
}
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
Mongo Playground link
pullAll matches the entire object (or objects) to be pulled. In other words, if the input was:
[
{
_id: 1234,
fallBackData: {
nestedKey: [
{
arrayKey: "theValue",
foo: "bar"
},
{
arrayKey: "anotherValue",
foo: "baz"
}
]
}
}
]
Then you need to do:
db.collection.update({
_id: 1234
},
{
$pullAll: {
"fallBackData.nestedKey": [
{
arrayKey: "theValue",
foo: "bar"
}
]
}
})
See https://mongoplayground.net/p/iJkfqIWK0JO.
On the other hand, $pull can match objects based on a condition, so you can pull from the array based on the specific key you want to match. So, given the same input as above, you would simply do:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
arrayKey: "theValue"
}
}
})
See https://mongoplayground.net/p/MOuSmh7Ir7b.
The conditions can be more complex than a simple field value match. For example to match and pull multiple keys:
db.collection.update({
_id: 1234
},
{
$pull: {
"fallBackData.nestedKey": {
"arrayKey": {
$in: [
"theValue",
"anotherValue"
]
}
}
}
})
See https://mongoplayground.net/p/iSMVxp7a9TX.
Related
I am using Mongo 5.0.6 and have a document structured like this:
[
{
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
I need to query data based on the properties keys, this works fine in my mongo playground: https://mongoplayground.net/p/lEiLeStWGTn when I query for key that contain mark.
db.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
])
However when I use it through my mongoose object I get [] even so the data is exactly as in playground. I am using locally the latest mongoose6 and mondodb5.0.6. I get the same [] result when I run the query against a mongodb.com hosted database. What could be the problem?
My javascript query below, I tried both using mongoose and the driver directly, as shown below:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
]);
for await (const doc of data) {
console.log(doc);
}
Always gives me:
{ properties: {} }
When I take out the $addFields and $project like this:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
}
]);
for await (const doc of data) {
console.log(doc);
}
I get, so the data is there, but aggregation pipeline isn't working:
[
{
_id: "61b9f2f5d2a6021365aae6d6",
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
So the data is there. What am I missing? Do I need to write the query differently?
Example of the document:
{
postId:'232323',
post:'This is my first post',
commentsOnPost:[
{
commentId:'232323_8888',
comment:'Congrats',
repliesOnPost:[
{
replyId:'232323_8888_66666',
reply:'Thanks',
likesOnReply:['user1','user5','user3'],
}
]
}
]
}
I want to add userid in likesOnReply if users do not exist in likesOnReply, similarly remove userid from likesOnReply if exist.
I have tried like this but not working properly
await collection('post').findOneAndUpdate(
{
postId: postId,
'commentsOnPost.commentId': commentId,
'commentsOnPost.repliesOnPost.replyId': replyId
},
{
$push: { 'commentsOnPost.$[].repliesOnPost.$.likes': userid },
},
);
There is no straight way to do both the operation to pull or push in a single query,
There are 2 approaches,
1) Find and update using 2 queries:
use arrayFilters to updated nested array elements
$push to insert element
$pull to remove element
var post = await collection('post').findOne({
posted: postId,
ommentsOnPost: {
$elemMatch: {
commentId: commentId,
repliesOnPost: {
$elemMatch: {
replyId: replyId
likesOnReply: userid
}
}
}
}
});
var updateOperator = "$push";
// FOUND USER ID THEN DO REMOVE OPERATION
if (post) updateOperator = "$pull";
// QUERY
await collection('post').updateOne(
{ postId: postId },
{
[updateOperator]: {
"commentsOnPost.$[c].repliesOnPost.$[r].likesOnReply": userid
}
},
{
arrayFilters: [
{ "c.commentId": commentId },
{ "r.replyId": replyId }
]
}
)
Playground
2) Update with aggregation pipeline starting from MongoDB 4.2:
$map to iterate loop of commentsOnPost array check condition if commentId match then go to next process otherwise return existing object
$mergeObjects to merge current object with updated fields
$map to iterate loop of repliesOnPost array and check condition if replyId match then go to next process otherwise return an existing object
check condition for likesOnReply has userid then do remove using $filter otherwise insert using $concatArrays
await collection('post').findOneAndUpdate(
{ postId: "232323" },
[{
$set: {
commentsOnPost: {
$map: {
input: "$commentsOnPost",
in: {
$cond: [
{ $eq: ["$$this.commentId", commentId] },
{
$mergeObjects: [
"$$this",
{
repliesOnPost: {
$map: {
input: "$$this.repliesOnPost",
in: {
$cond: [
{ $eq: ["$$this.replyId", replyId] },
{
$mergeObjects: [
"$$this",
{
likesOnReply: {
$cond: [
{ $in: [userid, "$$this.likesOnReply"] },
{
$filter: {
input: "$$this.likesOnReply",
cond: { $ne: ["$$this", userid] }
}
},
{
$concatArrays: ["$$this.likesOnReply", [userid]]
}
]
}
}
]
},
"$$this"
]
}
}
}
}
]
},
"$$this"
]
}
}
}
}
}]
)
Playgorund
This is my data structure:
{
studentName: 'zzz',
teachers: [
{
teacherName: 'xxx',
feedbacks: []
}, {
teacherName: 'yyy',
feedbacks: []
}
]
}
Now I am trying to code a query to add an 'feedback' object to the 'feedbacks' array that belongs to the teacher named 'yyy'.
await collection.updateOne({
studentName: 'zzz',
teachers: {
$elemMatch: {
teacherName: {
$eq: 'yyy'
}
}
}
}, {
$push: {
'teachers.$.feedbacks': {}
}
})
The query part is faulty somehow. If I change '$' to 0 or 1, then the code works finally. Otherwise, the object cannot be pushed.
This update works fine, adds the string element "Nice work" to the teachers.feedbacks nested array.
db.test.updateOne(
{
studentName: "zzz",
"teachers.teacherName": "yyy"
},
{ $push: { "teachers.$.feedbacks" : "Nice work" } }
)
The $elemMatch syntax is not required, as the query condition for the array elements is for a single field.
The updated document:
{
"studentName" : "zzz",
"teachers" : [
{
"teacherName" : "xxx",
"feedbacks" : [ ]
},
{
"teacherName" : "yyy",
"feedbacks" : [
"Nice work"
]
}
]
}
Let's say I have a Collection with a field name and only "Sally" and "Bob" names exist. I want to group my results by this value. I'm currently getting my results and then using underscore to perform the group. But I should be able to do this with an aggregate.
The following is my code
const names = ['Bob', 'Sally'];
const docs = Collection.find({name: { $in: names}}, { fields: { name:1, age:1}, sort: { name:-1 } }).fetch();
//How can I do this part in the query above
const { Bob, Sally } = _.groupBy(docs, "name");
You can try,
$match your condition
$group by name and make array called fields and push name and age
$sort by _id means name
$replaceWith to replace object in root, $arrayToObject convert k, v format to object
const names = ['Bob', 'Sally'];
const docs = Collection.aggregate([
{ $match: { name: { $in: names } } },
{
$group: {
"_id": "$name",
fields: {
$push: {
name: "$name",
age: "$age"
}
}
}
},
{ $sort: { _id: -1 } },
{
$replaceWith: {
$arrayToObject: [
[
{
k: "$_id",
v: "$fields"
}
]
]
}
}
])
Playground
You can do something like this :
db.collection.aggregate([
{
$match: {
name: {
$in: names
}
}
},
{
$group: {
"_id": "$name",
age: {
$first: "$age"
}
}
},
{
$sort: {
name: -1
}
}
])
Try it here : MongoPlayground
I am trying to remove multiple objects that are in an array in mongoose. My Workout model look like this:
{
_id: 5e04068491a2d433007026cd,
exercises: [
{ _id: 5e0401b9dda7ea28a70e99ed, reps: '1', sets: '3' },
{ _id: 5e0401cadda7ea28a70e99ee, reps: '1', sets: '3' },
{ _id: 5e0401dbdda7ea28a70e99ef, reps: '1', sets: '3' }
]
}
I have an array of id's, named deletedExercises, these are the ids of the objects that I want removed from the exercise list. I am trying to loop through deletedExercise and remove any exercises that match the id of the deletedExercise item.
router.put("/:workoutId", (req, res)=>{
deletedOnes = req.body.exerciseId
deletedExercises = []
if(typeof deletedOnes === 'object'){
deletedOnes.forEach(item => {
deletedExercises.push(item)
})
} else {
deletedExercises.push(deletedOnes)
}
deletedExercises.forEach(item => {
Workout.findByIdAndUpdate( req.params.workoutId,
{ $pull: { exercises: { _id: item} } } )
});
You can simply delete exercises using the $in operator inside $pull like this:
router.put("/:workoutId", (req, res) => {
console.log(req.body.exerciseId); //[ '5e05c5306e964f0a549469b8', '5e05c5306e964f0a549469b6' ]
Workout.findByIdAndUpdate(
req.params.workoutId,
{
$pull: {
exercises: {
_id: {$in: req.body.exerciseId}
}
}
},
{ new: true }
)
.then(doc => {
res.send(doc);
})
.catch(err => {
console.log(err);
res.status(500).send("Error");
});
});
Let's say we have this workout with 3 exercises:
{
"_id": "5e05c5306e964f0a549469b5",
"exercises": [
{
"_id": "5e05c5306e964f0a549469b8",
"reps": 8,
"sets": 4
},
{
"_id": "5e05c5306e964f0a549469b7",
"reps": 10,
"sets": 3
},
{
"_id": "5e05c5306e964f0a549469b6",
"reps": 12,
"sets": 2
}
],
}
If we want to remove the exercises 5e05c5306e964f0a549469b8 and 5e05c5306e964f0a549469b6 for this 5e05c5306e964f0a549469b5 workout, we can send a PUT request with this body: (url must end something like this http://.../5e05c5306e964f0a549469b5)
{
"exerciseId": [
"5e05c5306e964f0a549469b8",
"5e05c5306e964f0a549469b6"
]
}
The response will be:
{
"_id": "5e05c5306e964f0a549469b5",
"exercises": [
{
"_id": "5e05c5306e964f0a549469b7",
"reps": 10,
"sets": 3
}
]
}
Hard to tell considering you're not saying what error you are getting, but my guess from looking at it is that you are comparing an ObjectId with a String, try to replace this line:
{ $pull: { exercises: { _id: item} } } )
with this:
{ $pull: { exercises: { _id: new ObjectId(item)} } } )
** EDIT **
you probably need to also convert the main ID you are searching for to an ObjectId:
Workout.findByIdAndUpdate( new ObjectId(req.params.workoutId),
{ $pull: { exercises: { _id: new ObjectId(item)} } } )