MongoDB - convert an object to an array - mongodb

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

Related

MongoDB document merge without a-priori knowledge of fields

I would like to merge several documents. Most of the fields have the same values but there might be one or two fields that have different values. These fields are unknown beforehand. Ideally I would like to merge all the documents keeping the fields that are the same as is but creating an array of values only for those fields that have some variation.
For my first approach I grouped by a common field to my documents and kept the first document, this however discards some information that varies in other fields.
group_documents = {
"$group": {
"_id": "$0020000E.Value",
"doc": {
"$first": "$$ROOT"
}
}
}
merge_documents = {
"$replaceRoot": {
"newRoot": "$doc"
}
}
write_collection = { "$out": { "db": "database", "coll": "records_nd" } }
objects = coll.aggregate(pipeline)
IF the fields that have different values where known I would have done something like this,
merge_sol1
or
merge_sol2
or
merge_sol3
The third solution is actually very close to my desired output and I could tweak it a bit. But these answers assume a-priori knowledge of the fields to be merged.
You can first convert $$ROOT to array of k-v tuples by $objectToArray. Then, $group all fields by $addToSet to put all distinct values into an array first. Then, check the size of the result array and conditionally pick the first item if the array size is 1 (i.e. the value is the same for every documents in the field); Otherwise, keep the result array. Finally, revert back to original document form by $arrayToObject.
db.collection.aggregate([
{
$project: {
_id: "$key",
arr: {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arr"
},
{
$match: {
"arr.k": {
$nin: [
"key",
"_id"
]
}
}
},
{
$group: {
_id: {
id: "$_id",
k: "$arr.k"
},
v: {
"$addToSet": "$arr.v"
}
}
},
{
$project: {
_id: "$_id.id",
arr: [
{
k: "$_id.k",
v: {
"$cond": {
"if": {
$gt: [
{
$size: "$v"
},
1
]
},
"then": "$v",
"else": {
$first: "$v"
}
}
}
}
]
}
},
{
"$project": {
doc: {
"$arrayToObject": "$arr"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{
_id: "$_id"
},
"$doc"
]
}
}
}
])
Mongo Playground

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

How to compute frequency for multiple fields using a single pipeline in MongoDB?

Is it possible to calculate the frequency of multiple fields with a single query in MongoDB? I can do that with separate $group stages for each field. How can I optimize it and build one pipeline that can do the job for all items?
I have the following pipeline in MongoDB 4.5
{
$match: {
field1: { $in: ['value1', 'value2'] },
field2: { $in: ['v1', 'v2'] },
}
},
{
$group: {
_id: {
field1: '$field1',
field2: '$field2'
},
frequency: { $sum: 1.0 }
}
}
From this, I obtain data like the following:
{
"_id": {
"field1": "value1",
"field2": "v1"
},
"count": 7.0
},
{
"_id": {
"field1": "value1",
"field2": "v2"
},
"count": 3.0
},
{
"_id": {
"field1": "value2",
"field2": "v1"
},
"count": 4.0
}
The result that I am trying to get is:
{
"field1": [
"value1": 10.0,
"value2": 4.0
],
"field2": [
"v1": 11.0,
"v2": 3.0
]
}
convert your required fields into array key-value format using $objectToArray
$unwind to deconstruct the above converted array
$group by key and value and count sum
$group by key and construct the array of value and count
$group by null and construct the array of field and above array after converting from $arrayToObject
$replaceToRoot to replace above array after converting from array to object
db.collection.aggregate([
{
$match: {
field1: { $in: ["value1", "value2"] },
field2: { $in: ["v1", "v2"] }
}
},
{
$project: {
arr: {
$objectToArray: {
fields1: "$field1",
fields2: "$field2"
}
}
}
},
{ $unwind: "$arr" },
{
$group: {
_id: {
k: "$arr.k",
v: "$arr.v"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.k",
arr: {
$push: {
k: "$_id.v",
v: "$count"
}
}
}
},
{
$group: {
_id: null,
arr: {
$push: {
k: "$_id",
v: { $arrayToObject: "$arr" }
}
}
}
},
{ $replaceRoot: { newRoot: { $arrayToObject: "$arr" } } }
])
Playground

Mongo DB aggregation using Compass - use variable as key name

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

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