Add field to every object in array of objects in mongo aggregation - mongodb

I have one field in schema at root level and want to add it in every object from array which matches a condition.
Here is a sample document....
{
calls: [
{
"name": "sam",
"status": "scheduled"
},
{
"name": "tom",
"status": "cancelled"
},
{
"name": "bob",
"status": "scheduled"
},
],
"time": 1620095400000.0,
"call_id": "ABCABCABC"
}
Required document is as follows:
[
{
"call_id": "ABCABCABC",
"calls": [
{
"call_id": "ABCABCABC",
"name": "sam",
"status": "scheduled"
},
{
"name": "tom",
"status": "cancelled"
},
{
"call_id": "ABCABCABC",
"name": "bob",
"status": "scheduled"
}
],
"time": 1.6200954e+12
}
]
The call_id should be added to all objects in array whose status is "scheduled".
Is it possible to do this with mongo aggregation? I have tried $addFields but was unable to achieve above result.
Thanks in advance!

Here is how I would do it using $map and $mergeObjects
db.collection.aggregate([
{
"$addFields": {
calls: {
$map: {
input: "$calls",
as: "call",
in: {
$cond: [
{
$eq: [
"$$call.status",
"scheduled"
]
},
{
"$mergeObjects": [
"$$call",
{
call_id: "$call_id"
}
]
},
"$$call"
]
}
}
}
}
}
])
Mongo Playground

Related

How to use an existing field value with $push in MongoDb?

I have the following Mongo collection
{
"_id": ObjectId("5524d12d2702a21830bdb8e5"),
"code": "Apple",
"name": "iPhone",
"parameters": [
{
"code": "xxx",
"name": "Andrew",
"value": "9",
},
{
"code": "yyy",
"name": "Joy",
"value": "7",
},
]
}
I am using the following query to push into the parameters array object
db.coll.update({
"parameters.name": "Andrew"
},
{
$push: {
"parameters": {
"code": "$code",
"name": "bar",
"value": "10",
}
}
},
{
multi: true
})
However, for the value of code, I want to use the value of the object that matched (i.e. the object with parameters.name == "Andrew", which here is xxx.
Here's a playground link to the problem https://mongoplayground.net/p/v-j1tCCjiWq
Also, I am using a really old version (3.2) of MongoDb. It would be preferable if the solution worked with that.
With MongoDB v4.4+, you can first $match with your criteria. Chain up $first with $filter to extract the array element you want. Use $concatArrays to append a new element(i.e. same as $push) to the array and $merge to update back into the collection.
db.coll.aggregate([
{
$match: {
"parameters.name": "Andrew"
}
},
{
$set: {
parameters: {
"$concatArrays": [
"$parameters",
[
{
"$mergeObjects": [
// get the object matched
{
"$first": {
"$filter": {
"input": "$parameters",
"as": "p",
"cond": {
$eq: [
"Andrew",
"$$p.name"
]
}
}
}
},
// update the object matched with other fields with constant value
{
"name": "bar",
"value": "10"
}
]
}
]
]
}
}
},
{
"$merge": {
"into": "coll1",
"on": "_id"
}
}
])
Mongo Playground

Get Array containing objects to normal object which have value as key in MongoDb result

I have this in
[
{
"date": "2022-12-03T12:16:52.403Z",
"configs": [
{
"name": "Shubham",
"values": [
{
"text": "172cm",
"type": "Height",
},
{
"text": "80kg",
"type": "Weight",
},
{
"text": "male",
"type": "Gender",
},
],
}
]
},
{....},{....}
]
Want to convert config into objects like this
[
{
"date": "2022-12-03T12:16:52.403Z",
"configs": {
"name": "Shubham",
"Height": "172cm",
"Weight": "80kg",
"Gender": "male",
}
},
{...},{...},
]
I know how to do in Javascript array map method but need to understand how to do in mongoDb query ($project).
Here is a possible approach
First $unwind the configs since it contains only 1 element
In the $project stage use $arrayToObject to convert the [{k:'key1',v:'value1'},{k:'key2',v:'value2'}] to {key1:'value1',key2:'value2'}.
The $map stage is used to convert the field names in values array to the above format.
$concatArrays is used to concat the name field and value before converting array to object so that it becomes [{k:'name',v:'Shubham'},{k:'Height',v:'172cm'}...]
Playground Link
db.collection.aggregate([
{
$unwind: "$configs"
},
{
$project: {
date: 1,
configs: {
$arrayToObject: {
$concatArrays: [
{
$map: {
input: "$configs.values",
as: "el",
in: {
k: "$$el.type",
v: "$$el.text"
}
}
},
[
{
k: "name",
v: "$configs.name"
}
]
]
},
},
},
},
])
Did you try this?
arr=[];
yourOutPutJson.forEach(function(b){var obj={};obj.name=b.configs[0].name;b.configs[0].values.forEach(function(c){obj[c.type]=c.text});arr.push(obj)});

MongoDB - How to find and update elements in a nested array

Here is the collection:
db.employees.insertMany([
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"SHORE",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"HELLO",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
}
]
}
}
]);
What I want is to locate the elements in 'category' with a 'subcategory' that contains 'EDUCATION' and replace 'EDUCATION' with another string, let's say 'SPORTS'.
I tried a couple of commands but nothing really did the job:
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$": {
"subcategory": "SPORTS"
}
}
})
What I saw is that it doesn't update the element by replacing it and it doesn't replace every element that meets the criteria.
Think that MongoDB Update with Aggregation Pipeline fulfills your scenario.
$set - Set data.category value.
1.1. $map - Iterate each element in data.category and return an array.
1.1.1. $mergeObjects - Merge the current document with the document with subcategory field from 1.1.1.1.
1.1.1.1 $map - Iterate each value from the subcategory array. With $cond to replace the word EDUCATION with SPORTS if fulfilled, else use existing value ($$this).
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
[
{
"$set": {
"data.category": {
$map: {
input: "$data.category",
in: {
$mergeObjects: [
"$$this",
{
subcategory: {
$map: {
input: "$$this.subcategory",
in: {
$cond: {
if: {
$eq: [
"$$this",
"EDUCATION"
]
},
then: "SPORTS",
else: "$$this"
}
}
}
}
}
]
}
}
}
}
}
]
Sample Mongo Playground
Here's another way to do it using "arrayFilters".
db.collection.update({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$[].subcategory.$[elem]": "SPORTS"
}
},
{
"arrayFilters": [
{ "elem": "EDUCATION" }
],
"multi": true
})
Try it on mongoplayground.net.

MongoDB Aggregation - Change a value of field based on other field value in deeply nested array of objects

So I have a number of documents in my collection. Each object is a user object which contains thoughts and thoughts have replies. What I want is when a reply has anonymous true, its username value should say anonymous instead of the username value.
Document
[
{
"_id": {
"$oid": "6276eb2195b181d38eee0b43"
},
"username": "abvd",
"password": "efgh",
"thoughts": [
{
"_id": {
"$oid": "62778ff975e2c8725b9276f5"
},
"text": "last thought",
"anonymous": true,
"replies": [
{
"_id": {
"$oid": "62778fff75e2c8725b9276f5"
},
"text": "new reply",
"anonymous": true,
"username": "cdf"
},
{
"_id": {
"$oid": "62778fff75e2c8725b9276f5"
},
"text": "new reply",
"anonymous": false,
"username": "cdf"
}
]
}
]
}
]
Output Required. If you see the value in username says anonymous even though the existing document has "cdf" as the value
[
{
"_id": {
"$oid": "6276eb2195b181d38eee0b43"
},
"username": "abvd",
"password": "efgh",
"thoughts": [
{
"_id": {
"$oid": "62778ff975e2c8725b9276f5"
},
"text": "last thought",
"anonymous": true,
"replies": [
{
"_id": {
"$oid": "62778fff75e2c8725b9276f5"
},
"text": "new reply",
"anonymous": true,
"username": "anonymous"
},
{
"_id": {
"$oid": "62778fff75e2c8725b9276f5"
},
"text": "new reply",
"anonymous": false,
"username": "cdf"
}
]
}
]
}
]
Let me know if you know how to help.
Here's a MongoDB Playground URL containing the existing document:
https://mongoplayground.net/p/WoP-3z-DMuf
A bit complex query.
$set - Update the thoughts field.
1.1. $map - Iterate each thought document and return new document from 1.1.1.
1.1.1. $mergeObjects - Merge objects for thoughts document and replies array from 1.1.1.1.
1.1.1.1. $map - Iterate the reply document, return the new document by merging reply document and username field with updating its value based on the anonymous field via $cond.
db.collection.aggregate([
{
$set: {
thoughts: {
$map: {
input: "$thoughts",
as: "thought",
in: {
$mergeObjects: [
"$$thought",
{
replies: {
$map: {
input: "$$thought.replies",
as: "reply",
in: {
$mergeObjects: [
"$$reply",
{
username: {
"$cond": {
"if": "$$reply.anonymous",
"then": "anonymous",
"else": "$$reply.username"
}
}
}
]
}
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground

Delete objects that met a condition inside an array in mongodb

My collection has array "name" with objects inside. I need to remove only those objects inside array where "name.x" is blank.
"name": [
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b57b",
"name": "abc",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e6"
},
"qty": "1.0",
"Unit": "pound,lbs"
},
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b430",
"name": "xyz",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
},{
"name.x": []
,
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
}
I tried to get all the ids where name.x is blank using python and used $pull to remove objects base on those ids.But the complete array got deleted.How can I remove the objects that meet the condition.
Think MongoDB update with aggregation pipeline meets your requirement especially to deal with the field name with ..
$set - Update the name array field by $filter name.x field is not an empty array.
db.collection.update({},
[
{
$set: {
name: {
$filter: {
input: "$name",
cond: {
$ne: [
{
$getField: {
field: "name.x",
input: "$$this"
}
},
[]
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground