How change the MongoDB aggregation result output? - mongodb

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' }
}
}

Related

Mongoose subquery

I have a collection that looks like below:
[
{
"orderNum": "100",
"createdTime": ISODate("2020-12-01T21:00:00.000Z"),
"amount": 100,
"memo": "100memo",
"list": [
1
]
},
{
"orderNum": "200",
"createdTime": ISODate("2020-12-01T21:01:00.000Z"),
"amount": 200,
"memo": "200memo",
"list": [
1,
2
]
},
{
"orderNum": "300",
"createdTime": ISODate("2020-12-01T21:02:00.000Z"),
"amount": 300,
"memo": "300memo"
},
{
"orderNum": "400",
"createdTime": ISODate("2020-12-01T21:03:00.000Z"),
"amount": 400,
"memo": "400memo"
},
]
and I'm trying to get the total amount of orders that were created before order# 300 (so order#100 and #200, total amount is 300).
Does anyone know how to get it via Mongoose?
You can use this one:
db.collection.aggregate([
{ $sort: { orderNum: 1 } }, // by default the order of documents in a collection is undetermined
{ $group: { _id: null, data: { $push: "$$ROOT" } } }, // put all documents into one document
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } }, // cut desired elementes from array
{ $unwind: "$data" }, // transform back to documents
{ $replaceRoot: { newRoot: "$data" } },
{ $group: { _id: null, total_amount: { $sum: "$amount" } } } // make summary
])
Actually it is not needed to $unwind and $group, so the shortcut would be this:
db.collection.aggregate([
{ $sort: { orderNum: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $project: { total_amount: { $sum: "$data.amount" } } }
])
But the answer from #turivishal is even better.
Update for additional field
{
$set: {
data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] },
memo: { $arrayElemAt: [ "$data.memo", { $indexOfArray: ["$data.orderNum", "300"] } ] }
}
}
or
{ $set: { data: { $slice: ["$data", { $indexOfArray: ["$data.orderNum", "300"] }] } } },
{ $set: { memo: { $last: { "$data.memo" } } },
$match orderNum less than 300
$group by null and get totalAmount using $sum of amount
YourSchemaModel.aggregate([
{ $match: { orderNum: { $lt: "300" } } },
{
$group: {
_id: null,
totalAmount: { $sum: "$amount" }
}
}
])
Playground

MongoDb count elements of array in array by $size

I need help to count the elements of an array when it is in another array.
My command, when I tried to select the second array is,
db.artysci.aggregate([
{
$project: {
_id:0,
nazwa: 1,
nazwisko: 1,
numberOfSongs: { "album": {$size: "$utwor"}}
}
}
] )
Grid:
db.artysci.insert({
imie: 'Laurie',
nazwisko: 'Adkins',
rok_debiutu: 2006,
kraj_pochodzenia: ['Wielka Brytania'],
gatunek: 'neo soul',
album: [{
tytul:"19",
rok_edycji:2007,
gatunek: 'pop',
typ_nosnika: 'CD',
utwor: [{
numer: 1,
tytul_utworu: 'Daydreamer',
dlugosc_trwania: 3.41
},
{
numer: 2,
tytul_utworu: 'Best for Last',
dlugosc_trwania: 4.19
},
{
numer: 3,
tytul_utworu: 'Chasing Pavements',
dlugosc_trwania: 3.31
}
]
}]
})
Output when counting by $size:"$album",
{
"nazwisko" : "Adkins",
"numberOfSongs" : {
"album" : NumberInt(3)
}
}
How can I count elements of an array in an array by $size?
You can achieve this using Map and then summing it up. It works..
db.artysci.aggregate({
"$project": {
_id: 0,
nazwa: 1,
nazwisko: 1,
"numberOfAlbums": { "$size": { $ifNull: ["$album", []] } },
"numberOfSongs": {
"$sum": {
"$map": {
"input": "$album",
"in": { "$size": { $ifNull: ["$$this.utwor", []] } }
}
}
}
}
})
#Kacper,
Here is the soultion for your second question.
Yes, you can achieve it in either way, using the above method or using unwind and do the average..
Lets see an example using unwind:
Without divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: "$album.utwor.dlugosc_trwania" }
}
},
]);
With divide/second:
db.notifications.aggregate([
{ $unwind: "$album" },
{ $unwind: "$album.utwor" },
{
$group: {
_id: "$_id",
avgDuration: { $avg: { $divide: ["$album.utwor.dlugosc_trwania", 60] } }
}
},
]);
You can use $unwind and $group to get the counts.
db.collection.aggregate([
{
$unwind: "$album"
},
{
$unwind: "$album.utwor"
},
{
$group: {
_id: 0,
total: {
$sum: 1
}
}
}
])
Play
If you need more information, add it to your question.

Add field and groupby in in MongoDB

I want to be able to add a new field in aggregation and return the average value of all records with the same session ID. Something like this, but it's not possible to use a groupby inside addfield:
{
$addFields: {
sessionData: {
$group: {
_id: {
"sessionId": "$sessionId"
},
avgScrollDepth: { $avg: "$scrollDepthChange" },
totalSessionLength: { $max: "$scrollDepthChange" }
}
}
}
},
Edit:
Lets say these are the documents:
{
"sessionId": 1,
"sessionDepth": 1
},
{
"sessionId": 1,
"sessionDepth": 2
},
{
"sessionId": 1,
"sessionDepth": 3
}
I would want to return documents like this:
{
"sessionId": 1,
"sessionDepth": 1,
"totalSessionLength": 3
},
{
"sessionId": 1,
"sessionDepth": 2,
"totalSessionLength": 3
},
{
"sessionId": 1,
"sessionDepth": 3,
"totalSessionLength": 3
}
You can do it this way:
We store inside tmp variable all grouped documents
Count totalSessionLength
Flatten tmp variable with $unwind operator
Add totalSessionLength into final result
db.collection.aggregate([
{
$group: {
_id: "$sessionId",
totalSessionLength: {
$sum: 1
},
tmp: {
$push: {
"sessionId": "$sessionId",
"sessionDepth": "$sessionDepth"
}
}
}
},
{
$unwind: "$tmp"
},
{
$project: {
"_id": 0,
"sessionId": "$tmp.sessionId",
"sessionDepth": "$tmp.sessionDepth",
"totalSessionLength": "$totalSessionLength"
}
}
])
MongoPlayground
Generic way:
db.collection.aggregate([
{
$group: {
_id: "$sessionId",
totalSessionLength: {
$sum: 1
},
tmp: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$tmp"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$tmp",
{
totalSessionLength: "$totalSessionLength"
}
]
}
}
}
])
MongoPlayground

MongoDB to return formatted object when no results can be found

I have the following stage in my MongoDB aggregation pipeline that returns the qty and sum of sales, which works fine:
{
$lookup: {
from: 'sales',
let: { part: '$_id' },
pipeline: [
{ $match: { $and: [{ $expr: { $eq: ['$partner', '$$part'] } }] } },
{ $group: { _id: null, qty: { $sum: 1 }, soldFor: { $sum: '$soldFor' } } },
{ $project: { _id: 0, qty: 1, soldFor: 1 } }],
as: 'sales'}},
{ $unwind: { path: '$sales', preserveNullAndEmptyArrays: true } },
{ $project: { _id: 1, sales: 1 }
}
However, if there are no sales, then the $project projection returns an empty sales object, but what I'd really like is it to return a completed object, but with 0 - like this:
{
sales: {
qty: 0,
soldFor: 0
}
}
You can use $cond operator here
{
"$project": {
"_id": 1,
"sales": {
"$cond": [
{ "$eq": [{ "$size": "$sales" }, 0] },
{
"sales": {
"qty": 0,
"soldFor": 0
}
},
"$sales"
]
}
}
}

How do I flatten the results of an aggregation?

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 }
}
}
])