How to Merge Array and Document Field in Mongo DB - mongodb

I have this document in MongoDB
[
{
"locations": [5, 5],
"id": "fff"
},
{
"locations": [7, 7],
"id": "aaa"
},
{
"locations": [9, 9],
"id": "ccc"
}
]
And I want to merge the array field and string field into a field that contains a combination of them like this
{
"device": [
["fff", 5, 5],
["aaa", 7, 7],
["ccc", 9, 9]
]
}
Is it possible to do this with aggregation? Thank you.

You can use $concatArrays to merge two fields and then $group to make it two dimensional array
db.collection.aggregate([
{ "$group": {
"_id": null,
"devices": { "$push": { "$concatArrays": [["$id"], "$locations"] } }
}}
])
Output
[
{
"devices": [
["fff", 5, 5],
["aaa", 7, 7],
["ccc", 9, 9]
]
}
]

Related

MongoDB query: keys and their distinct values (each key independently)?

If you have this collection of objects:
{ "a": 10, "b": 20, "c": 30 }
{ "a": 11, "b": 20, "c": 31 }
{ "a": 10, "b": 20, "c": 31 }
There is a way to get distinct values, for example, for field "a":
[10, 11]
There is also a way to get distinct values of any tuple, for example, for pairs of ("b", "c"):
[
{"b": 20, "c": 30},
{"b": 20, "c": 31}
]
Is there a way to query distinct values for each field individually in a single query?
For example, I can simply use query 1 above 3 times for "a", "b", "c":
[10, 11]
[20]
[30, 31]
But I guess it might be less efficient and there should be a better option.
Bonus: How to do it if the list of fields is not known upfront?
Ideally, the single query should return all keys and their distinct values:
{
"a": [10, 11],
"b": [20],
"c": [30, 31]
}
Assuming you don't know the full list of the fields beforehand, you need to use $objectToArray to convert the $$ROOT document into an array of k-v tuples. Then group by the field name and $addToSet the values.
db.collection.aggregate([
{
"$project": {
_id: 0,
arr: {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arr"
},
{
$match: {
"arr.k": {
$ne: "_id"
}
}
},
{
$group: {
_id: "$arr.k",
values: {
"$addToSet": "$arr.v"
}
}
}
])
Mongo Playground

how to use $project with condition?

I have a collection as below:
[
{
"a": 1,
"b": [
{
"a": 1,
"bb": 10
},
{
"a": 1,
"bb": 11
}
]
},
{
"a": 2,
"b": [
{
"a": 2,
"bb": 20
},
{
"a": 1,
"bb": 21
}
]
},
]
in each row of the top collection I have two keys :
a and b
'b' key is an array of objects with [a, bb] keys.
now, I want to retrieve items of subarray(b) that the key exists in each row.
I want the result to be like this:
[
{
"a": 1,
"bb": 10
},
{
"a": 2,
"bb": 20
},
]
Do you have any idea to solve this?
MongoDB Playground
use this aggregation
in result you have a value and your result as gg if you need something else notify me
db.collection.aggregate([
{
'$project': {
'a': 1,
'gg': {
'$filter': {
'input': '$b',
'as': 'z',
'cond': {
'$eq': [
'$$z.a', '$a'
]
}
}
}
}
}
])

Transform field in document's array

I'm trying to convert some fields from string to integer with aggregate pipelines in MongoDB.
These fields are nested in a document's array, so that they are handled as array too.
Below an example document:
{
"_id": *****,
"subject": "first",
"items": [
"item_id": "1",
"ratings": {
"count": 90,
"value": "8",
},
"item_id": "2",
"ratings": {
"count": 75,
"value": "9",
},
]
}
I tried with $convert and $map (on items.item_id and items.rating.value) but I got the following results:
$convert: cannot convert array to int
$map: each field becomes an array itself holding values from all items:
{
"subject": "first",
"items": [
"item_id": [1, 2],
"ratings": {
"count": 90,
"value": [8, 9],
},
"item_id": [1, 2],
"ratings": {
"count": 75,
"value": [8, 9],
},
]
}
Any help is appreciated :)
$map to iterate loop of items array,
$toInt to convert value to integer type
$mergeObjects to merge current object with modified fields value
db.collection.aggregate([
{
$addFields: {
items: {
$map: {
input: "$items",
in: {
$mergeObjects: [
"$$this",
{
"ratings": {
count: "$$this.ratings.count",
value: { $toInt: "$$this.ratings.value" }
}
}
]
}
}
}
}
}
])
Playground

MonogDB document structure: Map vs. Array for element-wise aggregations

We want to store ratings of a metric (say sales, profit) for some category (say city) in MondoDB. Example rating scale: [RED, YELLOW, GREEN], the length will be fixed. We are considering the following two document structures:
Structure 1: Ratings as an array
{
"_id": 1,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : [1, 2, 3], // frequency of RED, YELLOW, GREEN ratings, fixed length array
"profit": [4, 5, 6],
},
}
{
"_id": 2,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : [1, 2, 3], // frequency of RED, YELLOW, GREEN ratings, fixed length array
"profit": [4, 5, 6],
},
}
Structure 2: Ratings as a map
{
"_id": 1,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : { // map will always have "RED", "YELLOW", "GREEN" keys
"RED": 1,
"YELLOW": 2,
"GREEN": 3
},
"profit" : {
"RED":4,
"YELLOW": 5,
"GREEN": 6
},
},
}
{
"_id": 2,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : { // map will always have "RED", "YELLOW", "GREEN" keys
"RED": 1,
"YELLOW": 2,
"GREEN": 3
},
"profit" : {
"RED":4,
"YELLOW": 5,
"GREEN": 6
},
},
}
Our use case:
aggregate ratings grouped by city and metric
we do not intend to index on the "ratings" field
So for structure 1, to aggregate ratings, I need element-wise aggregations and it seems it will likely involve unwind steps or maybe map-reduce and the resulting document would look something like this:
{
"city": "X",
"sales": [2, 4, 6]
"profit": [8, 10, 12]
}
For structure 2, I think aggregation would be relatively straightforward using the aggregation pipeline, ex (aggregating just sales):
db.getCollection('Collection').aggregate([
{
$group: {
"_id": {"city": "$city" },
"sales_RED": {$sum: "$ratings.sales.RED"},
"sales_YELLOW": {$sum: "$ratings.sales.YELLOW"},
"sales_GREEN": {$sum: "$ratings.sales.GREEN"}
}
},
{
$project: {"_id": 0, "city": "$_id.city", "sales": ["$sales_RED", "$sales_YELLOW", "$sales_GREEN"]}
}
])
Would give the following result:
{
"city": "X",
"sales": [2, 4, 6]
}
Query:
I am tending towards the second structure mainly because I am not clear on how to achieve element-wise array aggregation in MOngoDB. From what I have seen it will probably involve unwinding. The second document structure will have a larger document size because of the repeated field names for the ratings but the aggregation itself is simple. Can you please point out, based on our use case, how would they compare in terms of computational efficiency, and if I am missing any points worth considering?
I was able to achieve the aggregation with the array structure using $arrayElemAt. (However, this still involves having to specify aggregations for individual array elements, which is the same as the case for document structure 2)
db.getCollection('Collection').aggregate([
{
$group: {
"_id": {"city": "$city" },
"sales_RED": {$sum: { $arrayElemAt: [ "$ratings.sales", 0] }},
"sales_YELLOW": {$sum: { $arrayElemAt: [ "$ratings.sales", 1] }},
"sales_GREEN": {$sum: { $arrayElemAt: [ "$ratings.sales", 2] }},
}
},
{
$project: {"_id": 0, "city": "$_id.city", "sales": ["$sales_RED", "$sales_YELLOW", "$sales_GREEN"]}
}
])

Search Exact Array Values In Multiple Fields

I have a collection which has 3 documents like below:
Collection:
{
name: "A",
arr: [1, 2, 3],
arr1: [4, 5, 6]
},
{
name: "B",
arr: [3, 7, 11],
arr1: [5, 6, 9]
},
{
name: "C",
arr: [3, 4, 5],
arr1: [7, 9, 12]
}
I want to search array below in the collection.
But all array values must be matched in fields "arr" or "arr1".
I mean array values can be in either fields but all values must be in the document.
So when I search array in the collection only second which has name:"B" and third which has name:"C" documents should be the result.
Because in the second document; first array value( 3 ) in the "arr" field and second and third array values(5 and 9) in the "arr1" field. In the third document first and second (3, 5) array values in the "arr" field and third array value (9) in the "arr1" field.
Array : [3, 5, 9]
Can you help me?
The best way to do this is using the $redact operator.
db.collection.aggregate([
{ "$redact": {
"$cond": [
{ "$setIsSubset": [ [3,5,9], { "$setUnion": [ "$arr", "$arr1" ] } ] },
"$$KEEP",
"$$PRUNE"
]}
}
])
You can also use $project with the $setUnion operator
and $match.
db.collection.aggregate([
{ "$project": { "name": 1, "arr": 1, "arr1": 1, "allvalues": { "$setUnion": [ "$arr", "$arr1" ]}}},
{ "$match": { "allvalues": { "$all": [3, 5, 9] }}}
])
Output:
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbe"), "name" : "B", "arr" : [ 3, 7, 11 ], "arr1" : [ 5, 6, 9 ] }
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbf"), "name" : "C", "arr" : [ 3, 4, 5 ], "arr1" : [ 7, 9, 12 ] }