Mongo DB aggregation using Compass - use variable as key name - mongodb

I'm trying to map an array of objects to a new array of objects. An example of the object in the array:
{
k:"Zip code"
v:{
questionId:"596080353"
question:"In which ZIP code do you currently reside?"
answer:"97213"
}
}
I want the final object to be:
{
"Zip code": "97213"
}
I'm having trouble setting k as the key name in the new object. Does anyone know how to use variables as the key name in a mongo aggregation?

Use $arrayToObject
Converts an array into a single document; the array must be either:
Shape your data in the below format
[ { "k": "Zip code", "v": "97213"}, { "k": "Zip code", "v": 97212 } ]
Example 1 - https://mongoplayground.net/p/AXKHsZf-Qzy
db.collection.aggregate([
{ $set: { doc: [ { k: "$k", v: "$v.answer" } ] } },
{ $set: { doc: { "$arrayToObject": "$doc" } } }
])
Example 2 - https://mongoplayground.net/p/Vm1DwHVb9KY
db.collection.aggregate([
{ $unwind: "$zip" },
{ $addFields: { doc: { $arrayToObject: [ [ { k: "$zip.k", v: "$zip.v" } ] ] } } },
{ $group: { _id: "$_id", zips: { $push: "$doc" } } }
])

Related

MongoDB: How to merge all documents into a single document in an aggregation pipeline

I have the current aggregation output as follows:
[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
]
The array has two documents. I would like to combine all the documents into a single document having all the fields in mongoDB
db.collection.aggregate([
{
$group: {
_id: 0,
merged: {
$push: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": "$merged"
}
}
}
])
Explained:
Group the output documents in one field with push
Replace the document root with the merged objects
Plyaground
{
$group: {
"_id": "null",
data: {
$push: "$$ROOT"
}
}
}
When you add this as the last pipeline, it will put all the docs under data, but here data would be an array of objects.
In your case it would be
{ "data":[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
] }
Another approach would be,
db.collection.aggregate([
{
$group: {
"_id": "null",
f: {
$first: "$$ROOT",
},
l: {
$last: "$$ROOT"
}
}
},
{
"$project": {
"output": {
"courseCount": "$f.courseCount",
"registeredStudentsCount": "$l.registeredStudentsCount"
},
"_id": 0
}
}
])
It's not dynamic as first one. As you have two docs, you can use this approach. It outputs
[
{
"output": {
"courseCount": 14,
"registeredStudentsCount": 1
}
}
]
With extra pipeline in the second approach
{
"$replaceRoot": {
"newRoot": "$output"
}
}
You will get the output as
[
{
"courseCount": 14,
"registeredStudentsCount": 1
}
]

MongoDB - convert an object to an array

I have two documents (obtained by other steps in an aggregation pipeline):
{
'_id': '2021-01-04',
'value': 1234.55
},
{
'_id': '2021-01-05',
'value': 345.67
}
I would now like to convert these two documents into an array that would look like this:
[
{ '2021-01-04': 1234.55 },
{ '2021-01-05': 345.67 }
]
I've tried to first convert the key/value pairs using a $group stage like so:
$group: {
_id: null,
data: {
$push: {
"k": "$_id",
"v": "$value"
}
}
}
This yields:
[
{
"_id": null,
"data": [
{
"k": "2019-01-04",
"v": 1234.55
},
{
"k": "2019-01-05",
"v": 345.67
}
]
}
]
While this would be useful as input for $arrayToObject, I don't want an object (as I need the objects to be ordered), but I cannot see how to get from here to the desired final output.
$sort order by _id in ascending order
$arrayToObject convert k and v array to object format
$group by null and push above converted object in data
db.collection.aggregate([
{ $sort: { _id: 1 } },
{
$group: {
_id: null,
data: {
$push: {
$arrayToObject: [
[{ k: "$_id", v: "$value" }]
]
}
}
}
}
])
Playground

Dynamic key in MongoDB

Im trying to create a dynamic group by (with sum agg) in MongoDB. But don't know how to right syntax that.
Lets imaging 2 documents:
{
"_id": {"$oid":"5f69f6a360c8479d0908a649"},
"key":"key1",
"data":{
"key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":"value4"
},
"count":10
}
{
"_id": {"$oid":"5f69f6a360c8479d0908a649"},
"key":"key2",
"data":{
"key1":"value5",
"key2":"value6",
"key3":"value7",
"key4":"value8"
},
"count":15
}
With the key attribute, I want to control, which is the groupby attribute.
A pseudo query could look like:
[{
$group: {
_id: {
'$key': data[$key]
},
sum: {
'$sum': '$count'
}
}
}]
Output should look like:
value1 : 10
value6 : 15
Somebody knows how to do that?
I don't understand the purpose of $sum and $group, there are no arrays in your documents.
This aggregation pipeline give desired result:
db.collection.aggregate([
{ $set: { data: { $objectToArray: "$data" } } },
{ $set: { data: { $filter: { input: "$data", cond: { $eq: ["$$this.k", "$key"] } } } } },
{ $set: { data: { k: { $arrayElemAt: ["$data.v", 0] }, v: "$count" } } },
{ $set: { data: { $arrayToObject: "$data" } } },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$data"] } } },
{ $unset: ["key", "count", "data"] }
])
You can try,
$reduce input data as array using $objectToArray, check condition if key matches with data key then return key as value and value as count field
convert that returned key and value object array to exact object using $arrayToObject
replace field using $replaceWith
db.collection.aggregate([
{
$replaceWith: {
$arrayToObject: [
[
{
$reduce: {
input: { $objectToArray: "$data" },
initialValue: {},
in: {
$cond: [
{ $eq: ["$$this.k", "$key"] },
{
k: "$$this.v",
v: "$count"
},
"$$value"
]
}
}
}
]
]
}
}
])
Playground

Mongodb having problem of adding two values inside nested document with dynamic key

I wish to add currentAsset.total and longTermAsset.total for each of my child documents with dynamic key to a new field. My current mongodb version is 4.0.12
My source document is as below:
{
"_id":"5f44bc4c36ac3e2c8c6db4bd",
"counter":"Apple",
"balancesheet":{
"0":{
"currentAsset":{
"total":123.12
},
"longTermAsset":{
"total":10.16
}
},
"1":{
"currentAsset":{
"total":10.23
},
"longTermAsset":{
"total":36.28
}
}
}
}
The result document I wanted to get is:
{
"_id": "5f44bc4c36ac3e2c8c6db4bd",
"counter": "Apple",
"balancesheet": {
"0": {
"currentAsset": {
"total": 123.12
},
"longTermAsset": {
"total": 10.16
},
"totalAsset": 133.28
},
"1": {
"currentAsset": {
"total": 10.23
},
"longTermAsset": {
"total": 36.28
},
"totalAsset": 46.51
}
}
}
I have tried a few aggegrates but failed as it is giving me "errmsg" : "$add only supports numeric or date types, not array"
db.balancesheets.aggregate([
{
$match: { counter: "Apple" }
},
{
$project: {
bs: { $objectToArray: "$balancesheet" }
}
},
{
$addFields: {
totalAsset: {
$add: ["$bs.k.currentAsset.total", "$bs.k.longTermAsset.total"]
}
}
}
])
As I refer to this, it seems like the version needs to be 4.2 and above. Is there anyway that will be able to do it on my existing 4.0.12 version?
MongoDB Aggregation: add field from an embedded document via a dynamic field path
There is no version issues, follow few fixes,
first 2 pipelines looks good,
$unwind deconstruct bs array
$addFields corrected, you used k instead of v in accessing field total
$group to reconstruct and prepare again object to array
$addFields to convert bs array to object using $reduce
db.collection.aggregate([
// $match ... pipeline
// $project ... pipeline
// unwind bs array
{ $unwind: "$bs" },
{
$addFields: {
"bs.v.totalAsset": { $add: ["$bs.v.currentAsset.total", "$bs.v.longTermAsset.total"] }
}
},
{
$group: {
_id: "$_id",
bs: { $push: { $arrayToObject: [["$bs"]] } },
counter: { $first: "$counter" },
},
}
{
$addFields: {
bs: {
$reduce: {
input: "$bs",
initialValue: {},
in: { $mergeObjects: ["$$value", "$$this"] }
}
}
}
}
])
Playground

Mogodb aggregation create output as {"key": "value"} from 2 arrays

I have a question about how to create output from 2 arrays one array with translation key and another with translation i would output as
"translation_key":"value"
Current output:
{
"_id" : ObjectId("5bfc0b2b30c4683f585078fb"),
"translation" : [
"hour",
"day"
],
"translation_key" : [
"HOUR_TEXT",
"DAY_TEXT"
],
"locale_id" : "EN_en"
}
OUTPUT should be :
{
"EN_en" :{
"HOUR_TEXT" :"hour",
"DAY_TEXT" :"day",
}
}
You can use below aggregation
You can use $range to get index from both the arrays and can create key (k) and value (v) pair for the resultant array and then finally $arrayToObject to convert resultant array into single document.
db.collection.aggregate([
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": [[
{
"k": "$locale_id",
"v": {
"$arrayToObject": {
"$map": {
"input": { "$range": [0, { "$size": "$translation_key" }] },
"in": {
"k": { "$arrayElemAt": ["$translation_key", "$$this"] },
"v": { "$arrayElemAt": ["$translation", "$$this"] }
}
}
}
}
}
]]
}
}}
])
Which returns
[
{
"EN_en": {
"DAY_TEXT": "day",
"HOUR_TEXT": "hour"
}
}
]
You can try below aggregation:
db.col.aggregate([
{
$project: {
array: [
{
k: "$locale_id",
v: {
$arrayToObject: {
$map: {
input: { $range: [0, { $size: "$translation" }] },
as: "index",
in: {
k: { $arrayElemAt: [ "$translation", "$$index" ] },
v: { $arrayElemAt: [ "$translation_key", "$$index" ] }
}
}
}
}
}
]
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
])
Basically you need to use $arrayToObject operator to manipulate your key names and in your case it should be used twice. This operator expects an array of objects with two properties k and v and therefore you should use $map to generate that values based on translation, $range is used to generate indexes to traverse translation and translation_key arrays. Then you can use $replaceRoot to promote dynamically generated key into root level of your document.
Output:
{ "EN_en" : { "hour" : "HOUR_TEXT", "day" : "DAY_TEXT" } }