how to map and mergeObjects of an array of an array - mongodb

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"}}}}])

Related

MongoDB addFields based on condition (array of array)

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

How to combine mongodb original output of query with some new fields?

Collection:
[
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
My query:
db.collection.aggregate([
{
"$addFields": {
"arrayofkeyvalue": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arrayofkeyvalue"
},
{
"$group": {
"_id": null,
"allkeys": {
"$addToSet": "$arrayofkeyvalue.k"
}
}
}
])
The output currently:
[
{
"_id": null,
"allkeys": [
"additional",
"_id",
"para",
"type",
"name"
]
}
]
Detail see Playground
What I want to do is add a new column which includes all of top key of the mongodb query output, exclude "para". And then combine it with the old collection to form a new json.
Is it possible?
The expected result:
{
"column": [{"prop": "name"}, {"prop": "type"}, {"prop": "additional"}],
"columnData": [
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
}
You have the right general idea in mind, here's how I would do it by utilizing operators like $filter, $map and $reduce to manipulate the objects structure.
I separated the aggregation into 3 parts for readability but you can just merge stage 2 and 3 if you wish.
db.collection.aggregate([
{
"$group": {
"_id": null,
columnData: {
$push: "$$ROOT"
},
"keys": {
"$push": {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "field",
in: "$$field.k"
}
}
}
}
},
{
"$addFields": {
unionedKeys: {
$filter: {
input: {
$reduce: {
input: "$keys",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
as: "item",
cond: {
$not: {
"$setIsSubset": [
[
"$$item"
],
[
"_id",
"para"
]
]
}
}
}
}
}
},
{
$project: {
_id: 0,
columnData: 1,
column: {
$map: {
input: "$unionedKeys",
as: "key",
in: {
prop: "$$key"
}
}
}
}
}
])
Mongo Playground

Aggregate mongodb change objects to array

I am working with a mongodb aggregation and I would need to replace the part of my object array to an array concatenation. I understand that the part that I have to modify would be push but it is added as objects and I am not very clear how to concatenate that part
This is my current aggregation:
Datagreenhouse.aggregate([
{ "$match": { "id_sensor_station_absolute": { "$in": arr }, "recvTime": { $gte: fechainicio, $lte: fechafin } } }, //, "id_station": { "$in": id_station }
{
"$lookup": {
"from": "station_types",
"localField": "id_station", // local field in measurements collection
"foreignField": "id_station", //foreign field from sensors collection
"as": "sensor"
}
},
{ "$unwind": "$sensor" },
{
"$addFields": {
"sensor.attrValue": "$attrValue", // Add attrValue to the sensors
"sensor.recvTime": "$recvTime", // Add attrName to the sensors
"sensor.marca": "$sensor.marca",
"sensor.sensor_type": {
$filter: {
input: '$sensor.sensor_type',
as: 'shape',
cond: { $eq: ['$$shape.name', '$attrName'] },
}
}
}
},
{
"$group": {
"_id": {
"name_comun": "$sensor.sensor_type.name_comun",
"name_comun2": "$sensor.marca"
}, // Group by time
"data": {
"$push": {
"name": "$sensor.recvTime",
"value": "$sensor.attrValue",
},
},
}
},
{
"$project": {
"name": {
$reduce: {
input: [
["$_id.name_comun2"]
],
initialValue: "$_id.name_comun",
in: { $concatArrays: ["$$value", "$$this"] }
}
},
"_id": 0,
"data": 1
}
}
]
The current output of this aggregation is:
[
{
"data":[
{
"name":"2020-06-04T14:30:50.00Z",
"value":69.4
},
{
"name":"2020-06-04T14:13:31.00Z",
"value":68.9
}
],
"name":[
"Hum. Relativa",
"Hortisys"
]
},
{
"data":[
{
"name":"2020-06-04T14:30:50.00Z",
"value":25.5
},
{
"name":"2020-06-04T14:13:31.00Z",
"value":26.6
}
],
"name":[
"Temp. Ambiente",
"Hortisys"
]
}
]
I need to change the format of data for data: [[], [], []]
Example:
[
{
"data":[
["2020-06-04T14:30:50.00Z",69.4],
["2020-06-04T14:13:31.00Z",68.9],
"name":[
"Hum. Relativa",
"Hortisys"
]
},
{
"data":[
["2020-06-04T14:30:50.00Z",69.4],
["2020-06-04T14:13:31.00Z",68.9],
"name":[
"Temp. Ambiente",
"Hortisys"
]
}
]
The $push operator won't accept an array directly, but you can give it another operator that returns an array, like
data:{ $push:{ $concatArrays:[[ "$sensor.recvTime", "$sensor.attrValue" ]]}},

MongoDB: aggregating by property names

I have a collection within which each document looks a bit like this:
{
"_id" : ObjectId("5b9fe6010c969210d442a377"),
"statuses" : {
"SEARCHING" : 3
},
"regions" : {
"eu-central-1" : 1,
"us-west-2": 2
}
}
I want to group and transform this into a result set like this:
eu-central-1|us-west-2
1|2
Is this possible in one step/query?
This is probably what you want:
db.collection.aggregate({
$project: {
"regions": { $objectToArray: "$regions" } // convert sub-document into an array of key-value-pairs in order to get hold of the field names
}
}, {
$unwind: "$regions" // flatten the "regions" array
}, {
$group: {
"_id": "$regions.k",
"count": { $sum: "$regions.v" } //
}
})
Alternatively, if you really want to get a pipe-delimited output here is what you'd do:
db.collection.aggregate({
$project: {
"result": {
$reduce: {
"input": { $objectToArray: "$regions" },
"initialValue": { k: "", v: "" }, // start with empty strings for both key and value
"in": {
k: { $concat: [ "$$value.k", "|", "$$this.k" ] }, // concatenate existing value with "|" followed by currently looked at value for both key and value
v: { $concat: [ "$$value.v", "|", { $substr: [ "$$this.v", 0, 1000 ] } ] } // substr is needed to convert an integer field into a string
//v: { $concat: [ "$$value.v", "|", { $toString: "$$this.v" } ] } // this works from MongoDB v4.0 onwards and looks a bit cleaner
}
}
}
}
}, {
$project: { // remove the leading "|"
"result.k": { $substr: [ "$result.k", 1, -1 ] },
"result.v": { $substr: [ "$result.v", 1, -1 ] }
}
})

After applying $filter in mongo, project specific nested array attributes

The collection I'm trying to query has documents with the following structure -
{
"_id": 1,
"topA": "topAValue",
"topB": "topBValue",
"topC": "topCValue",
"nestedDocArray": [
{
"attr1": "a",
"attr2": "b",
"attr3": "c"
},
{
"attr1": "a5",
"attr2": "b5",
"attr3": "c5"
},
{
"attr1": "a1000",
"attr2": "b1000",
"attr3": "c1000"
}
]
}
I'm trying to query this document with "_id": 1 with a requirement to project only certain attributes. In addition to this, the requirement is to only fetch nestedDocArray which matches the condition "attr1": "a5".
The query I tried is as below -
db.testCollection.aggregate(
[
{
"$match": {
"_id": 1
}
},
{
"$project": {
"topA": 1,
"nestedDocArray": {
"$filter": {
"input": "$nestedDocArray",
"as": "nestedDocArray",
"cond": {
"$eq": [
"$$nestedDocArray.attr1",
"a5"
]
}
}
}
}
}
]
);
The response of this query looks something like below -
{
"_id": 1,
"topA": "topAValue",
"nestedDocArray": [
{
"attr1": "a5",
"attr2": "b5",
"attr3": "c5"
}
]
}
This is fine. This has managed to project attributes topA and nestedDocArray.
I further want to only project nestedDocArray.attr2.
The output i'm looking for is like below.
{
"_id": 1,
"topA": "topAValue",
"nestedDocArray": [
{
"attr2": "b5"
}
]
}
How can I modify the query to achieve the same?
You can use $map with $filter to reshape your data:
db.testCollection.aggregate([
{
$match: { _id: 1 }
},
{
$project: {
topA: 1,
nestedDocArray: {
$map: {
input: {
$filter: {
input: "$nestedDocArray",
as: "nestedDocArray",
cond: {
$eq: [ "$$nestedDocArray.attr1", "a5" ]
}
}
},
as: "item",
in: {
attr2: "$$item.attr2"
}
}
}
}
}
])