Convert array of objects into an array of values from that object - mongodb

I have the following documents in my collection:
{
"archives" : [
{ "colour" : "red", "default" : true },
{ "colour" : "green", "default" : false }
]
}
{
"archives" : [
{ "colour" : "yellow", "default" : true }
]
}
I want to project the colour value from the archive objects as follows:
{
"archives" : [ "red", "green" ]
}
{
"archives" : [ "yellow" ]
}
My proposal
My best attempt at this has been this query:
db.test.find({}, {
'archives': {
'$map': {
'input': '$archives',
'in': '$archives.colour'
}
}
})
But it's returning an array of arrays with redundant information, like so:
{ "archives" : [ [ "red", "green" ], [ "red", "green" ] ] }
{ "archives" : [ [ "yellow" ] ] }
So what would be the correct query to give the result I need, preferably on the database side, and as efficient as possible?

Why not simply this:
db.test.aggregate([
{ $set: { archives: "$archives.colour" } }
])
If you like to use $map, then is would be this one. You missed the $$this variable:
db.test.aggregate([
{
$set: {
archives: {
"$map": {
"input": "$archives",
"in": "$$this.colour"
}
}
}
}
])
or
db.test.aggregate([
{
$set: {
archives: {
"$map": {
"input": "$archives.colour",
"in": "$$this"
}
}
}
}
])

You can use aggregation framework:
$unwind
$group
db.test.aggregate([
{
"$unwind": "$archives"
},
{
"$group": {
"_id": "$_id",
"archives": {
"$push": "$archives.colour"
}
}
}
])
Playground
And if you don't want the _id in the output, you can exclude it by adding an additional $project stage:
db.test.aggregate([
{
"$unwind": "$archives"
},
{
"$group": {
"_id": "$_id",
"archives": {
"$push": "$archives.colour"
}
}
},
{
"$project": {
_id: 0
}
}
])

Related

Perform search with facets unknown upfront Atlas MongoDB

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

Variable set of attributes $facets MongoDB [duplicate]

I have the following document structure in MongoDB:
{
// other keys,
tags: [
tagA: "red",
tagB: "green"
]
},
{
// other keys,
tags: [
tagA: "orange",
tagB: "green",
tagC: "car"
]
}
I want to perform a $facets search that gives me the following output (name of each tag + values that occur on that tag + count of these value):
{
[
tagA: {
red: 1,
orange: 1
},
tagB: {
green: 2
},
tagC: {
car: 1
}
]
}
The tricky part is that the facets are unknown upfront (they can vary), and every tutorial I found only works for a predefined set of facets.
Is it possible?
P.S.: how to get the output of this to come alongside with a given query? So that the return is something like:
{
queryResults: [all the results, as in a normal query],
facets: [result showed in accepted answer]
}
If you consider having this as input (i've added bracket around object in your array) :
[
{
tags: [
{
tagA: "red"
},
{
tagB: "green"
}
]
},
{
tags: [
{
tagA: "orange"
},
{
tagB: "green"
},
{
tagC: "car"
}
]
}
]
You could then do an aggregation pipeline as follow :
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$addFields": {
"kv": {
"$objectToArray": "$tags"
}
}
},
{
"$unwind": "$kv"
},
{
"$group": {
"_id": {
key: "$kv.k",
value: "$kv.v"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.key",
"value": {
"$push": {
"k": "$_id.value",
"v": "$count"
}
}
}
},
{
$project: {
val: [
{
k: "$_id",
v: {
"$arrayToObject": "$value"
}
}
]
}
},
{
$project: {
res: {
"$arrayToObject": "$val"
}
}
},
{
$replaceRoot: {
newRoot: "$res"
}
}
])
It would give you this result :
[
{
"tagA": {
"orange": 1,
"red": 1
}
},
{
"tagB": {
"green": 2
}
},
{
"tagC": {
"car": 1
}
}
]
You can see this on mongoplayground : https://mongoplayground.net/p/FZbM-BGJRBm
Hope this answer your question.
Detailled explanation :
I use $unwind on the tags field in order to get one object per object in tags array.
I use $objectToArray to get keys (tagsA, tagsB) as values.
$unwind to go from an array to objets.
$group with $sum accumulator to calculate the occurence of each unique combination.
$group by tagsA,tagsB, etc with $push accumulator to add value in array (will be usufull afterwards)
$arrayToObject to go from array to object
Same
$replaceRoot to display results better.
If you want to understand more each step, consider reading mongo doc of each pipeline aggregator i used. You can also use the mongoplayground link above, delete some code to see what happens after each step.

Simple MongoDB Aggregation

I'm a bit confused on how to group using aggregation but still be able to extract specific values from arrays:
db.collection.aggregate([
{ "$unwind": f"${stat_type}" },
{
"$group": {
"_id": "$userId",
"value" : { "$max" : f"${stat_type}.stat_value" },
"character" : f"${stat_type}.character_name", <-- how do I extract this value that matches where the $max from above is grabbed.
}
},
{ "$sort": { "value": -1 }},
{ '$limit' : 30 }
])
Sample Entries:
{
'name' : "Tony",
'userId' : 12345,
'damage_dealt' : [
"character_name" : "James",
"stat_value" : 100243
]
}
{
'name' : "Jimmy",
'userId' : 12346,
'damage_dealt' : [
"character_name" : "James",
"stat_value" : 1020243
]
}
{
'name' : "Tony",
'userId' : 12345,
'damage_dealt' : [
"character_name" : "Lebron",
"stat_value" : 99900243
]
}
A sample output for what I'm looking for is below:
[
{
'_id':12345,
'user' : 'Tony'
'character_name' : 'Lebron',
'stat_value' : 99900243
},
{
'_id':12346,
'user' : 'Jimmy'
'character_name' : 'James',
'stat_value' : 1020243
}
]
You can use the $top accumulator to achieve the desired result. Like this:
db.collection.aggregate([
{
"$unwind": "$damage_dealt"
},
{
"$group": {
"_id": "$userId",
"value": {
$top: {
output: {
character_name: "$damage_dealt.character_name",
stat_value: "$damage_dealt.stat_value"
},
sortBy: {
"damage_dealt.stat_value": -1
}
}
},
}
},
{
"$project": {
character_name: "$value.character_name",
stat_value: "$value.stat_value"
}
},
{
"$sort": {
"stat_value": -1
}
},
{
"$limit": 30
}
])
Playground link.
Or collects all the group elements in an array, and the max stat_value, then pick the object from the array containing the max stat_value.
db.collection.aggregate([
{
"$unwind": "$damage_dealt"
},
{
"$group": {
"_id": "$userId",
"max_stat": {
"$max": "$damage_dealt.stat_value"
},
"damages": {
"$push": {
name: "$name",
damage_value: "$damage_dealt"
}
}
}
},
{
"$project": {
"damages": {
"$arrayElemAt": [
{
"$filter": {
"input": "$damages",
"as": "damage",
"cond": {
"$eq": [
"$$damage.damage_value.stat_value",
"$max_stat"
]
}
}
},
0
]
}
}
},
{
"$project": {
"character_name": "$damages.damage_value.character_name",
"stat_value": "$damages.damage_value.stat_value",
"name": "$damages.name"
}
},
{
"$sort": {
"stat_value": -1
}
},
{
"$limit": 30
}
])
Playground link.
Here's another way you could do it.
db.collection.aggregate([
{
"$group": {
"_id": "$userId",
"user": {"$first": "$name"},
"damage_dealts": {"$push": "$damage_dealt"},
"maxStat": {"$max": {"$first": "$damage_dealt.stat_value"}}
}
},
{
"$set": {
"outChar": {
"$first": {
"$arrayElemAt": [
"$damage_dealts",
{"$indexOfArray": ["$damage_dealts.stat_value", "$maxStat"]}
]
}
}
}
},
{
"$project": {
"user": 1,
"character_name": "$outChar.character_name",
"stat_value": "$outChar.stat_value"
}
},
{"$sort": {"stat_value": -1}},
{"$limit": 30}
])
Try it on mongoplayground.net.

Retrieve the field from the array

I have a document like this
[
{
"empno": "×325007",
"vehicle": [
{
"valdate": "2020-08-02T13:17z",
"Inspectvalue": {
"price": "2000"
}
}
]
}
]
But I want document like this
{
"empno":"x325007",
"valdate":"2020-08-02T13:17z",
"price":"2000"
}
keeping them in the array, and including the keys to each element.
Example
db.collection.aggregate([
{
$addFields: {
newData: {
"$map": {
"input": "$vehicle",
"as": "v",
"in": {
valdate: "$$v.valdate",
price: "$$v.Inspectvalue.price",
empno: "$empno"
}
}
}
}
},
{
$unset: [
"vehicle",
"empno"
],
}
])
(you can't have repeated top level fields in JSON).
Or if you want them as top level keys, unwind the elements:
db.collection.aggregate([
{
$unwind: "$vehicle"
},
{
$addFields: {
valdate: "vehicle.valdate",
price: "vehicle.Inspectvalue.price"
}
},
{
$unset: "vehicle"
}
])
Example

Mongodb aggregation $size inside nested array

I have a problem with a query with aggregation framework.
Given a collection with documents like:
db.testSize.insert([{
"internalId" :1,
"first" : {
"second" : [
{
"value" : 1
}
]
}
}])
this aggregation :
db.testSize.aggregate([
{ $addFields: { tmpSize: { $strLenCP: { $ifNull: [ { $toString: "$first.second.value" }, "" ] } } } },
])
return this error:
{
"message" : "Unsupported conversion from array to string in $convert with no onError value",
"ok" : 0,
"code" : 241,
"codeName" : "ConversionFailure",
"name" : "MongoError"
}
Now, the solution on this problem is to use unwind in the following way:
db.testSize.aggregate([
{ $unwind: "$first.second"},
{ $addFields: { tmpSize: { $strLenCP: { $ifNull: [ { $toString: "$first.second.value" }, "" ] } } } },
])
But my requirement is to create a general approach for documents with various shape and possible nested array inside array.
Due this bug https://jira.mongodb.org/browse/SERVER-6436 seems to be impossible to unwind array inside array, so how to solve this problem ?
There is an approach ?
Some context:
I cannot change document structure before aggregation
I don't know where array will be in "field hierarchy", if first for example is an array, or is second
Thanks in advance
You can use $reduce.
====== Aggregate ======
db.testSize.aggregate([
{
"$addFields": {
"first.second.tmpSize": {
"$reduce": {
"input": "$first.second",
"initialValue": "",
"in": {
$strLenCP: {
$ifNull: [
{
$toString: "$$this.value"
},
""
]
}
}
}
}
}
}
])
====== Result ======
[
{
"_id": ObjectId("5d925bd3fabc692265f950d5"),
"first": {
"second": [
{
"tmpSize": 1,
"value": 1
}
]
},
"internalId": 1
}
]
Mongo Playground