> db.things.find()
{ "_id" : 1, "a" : [ ] }
{ "_id" : 2, "a" : [ { "x" : 2, "y" : 1 }, { "x" : 0, "y" : 1 } ] }
{ "_id" : 3, "a" : [ { "x" : 2, "y" : 5, "abitraryPropertyWhichShouldBeKept" : true } ] }
I could like to add a calculated property, say magnitude
, to each subdocument at a
. I don’t want to have to specify x
and y
manually, and there might be other properties I need to keep. My attempt so far (where I have to manually specify x
and y
which I shouldn’t have to do):
db.things.aggregate([
{
$addFields: {
a: {
$map: {
input: '$a',
as: 'item',
'in': {
x: '$$item.x',
y: '$$item.y',
magnitude: {
$sqrt: {
$add: [
{ $pow: ['$$item.x', 2], },
{ $pow: ['$$item.y', 2] }],
},
},
},
},
},
},
},
])
results which drop the extra key I wanted to keep:
{ "_id" : 1, "a" : [ ] }
{ "_id" : 2, "a" : [ {
"x" : 2, "y" : 1, "magnitude" : 2.23606797749979 }, {
"x" : 0, "y" : 1, "magnitude" : 1 } ] }
{ "_id" : 3, "a" : [ { "x" : 2, "y" : 5, "magnitude" : 5.385164807134504 } ] }
The only other way I could think of is chaining $unwind
, $addFields
, and $group
. But to prevent $unwind
from removing document 1, I’d have to add a dummy entry to the end of each a
array and then discard it when grouping them together again.
Is there something like $map
but that behaves like $addFields
so that I can just add a property to the nested document? Do I really have to specify all possible keys of the subdocument just to use $map
? If I do not know the keys in the nested subdocuments ahead of time, is my only choice really the $unwind
with all the extra pomp and circumstance necessary to use it?
Here’s my $unwind
…$group
try. But, as you can see, I need a ton of stages that wouldn’t be necessary if I could just get $addFields
-like behavior inside of $map
or some expression operator which would merge objects together like JavaScript’s Object.assign()
:
db.things.aggregate([
{ // Add dummy to preserve documents during imminent $unwind
$addFields: {
a: {
$concatArrays: [ '$a', [ null, ], ],
},
},
},
{ $unwind: '$a', },
{ // Do actual calculation on single array element
$addFields: {
'a.magnitude': {
$sqrt: {
$add: [
{ $pow: ['$a.x', 2] },
{ $pow: ['$a.y', 2] },
],
},
},
},
},
{
$group: {
_id: '$_id',
a: { $push: '$a', },
item: { $first: '$$ROOT', },
},
},
{ // Remove dummy element
$addFields: {
'item.a': {
$slice: [ '$a', { $add: [ { $size: '$a', }, -1] } ],
},
},
},
{ $replaceRoot: { newRoot: '$item', }, },
])
results which properly preserve any unanticipated keys in the original data:
{ "_id" : 3, "a" : [ {
"x" : 2, "y" : 5, "abitraryPropertyWhichShouldBeKept" : true,
"magnitude" : 5.385164807134504 } ] }
{ "_id" : 2, "a" : [ {
"x" : 2, "y" : 1, "magnitude" : 2.23606797749979 }, {
"x" : 0, "y" : 1, "magnitude" : 1 } ] }
{ "_id" : 1, "a" : [ ] }
You can use mergeObjects
aggregation operator is available in the 3.5.6 development release which will be rolled into upcoming 3.6 release.
db.things.aggregate([
{
"$addFields": {
"a": {
"$map": {
"input": "$a",
"as": "item",
"in": {
"$mergeObjects": [
"$$item",
{
"magnitude": {
"$sqrt": {
"$add": [
{
"$pow": [
"$$item.x",
2
]
},
{
"$pow": [
"$$item.y",
2
]
}
]
}
}
}
]
}
}
}
}
}
])
Using 3.4.4 & higher production release.
Use $arrayToObject
and $objectToArray
in a $map
to keep the existing key value and $concatArrays
to merge the computed key value array.
db.things.aggregate([
{
"$addFields": {
"a": {
"$map": {
"input": "$a",
"as": "item",
"in": {
"$arrayToObject": {
"$concatArrays": [
{
"$objectToArray": "$$item"
},
[
{
"k": "magnitude",
"v": {
"$sqrt": {
"$add": [
{
"$pow": [
"$$item.x",
2
]
},
{
"$pow": [
"$$item.y",
2
]
}
]
}
}
}
]
]
}
}
}
}
}
}
])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With