How to convert an array to object in mongodb query? - mongodb

I am new to mongodb and wanted to convert my array to object using pipeline. For example,
{
field1: [1,2,3,4,5],
field2: [‘a’,’b’,’c’,’d’,’e’],
}
I want the above document to be converted to,
{
fields: [
{
field1: 1,
field2: ‘a’
},
......
{
field1: 5,
field2: ‘e’
}
]
}
Any idea how I can achieve this?

You can use $unwind to separate your arrays.
And then format your new list with $project without forgetting to remove the duplicates created by the $unwind.
db.collection.aggregate({
"$unwind": {
path: "$field1",
includeArrayIndex: "field1_index"
}
},
{
"$unwind": {
"path": "$field2",
"includeArrayIndex": "field2_index"
}
},
{
"$project": {
"fields": {
"field1": "$field1",
"field2": "$field2"
},
"diff": {
$cmp: [
"$field1_index",
"$field2_index"
]
}
}
},
{
"$match": {
"diff": 0
}
},
{
$group: {
_id: "$_id",
fields: {
$push: "$fields"
}
}
})
Try it here

You can use $zip and $map and $reduce to achieve this:
db.collection.aggregate([
{
"$addFields": {
fields: {
$reduce: {
input: {
$zip: {
inputs: [
{
$map: {
input: "$field1",
as: "f1",
in: {
field1: "$$f1"
}
}
},
{
$map: {
input: "$field2",
as: "f2",
in: {
field2: "$$f2"
}
}
}
]
}
},
initialValue: [],
in: {
"$concatArrays": [
[
{
"$mergeObjects": "$$this"
}
],
"$$value"
]
}
}
}
}
}
])
MongoPlayground
Make sure both field1 and field2 are of equal length or you will lose some data.

Related

Project one field from specific object element in an array mongodb

I have a collection:
{
"_id": 1,
"_deleted": false,
"customFields": [{
"fieldName": "sapID",
"value": ""
}, {
"fieldName": "salesTerritory",
"value": ""
}, {
"fieldName": "clientType",
"value": "Corporate"
}],
}
How can I project(aggregate) only the value field of the element with fieldName = "clientType":
db.collection.aggregate(
{
$project:{value:<code>}
}
)
I tried $filter but it does not work
db.collection.aggregate([
{
$project: {
"customFields.value": 1
}
}
])
mongoplayground
db.collection.aggregate([
{
$set: {
customFields: {
$map: {
input: "$customFields",
as: "c",
in: {
$cond: {
if: { "$eq": [ "$$c.fieldName", "clientType" ] },
then: { value: "$$c.value" },
else: "$$c"
}
}
}
}
}
}
])
mongoplayground
What about this?
db.collection.aggregate([
{
$project: {
customFields: {
$filter: {
input: "$customFields",
$cond: { $eq: ["$$this.fieldName", "clientType"] }
}
}
}
}
])
Mongo Playground

mongodb can $unionWith use in $push

I want to get related data based on current item processing.
Sample:
[
{ field1: 1, field2: 2, value: 12 },
{ field1: 1, field2: 2, value: 21 },
{ field1: 1, value: 1 },
{ field2: 2, value: 2 },
{ field1: 2, field2: 3, value: 23 }
];
and result:
[
{
_id: { field1: 1, field2: 2 },
value: [12, 12],
relatedValue: [1, 2], // of item 1 and 2 because field 1 = 1 or field 2 = 2
},
];
Sample query:
db.collectionA.aggregate([
{
$match: { field1: 1 }
},
{
"$group":{
"_id":{
"field1":"$field1",
"field2":"$field2"
},
"alerts":{
"$push":{
"_id":"$_id",
"value":"$value",
"relatedData": {
"$unionWith": {
"coll": "collectionA",
"pipeline": [{
"$match": {
"$or": [
{ "field1": "$field1" },
{ "field2": "$field2" }
]
}
}]
}
}
}
}
}
}
])
I tried run this query but error, Please help me fix or give a solution
// Edited: value should be array because I want to group data by field1, field2 and push all value of group to an array
You're trying to use $unionWith within $group but it is a "pipeline stage" meaning it can't be used like that, the same way you can't use $group within a $group.
Additionally this stage is used to "union" two collections and not to populate data based on value matches ( which it seems you're trying to do here ), for this case you want to use $lookup, like so:
db.collection.aggregate([
{
$lookup: {
from: "collection",
let: {
field1: "$field1",
field2: "$field2",
docId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$or: [
{
$eq: [
"$$field1",
"$field1"
]
},
{
$eq: [
"$$field2",
"$field2"
]
}
]
},
{
$ne: [
"$$docId",
"$_id"
]
}
]
}
}
},
{
$project: {
value: 1
}
}
],
as: "relatedData"
}
},
{
$group: {
_id: {
field1: "$field1",
field2: "$field2"
},
values: {
$push: "$value"
},
relatedValue: {
$push: {
$map: {
input: "$relatedData",
in: "$$this.value"
}
}
}
}
},
{
$project: {
field1: "$_id.field1",
field2: "$_id.field2",
values: 1,
relatedValues: {
"$setDifference": [
{
"$reduce": {
input: "$relatedValue",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
"$values"
]
}
}
}
])
Mongo Playground

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

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

$group inner array values without $unwind

I want to group objects in the array by same value for specified field and produce a count.
I have the following mongodb document (non-relevant fields are not present).
{
arrayField: [
{ fieldA: value1, ...otherFields },
{ fieldA: value2, ...otherFields },
{ fieldA: value2, ...otherFields }
],
...otherFields
}
The following is what I want.
{
arrayField: [
{ fieldA: value1, ...otherFields },
{ fieldA: value2, ...otherFields },
{ fieldA: value2, ...otherFields }
],
newArrayField: [
{ fieldA: value1, count: 1 },
{ fieldA: value2, count: 2 },
],
...otherFields
}
Here I grouped embedded documents by fieldA.
I know how to do it with unwind and 2 group stages the following way. (irrelevant stages are ommited)
Concrete example
// document structure
{
_id: ObjectId(...),
type: "test",
results: [
{ choice: "a" },
{ choice: "b" },
{ choice: "a" }
]
}
db.test.aggregate([
{ $match: {} },
{
$unwind: {
path: "$results",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: {
_id: "$_id",
type: "$type",
choice: "$results.choice",
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
_id: "$_id._id",
type: "$_id.type",
result: "$results.choice",
},
groupedResults: { $push: { count: "$count", choice: "$_id.choice" } }
}
}
])
You can use below aggregation
db.test.aggregate([
{ "$addFields": {
"newArrayField": {
"$map": {
"input": { "$setUnion": ["$arrayField.fieldA"] },
"as": "m",
"in": {
"fieldA": "$$m",
"count": {
"$size": {
"$filter": {
"input": "$arrayField",
"as": "d",
"cond": { "$eq": ["$$d.fieldA", "$$m"] }
}
}
}
}
}
}
}}
])
The below adds a new array field, which is generated by:
Using $setUnion to get unique set of array items, with inner $map to
extract only the choice field
Using $map on the unique set of items,
with inner $reduce on the original array, to sum all items where
choice matches
Pipeline:
db.test.aggregate([{
$addFields: {
newArrayField: {
$map: {
input: {
$setUnion: [{
$map: {
input: "$results",
in: { choice: "$$this.choice" }
}
}
]
},
as: "i",
in: {
choice: '$$i.choice',
count: {
$reduce: {
input: "$results",
initialValue: 0,
in: {
$sum: ["$$value", { $cond: [ { $eq: [ "$$this.choice", "$$i.choice" ] }, 1, 0 ] }]
}
}
}
}
}
}
}
}])
The $reduce will iterate over the results array n times, where n is the number of unique values of choice, so the performance will depend on that.