How do I flatten the results of an aggregation? - mongodb

I have the following query...
db.getCollection('apprenticeships')
.aggregate([
{
$match: {
'Vacancy._id': { $in: [1, 2, 3] },
}
},
{
$group: {
'_id': {
'VacancyId': '$Vacancy._id',
'Status': '$Status'
},
'Count': { $sum: 1 }
}
},
{
$sort: {
'_id.VacancyId': 1,
'_id.Status': 1
}
}
])
Which gives results where each element has following structure
{
"_id" : {
"VacancyId" : 1,
"Status" : 90
},
"Count" : 40.0
}
How can I remap that structure so that the elements in the output look like this instead?
{
"VacancyId": 1,
"Status": 90,
"Count": 40
}

You can add $project stage to aggregation pipeline to add new fields VacancyId and status and then hide the _id
db.getCollection('apprenticeships')
.aggregate([{
$match: {
'Vacancy._id': {
$in: [1, 2, 3]
},
}
},
{
$group: {
'_id': {
'VacancyId': '$Vacancy._id',
'Status': '$Status'
},
'Count': {
$sum: 1
}
}
},
{
$sort: {
'_id.VacancyId': 1,
'_id.Status': 1
}
},
{
{
$project:{ 'VacancyId': '$_id.VacancyId', 'Status': '$_id.Status', 'Count': '$Count', '_id': 0 }
}
}
])

Related

How to get this pipeline to return exactly one document?

I am running the following aggregation pipeline:
const agg = [
{
'$match': {
'aaa': 'bbb'
}
}, {
'$group': {
'_id': '',
'total': {
'$sum': '$num'
}
}
}
];
My problem is, when $match matches nothing, the pipeline returns 0 documents. How do I get the pipeline to always return 1 document?
In MongoDB version 6.0 you can do it like this one:
db.collection.aggregate([
{ $match: { aaa: 'bbb' } },
{
$group: {
_id: null,
total: { $sum: "$num" }
}
},
{
$densify: {
field: "total",
range: { step: 1, bounds: [0, 0] }
}
},
{ $set: { _id: { $cond: [{ $eq: [{ $type: "$_id" }, "missing"] }, MaxKey, "$_id"] } } },
{ $sort: { _id: 1 } },
{ $limit: 1 }
])
In version < 6.0 you can try this one:
db.collection.aggregate([
{
$facet: {
data: [
{ $match: { aaa: 'bbb' } },
{ $group: { _id: null, total: { $sum: "$num" } } }
],
default: [
{ $limit: 1 },
{ $group: { _id: null, total: { $sum: 0 } } },
{ $set: { _id: MaxKey } }
]
}
},
{ $replaceWith: { $mergeObjects: [{ $first: "$default" }, { $first: "$data" }] } },
])
Or this one:
db.collection.aggregate([
{ $match: { aaa: 'bbb' } },
{ $group: { _id: null, total: { $sum: "$num" } } },
{
$unionWith: {
coll: "collection",
pipeline: [
{ $limit: 1 },
{ $set: { _id: MaxKey, total: 0 } },
{ $project: { _id: 1, total: 1 } }
]
}
},
{ $sort: { _id: 1 } },
{ $limit: 1 }
])

Aggregate function isn't showing results with a count greater than 1

I'm running this aggregate function, which is supposed to only show results when they have a count greater than 1. When I remove 'count': { '$gt': 1 } the aggregate works, however it obviously shows all results. How should I use this count correctly?
db.getCollection('songs').aggregate([
{
'$match': { 'is_song': 1, 'is_soundtrack': 0, 'count': { '$gt': 1 } }
},
{
'$group': { '_id': { 'name': '$name', 'artist_id': '$artist_id' }, 'count': { '$sum': 1 } }
},
{
'$sort': { 'count': -1 }
}
])
Sample data:
{
"_id" : ObjectId("5f93a43b4e8883298849ad18"),
"name" : "Come Fly With Me",
"song_id" : 5,
"artist_id" : 5,
"is_song" : 1,
"is_soundtrack" : 0,
"updatedAt" : ISODate("2016-10-04T13:34:53.328Z")
}
You should not add 'count': { '$gt': 1 } in the first $match stage.
As the count field is only populated after the $group stage.
So, you need add another $match stage after $group stage for filtering document with the count value is greater than 1.
db.collection.aggregate([
{
"$match": {
"is_song": 1,
"is_soundtrack": 0
}
},
{
"$group": {
"_id": {
"name": "$name",
"artist_id": "$artist_id"
},
"count": {
"$sum": 1
}
}
},
{
$match: {
"count": {
"$gt": 1
}
}
},
{
"$sort": {
"count": -1
}
}
])
Sample Mongo Playground

MongoDB get size of unwinded array

I'm trying to return size of 'orders' and sum of 'item' values for each 'order' for each order from documents like the example document:
orders: [
{
order_id: 1,
items: [
{
item_id: 1,
value:100
},
{
item_id: 2,
value:200
}
]
},
{
order_id: 2,
items: [
{
item_id: 3,
value:300
},
{
item_id: 4,
value:400
}
]
}
]
I'm using following aggregation to return them, everything works fine except I can't get size of 'orders' array because after unwind, 'orders' array is turned into an object and I can't call $size on it since it is an object now.
db.users.aggregate([
{
$unwind: "$orders"
},
{
$project: {
_id: 0,
total_values: {
$reduce: {
input: "$orders.items",
initialValue: 0,
in: { $add: ["$$value", "$$this.value"] }
}
},
order_count: {$size: '$orders'}, //I get 'The argument to $size must be an array, but was of type: object' error
}
},
])
the result I expected is:
{order_count:2, total_values:1000} //For example document
{order_count:3, total_values:1500}
{order_count:5, total_values:2500}
I found a way to get the results that I wanted. Here is the code
db.users.aggregate([
{
$project: {
_id: 1, orders: 1, order_count: { $size: '$orders' }
}
},
{ $unwind: '$orders' },
{
$project: {
_id: '$_id', items: '$orders.items', order_count: '$order_count'
}
},
{ $unwind: '$items' },
{
$project: {
_id: '$_id', sum: { $sum: '$items.value' }, order_count: '$order_count'
}
},
{
$group: {
_id: { _id: '$_id', order_count: '$order_count' }, total_values: { $sum: '$sum' }
}
},
])
output:
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f1"), order_count: 2 }, total_values: 1000 }
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f2"), order_count: 3 }, total_values: 1500 }

How change the MongoDB aggregation result output?

With this MongoDB aggregation pipeline:
db.getCollection('device1_hour_events').aggregate([
{ $match: { 'ts_hour' : ISODate('2013-10-11T04:00:00.000Z') } },
{ $unwind: '$minutes' },
{ $match: { 'minutes.min': { $gt: -1, $lt: 2 } } },
{ $unwind: '$minutes.seconds' },
{ $group: { '_id': '$minutes.min',
'temp_min': { $min: '$minutes.seconds.temp' },
'temp_avg': { $avg: '$minutes.seconds.temp' },
'temp_max': { $max: '$minutes.seconds.temp' }
}
},
{ $sort: { '_id': 1} }
])
that produces the following result:
/* 1 */
{
"_id": 0,
"temp_min": 12,
"temp_avg": 47.25,
"temp_max": 99
}
/* 2 */
{
"_id": 1,
"temp_min": 35,
"temp_avg": 47.67,
"temp_max": 65
}
It's possible to obtain maybe with $project the following output:
{
"_id": [0, 1],
"temp_min": [12, 35],
"temp_avg": [47.25, 47.67],
"temp_max": [99, 65]
}
You can add another $group with a $push for each field :
{
$group: {
'_id': 0,
'_ids': { $push: '$_id' },
'temp_min': { $push: '$temp_min' },
'temp_avg': { $push: '$temp_avg' },
'temp_max': { $push: '$temp_max' }
}
}

Return 5 elements for for each type with aggregation

How do I create an aggregate operation that shows me 5 for each type?
For example, what I need is to show 5 of type= 1 , 5 of type=2 and 5 of type=3.
I have tried:
db.items.aggregate([
{$match : { "type" : { $gte:1,$lte:3 }}},
{$project: { "type": 1, "subtipo": 1, "dateupdate": 1, "latide": 1, "long": 1, "view": 1,month: { $month: "$dateupdate" } }},
{$sort:{view: -1, dateupdate: -1}},
{$limit:5}
]);
After the $match pipeline, you need to do an initial group which creates an array of the original documents. After that you can $slice the array with the documents to return the 5 elements.
The intuition can be followed in this example:
db.items.aggregate([
{ '$match' : { 'type': { '$gte': 1, '$lte': 3 } } },
{
'$group': {
'_id': '$type',
'docs': { '$push': '$$ROOT' },
}
},
{
'$project': {
'five_docs': {
'$slice': ['$docs', 5]
}
}
}
])
The above will return the 5 documents unsorted in an array. If you need to return the TOP 5 documents in sorted order then you can introduce a $sort pipeline before grouping the docs that re-orders the documents getting into the $group pipeline by the type and dateupdate fields:
db.items.aggregate([
{ '$match' : { 'type': { '$gte': 1, '$lte': 3 } } },
{ '$sort': { 'type': 1, 'dateupdate': -1 } }, // <-- re-order here
{
'$group': {
'_id': '$type',
'docs': { '$push': '$$ROOT' },
}
},
{
'$project': {
'top_five': {
'$slice': ['$docs', 5]
}
}
}
])