MongoDB aggregation counts based on value - mongodb

I have a MongoDB query I am trying to figure out. I'd like to group my data by date and one other field (portfolio) and get the counts for each buildResult in that grouping.
Sample data looks like this:
{
"_id" : ObjectId("52dea764e4b0a491abb54102"),
"type" : "build",
"time" : ISODate("2014-01-21T16:59:16.642Z"),
"data" : {
"buildNumber" : 35,
"buildDuration" : 1034300,
"portfolio" : "Shared",
"buildResult" : "FAILURE"
}
}
{
"_id" : ObjectId("52dea7b7e4b0a491abb54103"),
"type" : "build",
"time" : ISODate("2014-01-21T17:00:39.617Z"),
"data" : {
"buildNumber" : 13,
"buildDuration" : 1186028,
"portfolio" : "Sample",
"buildResult" : "SUCCESS"
}
}
The output I am shooting for would be something like this:
{
"result" : [
{
"_id" : {
"month" : 2,
"day" : 28,
"year" : 2014,
"portfolio" : "Shared"
},
"aborted" : 3,
"failure" : 0,
"unstable" : 0,
"success" : 34
},
{
"_id" : {
"month" : 2,
"day" : 28,
"year" : 2014,
"portfolio" : "Sample"
},
"aborted" : 3,
"failure" : 2,
"unstable" : 0,
"success" : 37
}
],
"ok" : 1
}
My current query is:
db.builds.aggregate([
{ $match: { "data.buildResult" : { $ne : null} }},
{ $group: {
_id: {
month: { $month: "$time" },
day: { $dayOfMonth: "$time" },
year: { $year: "$time" },
portfolio: "$data.portfolio",
},
aborted: { $sum: { "$data.buildResult": "ABORTED" } },
failure: { $sum: { "$data.buildResult": "FAILURE" } },
unstable: { $sum: { "$data.buildResult": "UNSTABLE" } },
success: { $sum: { "$data.buildResult": "SUCCESS" } }
} },
{ $sort: { "_id.day": 1, "_id.month": 1, "_id.year": 1 } }
])
I have tried many variations with the following lines including $match, $in and other operators. Any help would be very appreciated.
aborted: { $sum: { "$data.buildResult": "ABORTED" } },
failure: { $sum: { "$data.buildResult": "FAILURE" } },
unstable: { $sum: { "$data.buildResult": "UNSTABLE" } },
success: { $sum: { "$data.buildResult": "SUCCESS" } }

To achieve that you can use the $cond and $eq operators like this:
aborted: {$sum: {$cond : [{$eq : ["$data.buildResult", "ABORTED"]}, 1, 0]}}
Edit:
As noted by Neil Lunn in the comments, the $cond here is irrelevant because the $eq operator already returns 0 or 1.
aborted: {$sum: {$eq : ["$data.buildResult", "ABORTED"]}}

Related

Mongodb aggregation json format by month result

Hello I am working with the reporting api which will going to use in highcharts and I am new to mongodb.
Below is my aggregation query (suggest me modification) :
db.product_sold.aggregate({
$group: {
_id: { year: { $year: "$solddate" }, month: { $month: "$solddate" }, productid: "$productid" },
totalQty: { $sum: "$qty" }
}
})
Output:
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(2),
"productid" : "11"
},
"totalQty" : 6.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(2),
"productid" : "14"
},
"totalQty" : 7.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(1),
"productid" : "13"
},
"totalQty" : 3.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(2),
"productid" : "10"
},
"totalQty" : 6.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2018),
"month" : NumberInt(2),
"productid" : "12"
},
"totalQty" : 5.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(2),
"productid" : "15"
},
"totalQty" : 8.0
}
// ----------------------------------------------
{
"_id" : {
"year" : NumberInt(2019),
"month" : NumberInt(1),
"productid" : "11"
},
"totalQty" : 2.0
}
// ----------------------------------------------
What I want in output is something like :
status: 200,
msg: "SUCCESS"
data: [{
1:[
{
"productid": 11,
"totalQty": 3
},
{
"productid": 12,
"totalQty": 27
}
],
2:[
{
"productid": 11,
"totalQty": 64
},
{
"productid": 12,
"totalQty": 10
}
]
}]
For reference attaching the image of the collection
Is there any way to achieve it using aggregation or anything else or I will have to do it manually by code ?
You can append below aggreagation stages to your current pipeline:
db.product_sold.aggregate([
// your current $group stage
{
$group: {
_id: "$_id.month",
docs: { $push: { productid: "$_id.productid", totalQty: "$totalQty" } }
}
},
{
$project: {
_id: 0,
k: { $toString: "$_id" },
v: "$docs"
}
},
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$project: {
_id: 0,
data: { $arrayToObject: "$data" }
}
}
])
The idea here is that you can use $group with _id set to null to get all the data into single document and then use $arrayToObject to get month number as key and all the aggregates for that month as value.

MongoDB aggregate array of objects together by object id and count occurences

I'm trying to figure out what I'm doing wrong, I have collected the following, "Subset of data", "Desired output"
This is how my data objects look
[{
"survey_answers": [
{
"id": "9ca01568e8dbb247", // As they are, this is the key to groupBy
"option_answer": 5, // Represent the index of the choosen option
"type": "OPINION_SCALE" // Opinion scales are 0-10 (meaning elleven options)
},
{
"id": "ba37125ec32b2a99",
"option_answer": 3,
"type": "LABELED_QUESTIONS" // Labeled questions are 0-x (they can change it from survey to survey)
}
],
"survey_id": "test"
},
{
"survey_answers": [
{
"id": "9ca01568e8dbb247",
"option_answer": 0,
"type": "OPINION_SCALE"
},
{
"id": "ba37125ec32b2a99",
"option_answer": 3,
"type": "LABELED_QUESTIONS"
}
],
"survey_id": "test"
}]
My desired output is:
[
{
id: '9ca01568e8dbb247'
results: [
{ _id: 5, count: 1 },
{ _id: 0, count: 1 }
]
},
{
id: 'ba37125ec32b2a99'
results: [
{ _id: 3, count: 2 }
]
}
]
Active query
Model.aggregate([
{
$match: {
'survey_id': survey_id
}
},
{
$unwind: "$survey_answers"
},
{
$group: {
_id: "$survey_answers.option_answer",
count: {
$sum: 1
}
}
}
])
Current output
[
{
"_id": 0,
"count": 1
},
{
"_id": 3,
"count": 2
},
{
"_id": 5,
"count": 1
}
]
I added your records to my db. Post that I tried your commands one by one.
$unwind results you similar to -
> db.survey.aggregate({$unwind: "$survey_answers"})
{ "_id" : ObjectId("5c3859e459875873b5e6ee3c"), "survey_answers" : { "id" : "9ca01568e8dbb247", "option_answer" : 5, "type" : "OPINION_SCALE" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3c"), "survey_answers" : { "id" : "ba37125ec32b2a99", "option_answer" : 3, "type" : "LABELED_QUESTIONS" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3d"), "survey_answers" : { "id" : "9ca01568e8dbb247", "option_answer" : 0, "type" : "OPINION_SCALE" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3d"), "survey_answers" : { "id" : "ba37125ec32b2a99", "option_answer" : 3, "type" : "LABELED_QUESTIONS" }, "survey_id" : "test" }
I am not adding code for match since that is okay in your query as well
The grouping would be -
> db.survey.aggregate({$unwind: "$survey_answers"},{$group: { _id: { 'optionAnswer': "$survey_answers.option_answer", 'id':"$survey_answers.id"}, count: { $sum: 1}}})
{ "_id" : { "optionAnswer" : 0, "id" : "9ca01568e8dbb247" }, "count" : 1 }
{ "_id" : { "optionAnswer" : 3, "id" : "ba37125ec32b2a99" }, "count" : 2 }
{ "_id" : { "optionAnswer" : 5, "id" : "9ca01568e8dbb247" }, "count" : 1 }
You can group on $survey_answers.id to bring it into projection.
The projection is what you're missing in your query -
> db.survey.aggregate({$unwind: "$survey_answers"},{$group: { _id: { 'optionAnswer': "$survey_answers.option_answer", 'id':'$survey_answers.id'}, count: { $sum: 1}}}, {$project : {answer: '$_id.optionAnswer', id: '$_id.id', count: '$count', _id:0}})
{ "answer" : 0, "id" : "9ca01568e8dbb247", "count" : 1 }
{ "answer" : 3, "id" : "ba37125ec32b2a99", "count" : 2 }
{ "answer" : 5, "id" : "9ca01568e8dbb247", "count" : 1 }
Further you can add a group on id and add results to a set. And your final query would be -
db.survey.aggregate(
{$unwind: "$survey_answers"},
{$group: {
_id: { 'optionAnswer': "$survey_answers.option_answer", 'id':'$survey_answers.id'},
count: { $sum: 1}
}},
{$project : {
answer: '$_id.optionAnswer',
id: '$_id.id',
count: '$count',
_id:0
}},
{$group: {
_id:{id:"$id"},
results: { $addToSet: {answer: "$answer", count: '$count'} }
}},
{$project : {
id: '$_id.id',
answer: '$results',
_id:0
}})
Hope this helps.

filter in aggregation and groupby to get values from given time interval

in the below $aggregate query, want to add a filter with $gt and $lt
The mongo doc:
{
"_id" : ObjectId("599eb4fae0f86361c36b1c91"),
"device_id" : ObjectId("5993df1b9a5fea3183064e49"),
"updatedAt" : ISODate("2017-08-24T11:38:12.135Z"),
"power_data" : [
{
"timestamp" : ISODate("2017-08-24T11:14:04.256Z"),
"_id" : ObjectId("599eb4fdeea8c69622751de3"),
"pfactor" : 1,
"rpower" : 0,
"voltage" : 0,
"current" : 0,
"energy" : 0,
"power" : 0
},
{
"timestamp" : ISODate("2017-08-24T11:14:04.256Z"),
"_id" : ObjectId("599eb507eea8c69622751de4"),
"pfactor" : 1,
"rpower" : 0,
"voltage" : 0,
"current" : 0,
"energy" : 0,
"power" : 0
},
{
"timestamp" : ISODate("2017-08-24T11:14:04.256Z"),
"_id" : ObjectId("599eb511eea8c69622751de5"),
"pfactor" : 1,
"rpower" : 0,
"voltage" : 0,
"current" : 0,
"energy" : 0,
"power" : 0
},
],
"__v" : 0,
"createdAt" : ISODate("2017-08-24T11:14:05.946Z")
}
and here is the query :
aggregate([{
$match: { "device_id": { $in: [ObjectId("5993df1b9a5fea3183064e49")] } }
}, {
$project: {
"power_data": 1
}
}, {
$unwind: "$power_data"
},
{
$project: {
"power": "$power_data.power", "hours": { $dayOfMonth: "$power_data.timestamp" }
}
}, {
$group: {
"_id": "$hours", "avg_power": { $avg: "$power" }
}
}]
all i want is, by passing some date timestamp range, i will get the data for this time interval only, currently its calculating overall.
Thanks For you valuable time!
I believe you just need to add an additional $match stage after the $unwind.
Here's the full pipeline:
{
$match: { "device_id": { $in: [ObjectId("5993df1b9a5fea3183064e49")] } }
},
{
$project: { "power_data": 1 }
},
{
$unwind: "$power_data"
},
{
$match: {
"power_data.timestamp" : { $gt : ISODate("2017-08-23T12:00:00.000Z"), $lt: ISODate("2017-08-25T12:00:00.000Z")}
}
},
{
$project: { "power": "$power_data.power", "hours": { $dayOfMonth: "$power_data.timestamp" }
}
},
{
$group: { "_id": "$hours", "avg_power": { $avg: "$power" } }
}

MongoDB Aggregate does not properly Sort

I've got the following query that works fine. However adding a sort to the aggregation pipeline does not actually sort it. It does change the order of the results but does not properly sort.
Any idea why it refuses to sort the results by date?
db.calls.aggregate({
$match: {
'_id.date': {
$gte: ISODate('2014-04-13T00:00:00.000Z'),
$lte: ISODate('2014-04-20T00:00:00.000Z')
}
}
}, {
$project: {
calls: 1,
year: {
$year: '$_id.date'
},
month: {
$month: '$_id.date'
},
day: {
$dayOfMonth: '$_id.date'
}
}
}, {
$group: {
_id: {
name: "$_id.name",
day: '$day',
year: '$year',
month: '$month'
},
date: {
'$first': '$_id.date'
},
calls: {
'$sum': '$calls'
}
}
}, {
$group: {
_id: '$_id.name',
records: {
$addToSet: {
date: '$date',
calls: '$calls'
}
}
}
}, {
$sort: {
'records.date': 1
}
})
Outputs:
/* 0 */
{
"result" : [
{
"_id" : "a",
"records" : [
{
"date" : ISODate("2014-04-13T00:00:00.000Z"),
"calls" : 522
},
{
"date" : ISODate("2014-04-16T00:00:00.000Z"),
"calls" : 1523
},
{
"date" : ISODate("2014-04-20T00:00:00.000Z"),
"calls" : 57
},
{
"date" : ISODate("2014-04-14T00:00:00.000Z"),
"calls" : 1540
},
{
"date" : ISODate("2014-04-15T00:00:00.000Z"),
"calls" : 1592
},
{
"date" : ISODate("2014-04-17T00:00:00.000Z"),
"calls" : 1466
},
{
"date" : ISODate("2014-04-18T00:00:00.000Z"),
"calls" : 1003
},
{
"date" : ISODate("2014-04-19T00:00:00.000Z"),
"calls" : 623
}
]
},
{
"_id" : "b",
"records" : [
{
"date" : ISODate("2014-04-15T00:00:00.000Z"),
"calls" : 102
},
{
"date" : ISODate("2014-04-17T00:00:00.000Z"),
"calls" : 97
},
{
"date" : ISODate("2014-04-16T00:00:00.000Z"),
"calls" : 116
},
{
"date" : ISODate("2014-04-13T00:00:00.000Z"),
"calls" : 118
},
{
"date" : ISODate("2014-04-14T00:00:00.000Z"),
"calls" : 142
},
{
"date" : ISODate("2014-04-19T00:00:00.000Z"),
"calls" : 68
},
{
"date" : ISODate("2014-04-18T00:00:00.000Z"),
"calls" : 100
},
{
"date" : ISODate("2014-04-20T00:00:00.000Z"),
"calls" : 6
}
]
},
{
"_id" : "c",
"records" : [
{
"date" : ISODate("2014-04-17T00:00:00.000Z"),
"calls" : 137130
},
{
"date" : ISODate("2014-04-14T00:00:00.000Z"),
"calls" : 139497
},
{
"date" : ISODate("2014-04-13T00:00:00.000Z"),
"calls" : 92166
},
{
"date" : ISODate("2014-04-18T00:00:00.000Z"),
"calls" : 123129
},
{
"date" : ISODate("2014-04-15T00:00:00.000Z"),
"calls" : 146390
},
{
"date" : ISODate("2014-04-20T00:00:00.000Z"),
"calls" : 4515
},
{
"date" : ISODate("2014-04-16T00:00:00.000Z"),
"calls" : 141792
},
{
"date" : ISODate("2014-04-19T00:00:00.000Z"),
"calls" : 104847
}
]
}
],
"ok" : 1
}
Document structure:
{
"_id" : {
"date" : ISODate("2014-04-08T12:00:00.000Z"),
"name" : "a"
},
"calls" : 515
}
Fixed by doing a $sort before the last group and switching out $addToSet with $push so the order is preserved.
db.calls.aggregate({
$match: {
'_id.date': {
$gte: ISODate('2014-04-13T00:00:00.000Z'),
$lte: ISODate('2014-04-20T00:00:00.000Z')
}
}
}, {
$project: {
calls: 1,
year: {
$year: '$_id.date'
},
month: {
$month: '$_id.date'
},
day: {
$dayOfMonth: '$_id.date'
}
}
}, {
$group: {
_id: {
name: "$_id.name",
day: '$day',
year: '$year',
month: '$month'
},
date: {
'$first': '$_id.date'
},
calls: {
'$sum': '$calls'
}
}
}, {
$sort: {
date: 1
}
}, {
$group: {
_id: '$_id.name',
records: {
$push: {
date: '$date',
calls: '$calls'
}
}
}
})

MongoDB aggregation sort not working

I'm having a problem applying a sort to an aggregation grouping. My raw data looks like the following:
{
"_id" : ObjectId("52deab2fe4b0a491abb54108"),
"type" : "build",
"time" : ISODate("2014-01-21T17:15:27.471Z"),
"data" : {
"buildNumber" : 43,
"buildDuration" : 997308,
"buildProjectName" : "TestABC",
"buildResult" : "SUCCESS"
}
}
I would like to sort this first by buildProjectName and then date. Here is my query:
db.builds.aggregate([
{ $group: {
_id: {
month: { $month: "$time" },
day: { $dayOfYear: "$time" },
year: { $year: "$time" },
buildProjectName: "$data.buildProjectName",
},
buildDuration: { $avg: "$data.buildDuration" }
} },
{ $sort: {buildProjectName: 1, year: 1, month: 1, day: 1} }
])
I've tried switching the order of the sort (i.e.: buildProjectName, day, month, year), but I always get the same result with the dates out of order:
{
"result" : [
{
"_id" : {
"month" : 1,
"day" : 20,
"year" : 2014,
"buildProjectName" : "TestABC"
},
"buildDuration" : 1170723.5
},
{
"_id" : {
"month" : 1,
"day" : 21,
"year" : 2014,
"buildProjectName" : "TestABC"
},
"buildDuration" : 2284863.3333333335
},
{
"_id" : {
"month" : 1,
"day" : 17,
"year" : 2014,
"buildProjectName" : "TestABC"
},
"buildDuration" : 2234662
}
],
"ok" : 1
}
The fields you're sorting on are part of the _id so you need to include that in your $sort field names:
db.builds.aggregate([
{ $group: {
_id: {
month: { $month: "$time" },
day: { $dayOfYear: "$time" },
year: { $year: "$time" },
buildProjectName: "$data.buildProjectName",
},
buildDuration: { $avg: "$data.buildDuration" }
} },
{ $sort: {
'_id.buildProjectName': 1,
'_id.year': 1,
'_id.month': 1,
'_id.day': 1
} }
])