MongoDB addFields based on condition (array of array) - mongodb

Let's say I have a sample object as below
[
{
"status": "ACTIVE",
"person": [
{
"name": "Jim",
"age": "2",
"qualification": [
{
"type": "education",
"degree": {
"name": "bachelor",
"year": "2022"
}
},
{
"type": "certification",
"degree": {
"name": "aws",
"year": "2021"
}
}
]
}
]
}
]
Now, I need to add a field "score" only when the qualification.type == bachelor
This is the query I tried but could not get the proper result. Not sure what mistake I am doing. Any help is highly appreciated. Thank you in advance.
db.collection.aggregate([
{
$addFields: {
"person.qualification.score": {
$reduce: {
input: "$person.qualification",
initialValue: "",
in: {
$cond: [
{
"$eq": [
"$$this.type",
"bachelor"
]
},
"80",
"$$REMOVE"
]
}
}
}
}
}
])

$set - Set person array field.
1.1. $map - Iterate person array and return a new array.
1.1.1. $mergeObjects - Merge current iterate (person) document and the document with qualification.
1.1.1.1. $map - Iterate the qualification array and return a new array.
1.1.1.1.1. $cond - Match type of current iterate qualification document. If true, merge current iterate qualification document and the document with score field via $mergeObjects. Else remain the existing document.
db.collection.aggregate([
{
$set: {
person: {
$map: {
input: "$person",
as: "p",
in: {
$mergeObjects: [
"$$p",
{
qualification: {
$map: {
input: "$$p.qualification",
in: {
$cond: {
if: {
$eq: [
"$$this.type",
"education"
]
},
then: {
$mergeObjects: [
"$$this",
{
score: "80"
}
]
},
else: "$$this"
}
}
}
}
}
]
}
}
}
}
}
])
Sample Mongo Playground

Related

Update the existing array values in a Document in Mongodb

I want to update the subject field that starts with http://test and consists of /base in the value with the value without base
for example "http://test/timespan/base/1", as it starts with https://test and has base in it. Then i want to update it to http://test/timespan/1
so at last the subject array becomes :
"subject": [
"http://test/concept/114",
"http://test/timespan/1",
"http://wikidata/1233"
]
Document sample
{
"_id": {
"$oid": "1233"
},
"type": "Entity",
"subject": [
"http://test/concept/114",
"http://test/timespan/base/1",
"http://wikidata/1233"
]
}
You can use a conditional update, like so:
db.collection.updateMany({},
[
{
$set: {
subject: {
$map: {
input: {
$ifNull: [
"$subject",
[]
]
},
in: {
$cond: [
{
$regexMatch: {
input: "$$this",
regex: "http:\\/\\/test.*base"
}
},
{
$replaceAll: {
input: "$$this",
find: "base/",
replacement: ""
}
},
"$$this"
]
}
}
}
}
}
])

How to compare a field to a field of a array in mongodb

I have a documents like below. I want to retrieve all documents whose address.city == "newyork" and address.id == active.
[
{
"name": "star1",
"active": 1,
"address": [
{
"id": 1,
"city": "newyork"
},
{
"id": 2,
"city": "sydney"
}
]
},
{
"name": "star2",
"active": 2,
"address": [
{
"id": 1,
"city": "newyork"
},
{
"id": 2,
"city": "london"
}
]
}
]
I have written below query and it Partially works, But It is not returning complete document. I can't use unwind. Do we have any solution without using "unwind". Is it possible to solve a problem only with $match
db.collection.aggregate([
{
$unwind: "$address"
},
{
$match: {
$expr: {
$eq: [
"$active",
"$address.id"
]
},
"address.city": "newyork"
}
}
])
Maybe something like this:
db.collection.aggregate([
{
"$addFields": {
"address": {
"$filter": {
"input": "$address",
"as": "a",
"cond": {
$and: [
{
$eq: [
"$$a.id",
"$active"
]
},
{
$eq: [
"$$a.city",
"newyork"
]
}
]
}
}
}
}
},
{
$match: {
address: {
$ne: []
}
}
}
])
Explained:
Use addFields/filter to match only matching documents in the array.
Remove the documents with empty address from the array for the cases where no subdocuments is found.
Playground
In case you need to match the whole document containing at least one entry having {address.id==active and address.city==newyork } here is an option:
db.collection.aggregate([
{
$match: {
$expr: {
"$in": [
{
id: "$active",
city: "newyork"
},
"$address"
]
}
}
}
])
Explained:
Match only documents having at least one object in address array with id==$active and city=="newyork"
Playground
In case we expect different order inside the address objects , the more correct option is as follow:
db.collection.aggregate([
{
$match: {
$expr: {
$or: [
{
"$in": [
{
id: "$active",
city: "newyork"
},
"$address"
]
},
{
"$in": [
{
city: "newyork",
id: "$active"
},
"$address"
]
}
]
}
}
}
])
Explained:
Match only documents having at least one object in array with { id==$active and city=="newyork" } or { city=="newyork" and id==$active }
Playground 2

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.

how to map and mergeObjects of an array of an array

I want to apply a mergeObjects and map of an Array of an Array:
I am able to complete the mapping and merging when I am one level of Array in but if the Array has another array, I am unsure how to apply another map/merge object.
{
_id: "123",
date: "1900-01-01T11:00:00.0000000",
name: "joe",
birthdayservice: [
{
"date": "1999-01-01",
"team" : [
{
"requestedDate": "1999-01-01"
},
{
"requestedDate": "1999-05-01"
}
},
{
"date": "1998-01-01",
"team" : [
{
"requestedDate": "1999-01-01"
},
{
"requestedDate": "1999-05-01"
}
}
]
}
I am able to map through the first array in birthdayservice to converte date to an ISO_Date/todate such as:
{
"$addFields": {
"birthdayservice": {
$map: {
input: "$birthdayservice", //this is an array
in: {
"$mergeObjects": [
"$$this",
{
"date": {
"$toDate": "$$this.date"
}
}
]}
}
}
}
}
but when I get into more of a nested value, I get
$mergeObjects requires object inputs, but input is an array
{
"$addFields": {
"birthdayservice": {
$map: {
input: "$birthdayservice", //this is an array
in: {
"$mergeObjects": [
"$$this",
{
"date": {
"$toDate": "$$this.date"
}
}
]}
}
}
}
},
{
"$addFields": {
"birthdayservice": {
$map: {
input: "$birthdayservice.team", //also an array
in: {
"$mergeObjects": [
"$$this",
{
"requestedDate": {
"$toDate": "$$this.requestedDate"
}
}
]}
}
}
}
}
My end goal is to relace the date string fields to a date convert
birthdayservice.date
birthdayservice.team.requestedDate
Both to ISO_Dates as below:
{
_id: "123",
date: "1900-01-01T11:00:00.0000000",
name: "joe",
birthdayservice: [
{
"date": ISO_Date("1999-01-01T11:00:00.0000000Z"),
"team" : [
{
"requestedDate": ISO_Date("1999-01-01T11:00:00.0000000Z")
},
{
"requestedDate": ISO_Date("1999-05-01T11:00:00.0000000Z")
}
},
{
"date": "1998-01-01",
"team" : [
{
"requestedDate": ISO_Date("1999-01-01T11:00:00.0000000Z")
},
{
"requestedDate": ISO_Date("1999-05-01T11:00:00.0000000Z")
}
}
]
}
Query
this converts the all date strings to dates inside birthdayservice and teams array
map birthdayservice
convert the "date"
nested map to convert the dates in the team
the way the field is changed value is by mergeObjects
(in mongodb5 we have also setField to do this)
Test code here
aggregate(
[{"$set":
{"birthdayservice":
{"$map":
{"input":"$birthdayservice",
"in":
{"$mergeObjects":
["$$bs",
{"date":{"$toDate":"$$bs.date"},
"team":
{"$map":
{"input":"$$bs.team",
"in":
{"$mergeObjects":
["$$t", {"requestedDate":
{"$toDate":"$$t.requestedDate"}}]},
"as":"t"}}}]},
"as":"bs"}}}}])

MongoDB remove objects from multiple nested arrays

I have a very complex json document in mongo like below:
{
"Category": [
{
"Name" : "",
"Description": "",
"SubCategory" : [
{
"Name": "",
"Description": "",
"Services": [
{"ServiceA": [
{
"Name": "",
"Description": "",
"CodeA": "1234"
}
]},
{"ServiceB" : [
{
"Name": "",
"Description": "",
"CodeBC": "ABCD",
"Key": ""
}
]},
{"ServiceC": [
{
"Name": "",
"Description": "",
"CodeBC": "ABCD",
"Section": [
{
"Name": "",
"Description": ""
}
]
}
]}
]
}
]
}
]
}
Now I want to retrieve the same document from Mongo but I want it to match only those objects inside ServiceA having CodeA = "1234" and those inside ServiceB and ServiceC having CodeBC = "ABCD".
I want it to remove all other objects inside ServiceA, ServiceB and ServiceC not matching the above condition and retrieve the document while maintaining the structure as it is.
Please Note : In the above example I just showed arrays containing single objects and fields that I want to retrieve but in real case it's very complex.
Try nested $map to iterate loop in your nested arrays, $mergeObjects to merge current object and new that we want to filter,
At the end in Services array filter documents on the base of condition in $filter, if filtered is null then that field will be removed using $ifNull
db.collection.aggregate([
{
$set: {
Category: {
$map: {
input: "$Category",
as: "c",
in: {
$mergeObjects: [
"$$c",
{
SubCategory: {
$map: {
input: "$$c.SubCategory",
as: "sc",
in: {
$mergeObjects: [
"$$sc",
{
Services: {
$map: {
input: "$$sc.Services",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
ServiceA: {
$ifNull: [
{
$filter: {
input: "$$s.ServiceA",
cond: { $eq: ["$$this.CodeA", "1234"] }
}
},
"$$REMOVE"
]
},
ServiceB: {
$ifNull: [
{
$filter: {
input: "$$s.ServiceB",
cond: { $eq: ["$$this.CodeBC", "ABCD"] }
}
},
"$$REMOVE"
]
},
ServiceC: {
$ifNull: [
{
$filter: {
input: "$$s.ServiceC",
cond: { $eq: ["$$this.CodeBC", "ABCD"] }
}
},
"$$REMOVE"
]
}
}
]
}
}
}
}
]
}
}
}
}
]
}
}
}
}
}
])
Playground