Mongodb array $push and $pull - mongodb

I was looking to pull some value from array and simultaneously trying to update it.
userSchema.statics.experience = function (id,xper,delet,callback) {
var update = {
$pull:{
'profile.experience' : delet
},
$push: {
'profile.experience': xper
}
};
this.findByIdAndUpdate(id,update,{ 'new': true},function(err,doc) {
if (err) {
callback(err);
} else if(doc){
callback(null,doc);
}
});
};
i was getting error like:
MongoError: exception: Cannot update 'profile.experience' and 'profile.experience' at the same time

I found this explanation:
The issue is that MongoDB doesn’t allow multiple operations on the
same property in the same update call. This means that the two
operations must happen in two individually atomic operations.
And you can read that posts:
Pull and addtoset at the same time with mongo
multiple mongo update operator in a single statement?

In case you need replace one array value to another, you can use arrayFilters for update.
(at least, present in mongo 4.2.1).
db.your_collection.update(
{ "_id": ObjectId("your_24_byte_length_id") },
{ "$set": { "profile.experience.$[elem]": "new_value" } },
{ "arrayFilters": [ { "elem": { "$eq": "old_value" } } ], "multi": true }
)
This will replace all "old_value" array elements with "new_value".

Starting from MongoDB 4.2
You can try to update the array using an aggregation pipeline.
this.updateOne(
{ _id: id },
[
{
$set: {
"profile.experience": {
$concatArrays: [
{
$filter: {
input: "$profile.experience",
cond: { $ne: ["$$this", delet] },
},
},
[xper],
],
},
},
},
]
);
Following, a mongoplayground doing the work:
https://mongoplayground.net/p/m1C1LnHc0Ge
OBS: With mongo regular update query it is not possible.

Since Mongo 4.2 findAndModify supports aggregation pipeline which will allow atomically moving elements between arrays within the same document. findAndModify also allows you to return the modified document (necessary to see which array elements were actually moved around).
The following includes examples of:
moving all elements from one array onto the end of a different array
"pop" one element of an array and "push" it to another array
To run the examples, you will need the following data:
db.test.insertMany( [
{
"_id": ObjectId("6d792d6a756963792d696441"),
"A": [ "8", "9" ],
"B": [ "7" ]
},
{
"_id": ObjectId("6d792d6a756963792d696442"),
"A": [ "1", "2", "3", "4" ],
"B": [ ]
}
]);
Example 1 - Empty array A by moving it into array B:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696441") },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, "$A" ] } } },
{ $set: { "A": [] } }
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696441"
},
"A": [],
"B": [
"7",
"8",
"9"
]
}
Example 2.a - Pop element from array A and push it onto array B
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"A": {$exists: true, $type: "array", $ne: [] }},
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ { $first: "$A" } ] ] } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696442"
},
"A": [
"2",
"3",
"4"
],
"B": [
"1"
]
}
Example 2.b - Pop element from array A and push it onto array B but in two steps with a temporary placeholder:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: false } },
update: [
{ $set: { "temp": { $first: "$A" } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
// do what you need to do with "temp"
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: true } },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ "$temp" ] ] } } },
{ $unset: "temp" }
],
new: true
});

Related

Add Aggregate field in MongoDB pipeline depending on all elements of an array

Given the following documents in a collection:
[{
"_id": {
"$oid": "63f06283b80a395adf27780d"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": true
}
]
},{
"_id": {
"$oid": "63f06283b80a395adf27780e"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": false
}
]
}]
I would like to create an aggregateField in each document that does the following: If the suppliers array has at least 1 element and every element in that has the duesPaid field == true, then add a field to the document suppliersPaid = true. Otherwise add suppliersPaid = false. The resulting documents from the pipeline should look like this:
[{
"_id": {
"$oid": "63f06283b80a395adf27780d"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": true
}
],
"suppliersPaid": true,
},{
"_id": {
"$oid": "63f06283b80a395adf27780e"
},
"suppliers": [
{
"name": "S1",
"duesPaid": true
},
{
"name": "S2",
"duesPaid": false
}
],
"suppliersPaid": false,
}]
I have tried the following pipeline:
[{$addFields: {
suppliersPaid: {
$and: [
{ $gte: [{ $size: "$suppliers" }, 1] },
{
suppliers: {
$not: {
$elemMatch: { duesPaid: false },
},
},
},
],
},
}}]
and I get the following error: Invalid $addFields :: caused by :: Unrecognized expression '$elemMatch'
I've tried to eliminate the reliance on $elemMatch per the docs https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#single-query-condition as such:
[{$addFields: {
suppliersPaid: {
$and: [
{ $gte: [{ $size: "$suppliers" }, 1] },
{
suppliers: {
$not: {
duesPaid: false
},
},
},
],
},
}}]
But this yields the incorrect result of setting suppliersPaid to true for both documents, which is incorrect.
Note: I would like to avoid using any sort of JS in this code i.e. no $where operators.
For the second condition:
$eq - Compare the result from 1.1 to return an empty array.
1.1. $filter - Filter the documents from suppliers containing { duesPaid: false }.
db.collection.aggregate([
{
$addFields: {
suppliersPaid: {
$and: [
{
$gte: [
{
$size: "$suppliers"
},
1
]
},
{
$eq: [
{
$filter: {
input: "$suppliers",
cond: {
$eq: [
"$$this.duesPaid",
false
]
}
}
},
[]
]
}
]
}
}
}
])
Demo # Mongo Playground

Unable to match objects in array

I have several documents like the following and I'm trying to retrieve the documents where the first element of the scores array was created within the past 24hrs:
[
{
"id": 1,
"scores": [
{
"score": 1,
created_at: ISODate("2022-11-19T00:05:00.000+00:00")
},
{
"score": 2,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
{
"id": 2,
"scores": [
{
"score": 3,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
},
{
"score": 5,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
]
This is the query:
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
"$scores.0.created_at",
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
https://mongoplayground.net/p/L1jI10efWGL
However, nothing is returned. Does anyone know what might be wrong?
Instead of using $scores.0.created_at, use $getField to get the value of created_at from the first element of the scores array.
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
{
$getField: {
field: "created_at",
input: {
$first: "$scores"
}
}
},
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
Demo # Mongo Playground

Remove Some array elements based on a condition and update size of array as one more filed in mongo

I have following collection
[
{
"_id": ObjectId("57315ba4846dd82425ca2408"),
"myarray": [
{
"point": 5,
"userId": "570ca5e48dbe673802c2d035"
},
{
"point": 2,
"userId": "613ca5e48dbe673802c2d521"
},
{
"point": 4,
"userId": "570ca5e48dbe673802c2d045"
},
{
"point": 4,
"userId": "570ca5e48dbe473802c2d035"
}
]
}
]
I have a collection like above and I want to remove some objects inside array based on userID condition and after removing I have to update one field in mongo with size of array
I'm trying with the below query where removing array elements is working as excepted but array size is not updating properly
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
{
$pull: {
"myarray": {
userId: {
$in: [
"570ca5e48dbe673802c2d035",
"613ca5e48dbe673802c2d521"
]
}
}
},
"$set": {
profilecount: {
$size: "$myarray"
}
}
})
to see result of query please click this link and run query https://mongoplayground.net/p/FtMk7ymacr3
One option is using an update with a pipeline:
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408")
},
[{
$set: {
"myarray": {
$filter: {
input: "$myarray",
cond: {
$not: {
$in: [
"$$this.userId",
["570ca5e48dbe673802c2d035", "613ca5e48dbe673802c2d521"]
]
}
}
}
}
}
},
{$set: {profilecount: {$size: "$myarray"}}}
])
See how it works on the playground example

set all fields in subdocument to false, then set the second one to true in a single query

Suppose I have the following the document structure.
[
{
"_id": 1,
"depots": [
{
"_id": 1,
"isFavourite": true
},
{
"_id": 2,
"isFavourite": false
},
{
"_id": 3,
"isFavourite": true
},
{
"_id": 4,
"isFavourite": false
}
]
}
]
I want to write a single update query which filters for the document with _id: 1 and first sets every isFavourite value to false and then sets the second isFavourite value (or any specified index) to true.
The resulting document should look like this.
[
{
"_id": 1,
"depots": [
{
"_id": 1,
"isFavourite": false
},
{
"_id": 2,
"isFavourite": true
},
{
"_id": 3,
"isFavourite": false
},
{
"_id": 4,
"isFavourite": false
}
]
}
]
What I tried:
db.collection.update({
_id: 1
},
[
{
"$set": {
"depots.isFavourite": false
}
},
{
"$set": {
"depots.2.isFavourite": true
}
}
])
Yet strangely this does not work. See the linked playground for the result of this query.
Mongo Playground
Using the index as a dot notation only works when the update is not a pipeline.
One option is to "rebuild" the array using $reduce, which allow us to use the size of the currently built array to find the item with the requested index, and then $mergeObjects it with the updated field and value:
db.collection.update(
{_id: 1},
[
{$set: {"depots.isFavourite": false}},
{$set: {
depots: {
$reduce: {
input: "$depots",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{$cond: [
{$eq: [{$size: "$$value"}, requestedIndex]},
{$mergeObjects: ["$$this", {isFavourite: true}]},
"$$this"
]
}
]
]
}
}
}
}
}
])
See how it works on the playground example
What do you think about this:
db.collection.update({
_id: 1
},
{
"$set": {
"depots.$[y].isFavourite": false,
"depots.$[x].isFavourite": true
}
},
{
arrayFilters: [
{
"x._id": 2
},
{
"y._id": {
$ne: 2
}
}
],
"multi": true
})
Explained:
Set two arrayFilters x & y that match the two conditions ...
Playground

MongoDB Aggregation: How to check if an object containing multiple properties exists in an array

I have an array of objects and I want to check if there is an object that matches multiple properties. I have tried using $in and $and but it does not work the way I want it to.
Here is my current implementation.
I have an array like
"choices": [
{
"name": "choiceA",
"id": 0,
"l": "k"
},
{
"name": "choiceB",
"id": 1,
"l": "j"
},
{
"name": "choiceC",
"id": 2,
"l": "l"
}
]
I am trying to write aggregation code that can check if there is an object that contains both "id":2 and "l":"j" properties. My current implementation checks if there is an object containing the first property then checks if there is an object containing the second one.
How can I get my desired results?
Below, see my aggregation query. The full code is here
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$and: [
{
$in: [
2,
"$choices.id"
]
},
{
$in: [
"j",
"$choices.l"
]
}
]
},
}
}
])
The above query returns true yet there is no object in the array both of the properties id:2 and "l":"J". I know the code works as expected. How can I get my desired results?
You want to use something like $elemMatch
db.collection.find({
choices: {
$elemMatch: {
id: 2,
l: "j"
}
}
})
MongoPlayground
EDIT
In an aggregation $project stage I would use $filter
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$gt: [
{
$size: {
$filter: {
input: "$choices",
as: "choice",
cond: {
$and: [
{
$eq: [
"$$choice.id",
2
]
},
{
$eq: [
"$$choice.l",
"j"
]
}
]
}
}
}
},
0
]
}
}
}
])
MongoPlayground