Mongodb while aggregate group value as key - mongodb

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

Related

How to group by date and by specific field in MongoDB

I want to print grouped by date and by "productId" within the date. In this example, the output should be as follow:
[
{
"_id": "2018-03-04",
"product1": 2,
"product2": 2
}
]
Data: https://mongoplayground.net/p/gzvm11EIPn2
How to make it?
When you use the $group stage in aggregation you learn to group by one field as such: { $group: { "_id": "$field1"...
When you want to group by two or more fields "_id" needs to be a subdocument and you pass the fields as key value pairs inside the subdocument as such:
db.mycollection.aggregate([
{
$group:
{
"_id": { "product1": "$product1", "product2": "$product2", ... }
}
}
])
... etc.
$group - Group by createdAt (date string) and productId and perform count via $sum.
$group - Group by createdAtand push data from (1) to products array field.
$replaceRoot - Replace input document with new document.
3.1. $arrayToObject - Convert the object from products array field to key value pair with productId (key) and count (value).
3.2. $mergeObjects - Create object with _id and merge the object from (3.2) into 1 object.
db.collection.aggregate([
{
$group: {
_id: {
createdAt: {
$dateToString: {
format: "%Y-%m-%d",
date: "$createdAt"
}
},
productId: "$productId"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.createdAt",
products: {
$push: {
productId: "$_id.productId",
count: "$count"
}
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
_id: "$_id"
},
{
$arrayToObject: {
$map: {
input: "$products",
in: {
k: {
$toString: "$$this.productId"
},
v: "$$this.count"
}
}
}
}
]
}
}
}
])
Sample Mongo Playground
Output
[
{
"5e345223b3aa703b8a9a4f34": 2,
"5e345223b3aa703b8a9a4f35": 2,
"_id": "2018-03-04"
}
]

Using $map in aggregate $group

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

clone and rename a field of an array of subdocuments in Mongo

I've got a collection like this:
{
name: "A Name",
answers: [
{order: 1},
{order: 2},
{order: 3}
]
}
What I want to do is to add a new filed id to each element of the answers array based on the value of the order property - I want just to clone it, so the output is
{
name: "A Name",
answers: [
{order: 1, id: 1},
{order: 2, id: 2},
{order: 3, id: 3}
]
}
I looked at this post and this one too, but I don't know how to combine them to work properly for subdocuments.
In MongoDB documentation for the aggregate method, I found a simple example of how to update embedded documents here, but I have no idea how to use the order property instead of a fixed term. The following tries seem not to work as I need:
db.collection.aggregate([
{
$addFields: {
"answers.id": "answers.$.order"
}
}
])
db.collection.aggregate([
{
$addFields: {
"answers.id": "$answers.order"
}
}
])
Is it possible to achieve the expected result with the `aggregate method?
Demo - https://mongoplayground.net/p/M79MV6-Zp4C
db.collection.aggregate([
{
$set: {
answers: {
$map: {
input: "$answers",
as: "answer",
in: { $mergeObjects: [ "$$answer", { id: "$$answer.order" } ]
}
}
}
}
}
])
Updated Demo - https://mongoplayground.net/p/iyqIPGQ5-ld
db.collection.aggregate([
{ $unwind: "$answers" },
{
$group: {
_id: "$_id",
answers: { $push: { order: "$answers.order", id: "$answers.order" } },
name: { $first: "$name" } // preserve properties add them to the group pick 1st value
}
}
])
Demo - https://mongoplayground.net/p/Ln5CcmT-Kkm
db.collection.aggregate([
{ $unwind: "$answers" }, // break into individuals documents
{ $addFields: { "answers.id": "$answers.order" } }, // copy order value to id
{ $group: { _id: "$_id", answers: { $push: "$answers" } } } // join and group it back
])
If you want to sort n get id from index
Demo - https://mongoplayground.net/p/6U_sRYWHtDR
db.collection.aggregate([
{ $sort: { "answers.order": 1 } },
{ $unwind: { path: "$answers", includeArrayIndex: "index" } },
{ $group: { _id: "$_id", answers: { "$push": { order: "$answers.order", id: { $add: [ "$index", 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