MongoDB - Increment a field using another array field - mongodb

I have a collection with the document of this structure:
{
"_id": {
"$oid": "63e8af476a3674484ea14888"
},
"my_id": 321123,
"version": 0,
"parameters": [
{"a": 1},
{"a": 1, "b": 2}
]
}
Using a RESTful API the user can change the version field to any number between 0 and len(parameters). Another endpoint let the user push an object to parameters and set version to the new len(parameters) - 1. So using the previous example, after using this endpoint we will get:
{
"_id": {
"$oid": "63e8af476a3674484ea14888"
},
"my_id": 321123,
"version": 2,
"parameters": [
{"a": 1},
{"a": 1, "b": 2},
{"a": 1, "b": 2, "c": 3}
]
}
I tried many things, but nothing seems to work. Here are examples of what I tried:
db.parameters.findOneAndUpdate({"my_id": 321123}, {"$inc":{version: parameters}, "$push":{"a": 1, "b": 2, "c": 3}}, upsert=true)
db.parameters.findOneAndUpdate({"my_id": 321123}, {"$inc":{version: {"$size": parameters}}, "$push":{"a": 1, "b": 2, "c": 3}}, upsert=true)

As your update requires referring to another field, you have to use the update query with the aggregation pipeline.
For the first scenario,
Add a new parameter object by combining it with the current parameters array via $concatArrays.
I don't think that increment is what you need, but rather update the version value via $set stage. An example as below is to compare the version param between the range of 0 and len(parameters), if true, then update with the version param, else update with the size of the parameters array.
db.parameters.update({
"my_id": 321123
},
[
{
"$set": {
parameters: {
"$concatArrays": [
"$parameters",
[
{
"a": 1,
"b": 2,
"c": 3
}
]
]
}
}
},
{
"$set": {
version: {
$cond: {
if: {
$and: [
{
$gt: [
2,
// version
0
]
},
{
$lt: [
2,
// version
{
$size: "$parameters"
}
]
}
]
},
then: 2,
else: {
$size: "$parameters"
}
}
}
},
}
],
{
upsert: true
})
Demo (1st scenario) # Mongo Playground
For the second scenario,
Add a new parameter object by combining it with the current parameters array via $concatArrays.
Set the version with the size of parameters array minus 1.
db.parameters.update({
"my_id": 321123
},
[
{
"$set": {
parameters: {
"$concatArrays": [
"$parameters",
[
{
"a": 1,
"b": 2,
"c": 3
}
]
]
}
}
},
{
"$set": {
version: {
"$subtract": [
{
$size: "$parameters"
},
1
]
}
},
}
],
{
upsert: true
})
Demo (2nd scenario) # Mongo Playground

Related

how to use $project with condition?

I have a collection as below:
[
{
"a": 1,
"b": [
{
"a": 1,
"bb": 10
},
{
"a": 1,
"bb": 11
}
]
},
{
"a": 2,
"b": [
{
"a": 2,
"bb": 20
},
{
"a": 1,
"bb": 21
}
]
},
]
in each row of the top collection I have two keys :
a and b
'b' key is an array of objects with [a, bb] keys.
now, I want to retrieve items of subarray(b) that the key exists in each row.
I want the result to be like this:
[
{
"a": 1,
"bb": 10
},
{
"a": 2,
"bb": 20
},
]
Do you have any idea to solve this?
MongoDB Playground
use this aggregation
in result you have a value and your result as gg if you need something else notify me
db.collection.aggregate([
{
'$project': {
'a': 1,
'gg': {
'$filter': {
'input': '$b',
'as': 'z',
'cond': {
'$eq': [
'$$z.a', '$a'
]
}
}
}
}
}
])

MongoDB aggregation to change the key based on value in each object

Is there an aggregation query available to convert the following:
after unwind I plan to change the key based on the value inside each object and append it too has---(type)
{
"rows": [
{
"type": "aaa",
"values": [
1,
2,
3
]
},
{
"type": "bbb",
"values": [
4,
5,
6
]
}
]
}
to
{
"hasaaa": {
"type": "aaa",
"values": [
1,
2,
3
]
},
"hasbbb": {
"type": "bbb",
"values": [
4,
5,
6
]
}
}
$map to iterate loop of rows array
$concat to prepare the custom key string
return key-value from map
$arrayToObject convert key-value array to object
db.collection.aggregate([
{
$project: {
rows: {
$arrayToObject: {
$map: {
input: "$rows",
in: {
k: { $concat: ["has", "$$this.type"] },
v: "$$this"
}
}
}
}
}
}
])
Playground

How to add calculated fields inside subdocuments without using unwind?

I'm looking for a simple solution to add a field to a subdocument without using $unwind and $group.
I need only to calculate the sum and the size of a nested subdocuments and show it in a new field.
This is my starting collection:
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3]
},
{
"b": 2
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6]
},
{
"b": 2
},
{
"c": 3,
"subArray": [8,8,8]
}
]
}
And this is my desired result, where I've added sum (sum of subArray) and size (number of elements in subArray):
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3],
"sum": 6,
"size": 3
},
{
"b": 2
"sum": 0,
"size": 0
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6],
"sum": 15,
"size": 3
},
{
"b": 2,
"sum": 0,
"size": 0
},
{
"c": 3,
"subArray": [8,8],
"sum": 16,
"size": 2
}
]
}
I know how to obtain this result using $unwind and then $group, but I'd like to know if there is any other way (or a better way!) to achieve the same result. I've tried using $addFields and $map without success.
Working playground example: https://mongoplayground.net/p/fK8t6SLlOHa
$map to iterate loop of subDoc array
$sum to get total of subArray array of numbers
$ifNull to check if field is not present or null then return empty array because $size operator only allows array input
$size to get total elements in subArray array
$mergeObjects to merge current object with new added fields
db.collection.aggregate([
{
$addFields: {
subDoc: {
$map: {
input: "$subDoc",
in: {
$mergeObjects: [
"$$this",
{
sum: { $sum: "$$this.subArray" },
size: {
$size: { $ifNull: ["$$this.subArray", []] }
}
}
]
}
}
}
}
}
])
Playground

Mongodb Update subdocument values matching subdocument and without altering other fields

I have a schema like this
{
"_id": "5f86da4b5bb9a62742371409",
"status" : true,
"doc": [
{
"_id": "5f86cacbe4d7c423f21faf50",
"a": 4,
"b": null
},
{
"_id": "5f86cb01a1ca5124063299c1",
"a": 6,
"b": null
}
]
}
And I have an update in the form of
[
{
"_id": "5f86cacbe4d7c423f21faf50",
"b": 90
},
{
"_id": "5f86cb01a1ca5124063299c1",
"b": 45
}
]
How can I update the collection to end up like this?
{
"_id": "5f86da4b5bb9a62742371409",
"status" : true,
"doc": [
{
"_id": "5f86cacbe4d7c423f21faf50",
"a": 4,
"b": 90
},
{
"_id": "5f86cb01a1ca5124063299c1",
"a": 6,
"b": 45
}
]
}
Basically I want to update the subdocument with specific keys only (leaving other keys intact)
You can use update with aggregation pipeline from MongoDB 4.2,
$map to iterate loop of array doc and inside it iterate through updateDocs array, it will return matching b, and then $mergeObjects will return updated document,
let updateDocs = [
{ "_id": mongoose.Types.ObjectId("5f86cacbe4d7c423f21faf50"), "b": 90 },
{ "_id": mongoose.Types.ObjectId("5f86cb01a1ca5124063299c1"), "b": 45 }
];
db.collection.updateMany({},
[
{
$set: {
doc: {
$map: {
input: "$doc",
as: "d",
in: {
$mergeObjects: [
"$$d",
{
$reduce: {
input: updateDocs,
initialValue: {},
in: {
$cond: [
{ $eq: ["$$this._id", "$$d._id"] },
{ b: "$$this.b" },
"$$value"
]
}
}
}
]
}
}
}
}
}
]
)
Playground

Mongodb array $push and $pull

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