Using $map in aggregate $group - mongodb

I need to analyze some mongo db collections. What I need to extract the names and values of a collection.
Heres's how far I got:
db.collection(coll.name)
.aggregate([
{ $project: { arrayofkeyvalue: { $objectToArray: '$$ROOT' } } },
{ $unwind: '$arrayofkeyvalue' },
{
$group: {
_id: null,
allkeys: { $addToSet: '$arrayofkeyvalue.k' },
},
},
])
.toArray();
This works quite nicely. I get all the keys. However I'd like to get the values too.
So, I thought "piece o' cake" and replaced the allkeys section with the allkeysandvalues section, which is supposed to create a map with key and value pairs.
Like this:
db.collection(coll.name)
.aggregate([
{ $project: { arrayofkeyvalue: { $objectToArray: '$$ROOT' } } },
{ $unwind: '$arrayofkeyvalue' },
{
$group: {
_id: null,
allkeysandvalues: {
$map: {
input: '$arrayofkeyvalue',
as: 'kv',
in: {
k: '$$kv.k',
v: '$$kv.v',
},
},
},
},
},
])
.toArray();
But that's not working. I get the error message
MongoError: unknown group operator '$map'
Does anyone know hot to solve this?

The $group pipeline stage requires accumulator expression so you have to use $push instead of $map:
{
$group: {
_id: null,
allkeysandvalues: {
$push: "$arrayofkeyvalue"
}
}
}
or
{
$group: {
_id: null,
allkeysandvalues: {
$push: {
k: "$arrayofkeyvalue.k",
v: "$arrayofkeyvalue.v"
}
}
}
}
which returns the same result.
Please note that arrayofkeyvalue is an object since you run $unwind prior to $group
Mongo Playground

MongoError: unknown group operator '$map'
You can not use $map operator in $group stage directly in root level,
you can try adding one more group stage,
$group by k (key) and get the first v (value)
$group by null and construct the array of key-value pair
$arrayToObject convert key-value pair array to object
db.collection(coll.name).aggregate([
{ $project: { arrayofkeyvalue: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$arrayofkeyvalue" },
{
$group: {
_id: "$arrayofkeyvalue.k",
value: { $first: "$arrayofkeyvalue.v" }
}
},
{
$group: {
_id: null,
allkeysandvalues: { $push: { k: "$_id", v: "$value" } }
}
},
{ $project: { allkeysandvalues: { $arrayToObject: "$allkeysandvalues" } } }
])
Playground

Related

Creating a new output from the combination of multiple documents coming from a stage - Aggregation Framework

From one of my stages I'm receiving an output which is:
{
_id: "Mario",
powers: ["Super Jump", "Cannon"],
},
{
_id: "Superman",
powers: ["Lasers", "Punch"],
},
{
_id: "Batman",
powers: ["Fang", "Blades", "Missiles"],
}
I want to merge all the documents into a single one such that the key would be the _id from the document and the value would be the powers. Which is something like:
{
"Mario": ["Super Jump", "Cannon"],
"Superman": ["Lasers", "Punch"],
"Batman": ["Fang", "Blades", "Missiles"],
}
My attempt:
{
'$project': {
'idNameAsArray': {
'$split': [
{
'$concatArrays': [
'$_id', ',', '$powers'
]
}, ','
]
}
}
}, {
'$group': {
'_id': '',
'arrayOfIdNameArrays': {
'$push': '$idNameAsArray'
}
}
}, {
'$project': {
'idNameObjs': {
'$arrayToObject': '$arrayOfIdNameArrays'
}
}
}, {
'$replaceRoot': {
'newRoot': '$idNameObjs'
}
}
As $split needs it to be a string, I'm pretty much confused as to what could be the best way to achieve the same within the aggregation pipeline.
Add below 2 stages after your pipeline stages,
$group by null and construct the array of key-value pair
$arrayToObject convert above converted key-value pair array of object to object
$replaceRoot to replace above object to root
{
$group: {
_id: null,
items: {
$push: {
k: "$_id",
v: "$powers"
}
}
}
},
{
$replaceRoot: { newRoot: { $arrayToObject: "$items" } }
}
Playgroud

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

Sort MongoDB Document by field/key name

I'm reviewing my MongoDB documents using Robo 3T, and I'd like to sort the keys in the document by their name.
My document might look like
{"Y":3,"X":"Example","A":{"complex_obj":{}}
and at the end I'd like the returned document to look like when I run a find query and apply a sort to it. {"A":{"complex_obj":{},"X":"Example","Y":3}
Is there a way to sort the returned keys / fields of a document? All the examples I see are for applying sort based on the value of a field, rather than the name of the key.
Not sure why the order of field does matter in a JSON document but you can try below aggregation query :
db.collection.aggregate([
{
$project: { data: { $objectToArray: "$$ROOT" } }
},
{
$unwind: "$data"
},
{
$sort: { "data.k": 1 }
},
{
$group: { _id: "_id", data: { $push: "$$ROOT.data" } }
},
{
$replaceRoot: { newRoot: { $arrayToObject: "$data" } }
},
{
$project: { _id: 0 }
}
])
Test : mongoplayground
There is a way but you won't like it. Technically you can do it with aggregation by converting objects to arrays, unwinding, sorting, grouping it back and converting the group to the object:
db.collection.aggregate([
{
$project: {
o: {
$objectToArray: "$$ROOT"
}
}
},
{
$unwind: "$o"
},
{
$sort: {
"o.k": 1
}
},
{
$group: {
_id: "$_id",
o: {
$push: "$o"
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$o"
}
}
}
])
but you don't want to do it. Too much hassle, too expensive, too little benefits.
Mongo by design preserve order of keys as they were inserted. Well, apart from _id, and few other edge cases.

Mongodb while aggregate group value as key

I was trying to aggregate and group values but want one of the field as key.
[
{id:1, value: "x"},
{id:2, value: "y"},
{id:1, value: "a"},
{id:2, value: "b"},
]
used this query but no luck
db.getCollection('Test').aggregate([
{
$group: {
_id: "$id",
"value": {$push: "$$ROOT" }
}
}
])
Was trying to achieve this
[
{ 1:[x,a] },
{ 2:[y,b] }
]
Can anyone help me with this query?
You need to run $group twice to get single document which contains an array of k,v pairs. Then you can run $arrayToObject on that document along with $replaceRoot to promote new object into root level:
db.collection.aggregate([
{
$group: {
_id: "$id",
values: { $push: "$value" }
}
},
{
$group: {
_id: null,
root: { $push: { k: { $toString: "$_id" }, v: "$values" } }
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$root"
}
}
}
])
Mongo Playground

Return original documents only from mongoose group/aggregation operation

I have a filter + group operation on a bunch of documents (books). The grouping is to return only latest versions of books that share the same book_id (name). The below code works, but it's untidy since it returns redundant information:
return Book.aggregate([
{ $match: generateMLabQuery(rawQuery) },
{
$sort: {
"published_date": -1
}
},
{
$group: {
_id: "$book_id",
books: {
$first: "$$ROOT"
}
}
}
])
I end up with an array of objects that looks like this:
[{ _id: "aedrtgt6854earg864", books: { singleBookObject } }, {...}, {...}]
Essentially I only need the singleBookObject part, which is the original document (and what I'd be getting if I had done only the $match operation). Is there a way to get rid of the redundant _id and books parts within the aggregation pipeline?
You can use $replaceRoot
Book.aggregate([
{ "$match": generateMLabQuery(rawQuery) },
{ "$sort": { "published_date": -1 }},
{ "$group": {
"_id": "$book_id",
"books": { "$first": "$$ROOT" }
}},
{ "$replaceRoot": { "newRoot": "$books" } }
])