Nested array aggregation - mongodb

How can I obtain the sum of all the first elements in each of the arrays in this document using the MongoDB aggregation framework?
{
items: {
item1: [5, 8, 2],
item2: [4, 3, 1],
...
}
}
Here is the partial pipeline I've tried:
[{
$addFields: {
itemsAsArray: {
$objectToArray: '$items'
}
}
}, {
$project: {
_id: 0,
itemsAsArray: 1
}
}, {
$unwind: '$itemsAsArray'
}]
So I take this items value from the document that was matched, convert it to an array, and unwind it. Now I have an array of objects like this:
[{
itemsAsArray: {
k: 'item1',
v: [5, 8, 2]
}
},
{
itemsAsArray: {
k: 'item2',
v: [4, 3, 1]
}
}, ... ]
With this array, how can I group them to yield the sum of the first element in each of the arrays now denoted as v? Am I on the right track?

Related

How to use a value from another key in field path in MongoDB Aggregation?

Documents
{ color: 'red',
value: {
red: {
level1: {
level2: 5
}}}}
{ color: 'blue',
value: {
blue: {
level1: {
level2: 8
}}}}
How to aggregate the values of value.red.level1.level2 and value.blue.level1.level2?
The keys red and blue come from the key color.
#turivishal requested more info:
I want to use $bucket.
{ '$bucket': {
groupBy: '$value.*red*.level1.level2',
boundaries: [1,2,3,4,5,6,7,8,9],
output: {
count: { '$sum': 1 }}}}
The expected result would be
[{ id: 5, count: 1}, { id: 8, count: 1 }]
You can access it by converting it to an array of objects,
$objectToArray to convert an object to an array of objects that will convert in k (key) v (value) format
$arrayElemAt to get first element from an array, you can use it directly in $bucket's groupBy property
db.collection.aggregate([
{
$addFields: {
value: { $objectToArray: "$value" }
}
},
{
$bucket: {
groupBy: { $arrayElemAt: ["$value.v.level1.level2", 0] },
boundaries: [1, 2, 3, 4, 5, 6, 7, 8, 9],
output: {
count: { $sum: 1 }
}
}
}
])
Playground
In the second approach, you can use all operations in the direct $bucket's groupBy property using $let operator,
If you are not using projection stages before $bucket then you can use this approach to avoid the more stages
db.collection.aggregate([
{
$bucket: {
groupBy: {
$let: {
vars: { value: { $objectToArray: "$value" } },
in: { $arrayElemAt: ["$$value.v.level1.level2", 0] }
}
},
boundaries: [1, 2, 3, 4, 5, 6, 7, 8, 9],
output: {
count: { $sum: 1 }
}
}
}
])
Playground
I ended up using $addField and $ifNull.
$ifNull takes an array and returns the first value that is not null.

MongoDB Query Dynamic keys

Need to find quoteIds where having different values and keys should start with 1 or 2 or 3. Could you please help.
{
"quoteId": 1,
"screening": {
"101": 1,
"201": 1,
"301": 1,
"100": 1,
"200": 1,
"300": 1,
"111": 1,
"211": 1,
"311": 1
}
}
{
"quoteId": 2,
"screening": {
"101": 1,
"201": 1,
"301": 1,
"100": 1,
"200": 1,
"300": 1,
"111": 1,
"211": 2,
"311": 1
}
}
$set - Create screenings array field, by converting object (key-value pair) to multiple documents (via $objectToArray) and fulfill the regex with starting of 1 or 2 or 3 in $filter.
$match - Filter documents that screenings is not an empty array.
$unset - Remove screenings field.
db.collection.aggregate([
{
$set: {
screenings: {
$filter: {
input: {
"$objectToArray": "$screening"
},
cond: {
"$regexMatch": {
input: "$$this.k",
regex: "^(1|2|3)"
}
}
}
}
}
},
{
$match: {
screenings: {
$ne: []
}
}
},
{
$unset: "screenings"
}
])
Sample Mongo Playground

How do I produce the union of embedded arrays in mongodb aggregate

I have a set of documents of the form:
{
skill_id: 2,
skill_recs: [
{
_id: 4,
member_ids: [1, 4, 5]
}
]
},
{
skill_id: 5,
skill_recs: [
{
_id: 4,
member_ids: [1, 7, 9]
}
]
}
Now I want to aggregate a set of these documents such that skill_recs are combined by _id and the member_ids of all combined docs are merged into a single union of values...
{ _id: 4,
member_ids: [1, 4, 5, 7, 9]
}
I get most of the way with:
db.aggregate([
{
$unwind: '$skill_recs'
},
{
$group: {
_id: '$skill_recs._id',
all_member_ids: {$push: '$skill_recs.member_ids'}
}
},
{
$addFields: {
member_ids: {$setUnion: '$all_member_ids'}
}
}
])
but the $setUnion doesn't do a union of the array of arrays that it is passed.
Instead it produces:
{ _id: 4,
member_ids: [[1, 4, 5], [1, 7, 9]]
}
Any way to produce the union of these arrays?
You're quite close, Here's a quick example of how to achieve this using $reduce
db.collection.aggregate([
{
$unwind: "$skill_recs"
},
{
$group: {
_id: "$skill_recs._id",
all_member_ids: {
$push: "$skill_recs.member_ids"
}
}
},
{
$addFields: {
member_ids: {
$reduce: {
input: "$all_member_ids",
initialValue: [],
in: {
$setUnion: [
"$$this",
"$$value"
]
}
}
}
}
}
])
Mongo Playground

Mongodb aggregation - count arrays with elements having integer value greater than

I need to write a MongoDB aggregation pipeline to count the objects having arrays containing two type of values:
>=10
>=20
This is my dataset:
[
{ values: [ 1, 2, 3] },
{ values: [12, 1, 3] },
{ values: [1, 21, 3] },
{ values: [1, 2, 29] },
{ values: [22, 9, 2] }
]
This would be the expected output
{
has10s: 4,
has20s: 3
}
Mongo's $in (aggregation) seems to be the tool for the job, except I can't get it to work.
This is my (non working) pipeline:
db.mytable.aggregate([
{
$project: {
"has10s" : {
"$in": [ { "$gte" : [10, "$$CURRENT"]}, "$values"]}
},
"has20s" : {
"$in": [ { "$gte" : [20, "$$CURRENT"]}, "$values"]}
}
},
{ $group: { ... sum ... } }
])
The output of $in seems to be always true. Can anyone help?
You can try something like this:
db.collection.aggregate([{
$project: {
_id: 0,
has10: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 10 ] }
}
}
},
has20: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 20 ] }
}
}
}
}
},
{
$group: {
_id: 1,
has10: { $sum: "$has10" },
has20: { $sum: "$has20" }
}
}
])
Using $project with $filter to get the actual elements and then via $size to get the array length.
See it working here

mongodb aggregation pipeline, fold an array property in `$project`ion

Given a collection of documents that each has an array property ks:
{
_id: ObjectId('...'),
ks: [4, 3, 2, 1, 3],
v: 45
},
{
_id: ObjectId('...'),
ks: [3, 3, 5],
v: 21
},
{
_id: ObjectId('...'),
ks: [1, 5, 2, 8, 9, 7],
v: 12
}
How can I aggregate this collection to a list using key = min ks or other fold functions?
[
{
_id: 1,
v: 28.5 // = mean [45, 12]
},
{
_id: 3,
v: 21 // = mean [21]
}
]
Grouping using the keyf function works
keyf: function(d) { d.ks.reduce(function(acc, a) { return acc<a ? acc : a; }) }
But is there a way to do this with aggregation pipeline?
It seems that you want the minimum $min value of ks for your aggregation key and the $avg of "v" for each min ks. You need to $unwind "ks" first.
You also need to $group your data twice, once for finding the min of ks and the next time for calculating the avg of v.
db.collection.aggregate([
// Unwind the array
{ "$unwind": "$ks" },
// Find the minimal key per document
{ "$group": {
"_id": "$_id",
"ks": { "$min": "$ks" },
"v": { "$first": "$v" }
}},
// Group with the average value
{ "$group": {
"_id": "$ks",
"v": { "$avg": "$v" }
}},
// Group does not sort results
{ "$sort": { "_id": 1 } }
])
Results in:
[
{
"_id" : 1,
"v" : 28.5
},
{
"_id" : 3,
"v" : 21
}
]