MongoDb Aggregation conditional count group by in nested array - mongodb

I am having trouble with the below situation.
I would like to count 'modAttmpt.retrySess.meta.k.gradedActionsFeedback' with value 'Very Good!' and group by 'modAttmpt.modId'.
So far tried below with no luck:
db.myCollection.aggregate()
.unwind("$modAttmpt")
.unwind("$modAttmpt.retrySess")
.project(
{
'modAttmpt.modId' :1,
'modAttmpt.retrySess.dur': 1,
'modAttmpt.retrySess.modPassSt': 1,
'modAttmpt.retrySess.meta' : 1 ,
'totalPass' :
{
$cond: [
{ $eq: ["$modAttmpt.retrySess.modPassSt", 1] },
1, 0
]
},
'passWithGood' :
{
$cond: { if: { $eq: ["$modAttmpt.retrySess.meta.k", 'gradedActionsFeedback'] }, then: 1, else: 0 }
}
}
)
.group
(
{
_id: "$modAttmpt.modId",
'avg': {$avg: '$modAttmpt.retrySess.dur'},
'totalPass' :{ $sum: '$totalPass'},
'passWithGood' : { $sum: '$passWithGood'}
}
)
Sample Data:
{
"_id" : ObjectId("617c10a8e1136c7cf51ffd9d"),
"modAttmpt" : {
"modId" : "mod_home",
"retrySess" : {
"meta" : [
{
"k" : "characterSelected",
"v" : "Male"
},
{
"k" : "gradedActionsFeedback",
"v" : "Very Good!"
}
],
"dur" : 432,
"modPassSt" : 1
}
},
"totalPass" : 1,
"passWithGood" : 0
},
{
"_id" : ObjectId("617c10a8e1136c7cf51ffd94"),
"modAttmpt" : {
"modId" : "mod_home",
"retrySess" : {
"meta" : [
{
"k" : "characterSelected",
"v" : "Male"
},
{
"k" : "gradedActionsFeedback",
"v" : "Very Good!"
}
],
}
},
"totalPass" : 1,
"passWithGood" : 0
},
{
"_id" : ObjectId("617c10a8e1136c7cf51ffd92"),
"modAttmpt" : {
"modId" : "mod_clq",
"retrySess" : {
"meta" : [
{
"k" : "gradedActionsFeedback",
"v" : "Very Good!"
}
],
}
},
"totalPass" : 1,
"passWithGood" : 0
},
{
"_id" : ObjectId("617c10a8e1136c7cf51ffd91"),
"modAttmpt" : {
"modId" : "mod_home",
"retrySess" : {
"meta" : [
{
"k" : "gradedActionsFeedback",
"v" : "Good!"
}
],
}
},
"totalPass" : 1,
"passWithGood" : 0
},
{
"_id" : ObjectId("617c10a8e1136c7cf51ffd95"),
"modAttmpt" : {
"modId" : "mod_clq",
"retrySess" : {
"meta" : [
{
"k" : "gradedActionsFeedback",
"v" : "Very Good!"
}
],
}
},
"totalPass" : 1,
"passWithGood" : 0
}

You can make use of $reduce operator to calculate modAttmpt.retrySess.meta.k.gradedActionsFeedback with value Very Good!
Replace your $project stage with the one below:
db.collection.aggregate([
{
"$project": {
"modAttmpt.modId": 1,
"modAttmpt.retrySess.dur": 1,
"modAttmpt.retrySess.modPassSt": 1,
"modAttmpt.retrySess.meta": 1,
"totalPass": {
$cond: [
{
$eq: [
"$modAttmpt.retrySess.modPassSt",
1
]
},
1,
0
]
},
"passWithGood": {
$cond: {
if: {
$eq: [
"$modAttmpt.retrySess.meta.k",
"gradedActionsFeedback"
]
},
then: 1,
else: 0
}
},
"passWithGood2": {
"$reduce": {
"input": "$modAttmpt.retrySess.meta",
"initialValue": 0,
"in": {
"$add": [
{
"$cond": {
"if": {
"$eq": [
"$$this.v",
"Very Good!"
]
},
"then": 1,
"else": 0,
},
},
"$$value",
]
}
}
}
}
},
{
"$group": {
_id: "$modAttmpt.modId",
"avg": {
$avg: "$modAttmpt.retrySess.dur"
},
"totalPass": {
$sum: "$totalPass"
},
"passWithGood": {
$sum: "$passWithGood2"
}
},
},
])
Mongo Playground Sample Execution

Related

MongoDB - Group by and count value, but treat per record as one

I want to group by and count follow_user.tags.tag_id per record, so no matter how many times the same tag_id show up on the same record, it only counts as 1.
My database structure looks like this:
{
"external_userid" : "EXID1",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG1"
}
]
},
{
"userid" : "USERID2",
"tags" : [
{
"tag_id" : "TAG1"
},
{
"tag_id" : "TAG2"
}
]
}
]
},
{
"external_userid" : "EXID2",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG2"
}
]
}
]
}
Here's my query:
[
{ "$unwind": "$follow_user" }, { "$unwind": "$follow_user.tags" },
{ "$group" : { "_id" : { "follow_user᎐tags᎐tag_id" : "$follow_user.tags.tag_id" }, "COUNT(_id)" : { "$sum" : 1 } } },
{ "$project" : { "total" : "$COUNT(_id)", "tagId" : "$_id.follow_user᎐tags᎐tag_id", "_id" : 0 } }
]
What I expected:
{
"total" : 1,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
What I get:
{
"total" : 2,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
$set - Create a new field follow_user_tags.
1.1. $setUnion - To distinct the value from the Result 1.1.1.
1.1.1. $reduce - Add the value of follow_user.tags.tag_id into array.
$unwind - Deconstruct follow_user_tags array field to multiple documents.
$group - Group by follow_user_tags and perform total count via $sum.
$project - Decorate output document.
db.collection.aggregate([
{
$set: {
follow_user_tags: {
$setUnion: {
"$reduce": {
"input": "$follow_user.tags",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this.tag_id"
]
}
}
}
}
}
},
{
$unwind: "$follow_user_tags"
},
{
$group: {
_id: "$follow_user_tags",
total: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
tagId: "$_id",
total: 1
}
}
])
Sample Mongo Playground

Get all possible combinations from array in MongoDB aggregation 🚀

How to do aggregation ($group) by the same values ​​from the array? Not all at once, but few or all, if any. I can do $group by one word, but I also need all possible variations...
Collection example:
{"keywords": ["gta", "distribution", "keys"]}
{"keywords": ["gta", "online", "moto", "races"]}
{"keywords": ["gta", "online", "samp"]}
Result example:
"gta" - 3 matches
"online" - 2 matches
"gta online" - 2 matches
You could use $reduce to extract all combinations of pairs from an array. I've started from this post and I've added the current item, $unwind the
initial array and count the items :
db.test.aggregate([
{
$project: {
pairs: {
$reduce: {
input: { $range: [0, { $size: "$keywords" }] },
initialValue: [],
in: {
$concatArrays: [
"$$value",
[[{ $arrayElemAt: ["$keywords", "$$this"] }]],
{
$let: {
vars: { i: "$$this" },
in: {
$map: {
input: { $range: [{ $add: [1, "$$i"] }, { $size: "$keywords" }] },
in: [{ $arrayElemAt: ["$keywords", "$$i"] }, { $arrayElemAt: ["$keywords", "$$this"] }]
}
}
}
}
]
}
}
}
}
}, {
$unwind: "$pairs"
}, {
$group: {
_id: "$pairs",
count: { $sum: 1 }
}
}
])
Output :
{ "_id" : [ "online", "samp" ], "count" : 1 }
{ "_id" : [ "gta", "samp" ], "count" : 1 }
{ "_id" : [ "online", "races" ], "count" : 1 }
{ "_id" : [ "moto", "races" ], "count" : 1 }
{ "_id" : [ "gta", "keys" ], "count" : 1 }
{ "_id" : [ "races" ], "count" : 1 }
{ "_id" : [ "gta", "distribution" ], "count" : 1 }
{ "_id" : [ "samp" ], "count" : 1 }
{ "_id" : [ "distribution", "keys" ], "count" : 1 }
{ "_id" : [ "gta" ], "count" : 3 }
{ "_id" : [ "online" ], "count" : 2 }
{ "_id" : [ "keys" ], "count" : 1 }
{ "_id" : [ "gta", "online" ], "count" : 2 }
{ "_id" : [ "moto" ], "count" : 1 }
{ "_id" : [ "online", "moto" ], "count" : 1 }
{ "_id" : [ "distribution" ], "count" : 1 }
{ "_id" : [ "gta", "moto" ], "count" : 1 }
{ "_id" : [ "gta", "races" ], "count" : 1 }
If you need more combinations, you may need to update the $reduce stage above

Conditionally Count Value from Array into New Field

Consider the following data:
{
"_id" : ObjectId("592ffb3d257acc76fc0eecd7"),
"primaryProcessName" : "BI",
"dateTimeStamp" : ISODate("2017-06-01T11:32:12.834+0000"),
"tag" : [
{
"key" : "processname",
"value" : "NEUpdateService",
"value_original" : "NEUpdateService"
},
{
"key" : "processstageid",
"value" : "inprocess",
"value_original" : "InProcess"
},
]
}
{
"_id" : ObjectId("592ffb3d257acc76fc0eecdd"),
"primaryProcessName" : "BI",
"dateTimeStamp" : ISODate("2017-06-01T11:32:13.345+0000"),
"tag" : [
{
"key" : "processname",
"value" : "CommissionPaymentSend",
"value_original" : "CommissionPaymentSend"
},
{
"key" : "processstageid",
"value" : "faulted",
"value_original" : "Faulted"
},
]
}
{
"_id" : ObjectId("592ffb3d257acc76fc0eece4"),
"primaryProcessName" : "BI",
"dateTimeStamp" : ISODate("2017-06-01T11:32:13.745+0000"),
"tag" : [
{
"key" : "processname",
"value" : "commonbusinessintegratorservice",
"value_original" : "CommonBusinessIntegratorService"
},
{
"key" : "processstageid",
"value" : "inprocess",
"value_original" : "InProcess"
},
]
}
{
"_id" : ObjectId("592ffb3d257acc76fc0eecea"),
"primaryProcessName" : "BI",
"dateTimeStamp" : ISODate("2017-06-01T11:32:13.876+0000"),
"tag" : [
{
"key" : "processname",
"value" : "commonbusinessintegratorservice",
"value_original" : "CommonBusinessIntegratorService"
},
{
"key" : "processstageid",
"value" : "inprocess",
"value_original" : "InProcess"
},
]
}
{
"_id" : ObjectId("592ffb3e257acc76fc0eecf1"),
"primaryProcessName" : "BI",
"dateTimeStamp" : ISODate("2017-06-01T11:32:14.193+0000"),
"tag" : [
{
"key" : "processname",
"value" : "SmartComplianceMessenger",
"value_original" : "SmartComplianceMessenger"
},
{
"key" : "processstageid",
"value" : "complete",
"value_original" : "Complete"
},
]
}
I am trying to write a query to aggregate this data to show in the following format:
{
"Total" : 1982, "InProcess" : 991, "Complete" : 991, "Faulted" : 0,
"name" : "SmartComplianceMessenger",
"displayName" : "SmartComplianceMessenger",
"drillDownUrl" : "process/forprimary/name/SmartComplianceMessenger"
},
{
"Total" : 122333, "InProcess" : 56375, "Complete" : 54856, "Faulted" : 11102,
"name" : "NEUpdateService",
"displayName" : "NEUpdateService",
"drillDownUrl" : "process/forprimary/name/NEUpdateService"
},
....
This is what I have so far:
db.ActivityNotice.aggregate([
{$match: {
dateTimeStamp: {
$gte: ISODate("2017-06-01T11:00:00.000Z")
, $lt: ISODate("2017-06-01T11:45:00.000Z")
}
}},
{$group :
{
_id: {process: "$primaryProcessName"} //, status:"$processStageId"
, Total:{$sum:1}
, InProcess: {$sum:0}// { $sum: {$cond: [{$eq: ["$processStageId","InProcess"]},1,0]}}
, Complete: {$sum:0} // { $sum: {$cond: [{$eq: ["$processStageId","Complete"]},1,0]}}
, Faulted: {$sum:0} // { $sum: {$cond: [{$eq: ["$processStageId","Faulted"]},1,0]}}
, Test: { $sum: {$cond: [{$eq: ["tag.key","processstageid"]},1,0]}}
}},
{$project: {
_id: 0,
name: "$_id.process", displayName: "$_id.process",
drillDownUrl: { $concat: [ "process/forprimary/name/", "$_id.process" ] },
Total: 1, InProcess: 1 , Complete: 1, Faulted: 1, Test: 1
}}
])
The challenge I am facing is selecting the value for the "processname" key from tags into a new field, called processName and the value for "processtageid" into a new field so I can do the sum on those values.
Any help would be greatly appreciated.
You want $filter and $size for the most efficient way:
{ "$group": {
"_id": "$primaryProcessName",
"Total": { "$sum": 1 },
"InProcess": {
"$sum": {
"$size": {
"$filter": {
"input": "$tag",
"as": "t",
"cond": {
"$and": [
{ "$eq": [ "$$t.key", "processstageid" ] },
{ "$eq": [ "$$t.value","inprocess"] }
]
}
}
}
}
},
"Complete": {
"$sum": {
"$size": {
"$filter": {
"input": "$tag",
"as": "t",
"cond": {
"$and": [
{ "$eq": [ "$$t.key", "processstageid" ] },
{ "$eq": [ "$$t.value","complete"] }
]
}
}
}
}
},
"Faulted": {
"$sum": {
"$size": {
"$filter": {
"input": "$tag",
"as": "t",
"cond": {
"$and": [
{ "$eq": [ "$$t.key", "processstageid" ] },
{ "$eq": [ "$$t.value","faulted"] }
]
}
}
}
}
}
}}
$filter has it's own condition for which we can use $and to match the multiple conditions of different properties of the array element. This reduces the array to only the entries that match, where you can then take the $size

multiple group in mongodb

My collection look likes this.
{
"_id" : ObjectId("572c4ed33c1b5f51215219a8"),
"name" : "This is an angular course, and integeration with php",
"description" : "After we connected we can query or update the database just how we would using the mongo API with the exception that we use a callback. The format for callbacks is always callback(error, value) where error is null if no exception has occured. The update methods save, remove, update and findAndModify also pass the lastErrorObject as the last argument to the callback function.",
"difficulty_level" : "Beginner",
"type" : "Fast Track",
"tagged_skills" : [
{
"_id" : "5714e894e09a0f7d804b2254",
"name" : "PHP"
},
{
"_id" : "5717355806313b1f1715fa50",
"name" : "c++"
},
{
"_id" : "5715025bc2c5dbb4675180da",
"name" : "java"
},
{
"_id" : "5714f188ec325f5359979e33",
"name" : "symphony"
}
]}
I want to group by the collection on the basis of type,difficulty level and tagged skills and also get the count in a single query.
I am not been able to add skills count.
My query is as follows:-
db.course.aggregate([
{$unwind:"$tagged_skills"},
{$group:{
_id:null,
skills: { $addToSet: "$tagged_skills.name" },
Normal_df:{$sum:{
"$cond": [
{ "$eq":[ "$difficulty_level","Normal"] },
1,
0
]
}},
Beginner_df:{$sum:{
"$cond": [
{ "$eq":[ "$difficulty_level","Beginner"] },
1,
0
]
}},
Intermediate_df:{$sum:{
"$cond": [
{ "$eq":[ "$difficulty_level","Intermediate"] },
1,
0
]
}},
Advanced_df:{$sum:{
"$cond": [
{ "$eq":[ "$difficulty_level","Advanced"] },
1,
0
]
}},
Fast_Track_type:{$sum:{
"$cond": [
{ "$eq":[ "$type","Fast Track"] },
1,
0
]
}},
Normal_type:{$sum:{
"$cond": [
{ "$eq":[ "$type","Normal"] },
1,
0
]
}},
Beginner_type:{$sum:{
"$cond": [
{ "$eq":[ "$type","Beginner"] },
1,
0
]
}},
Normal_Track_type:{$sum:{
"$cond": [
{ "$eq":[ "$type","Normal Track"] },
1,
0
]
}},
}}
])
The result is as follows:-
{
"_id" : null,
"skills" : [
"SQL",
"PHP",
"java",
"Angular Js",
"Laravel 23",
"c++",
"Node Js",
"symphony",
"Mysql",
"Express Js",
"JAVA"
],
"Normal_df" : 1,
"Beginner_df" : 14,
"Intermediate_df" : 7,
"Advanced_df" : 2,
"Fast_Track_type" : 8,
"Normal_type" : 6,
"Beginner_type" : 1,
"Normal_Track_type" : 9
}
I also want to get all skills with their count.
To get all the skills with their count, you need to first get a list of all the skills. You can obtain this list with running a distinct command on the approapriate fields. With this list you can then construct the appropriate $group pipeline document that will use the $sum and $cond operators.
Consider the following use case:
var difficultyLevels = db.course.distinct("difficulty_level"),
types = db.course.distinct("type"),
skills = db.course.distinct("tagged_skills.name"),
unwindOperator = { "$unwind": "$tagged_skills" },
groupOperator = {
"$group": {
"_id": null,
"skills": { "$addToSet": "$tagged_skills.name" }
}
};
difficultyLevels.forEach(function (df){
groupOperator["$group"][df+"_df"] = {
"$sum": {
"$cond": [ { "$eq": ["$difficulty_level", df] }, 1, 0]
}
}
});
types.forEach(function (type){
groupOperator["$group"][type.replace(" ", "_")+"_type"] = {
"$sum": {
"$cond": [ { "$eq": ["$type", type] }, 1, 0]
}
}
});
skills.forEach(function (skill){
groupOperator["$group"][skill] = {
"$sum": {
"$cond": [ { "$eq": ["$tagged_skills.name", skill] }, 1, 0]
}
}
});
//printjson(groupOperator);
db.course.aggregate([unwindOperator, groupOperator]);
In the first line, we obtain an array with the difficulty levels by running the distinct command on the difficulty_level field
db.course.distinct("difficulty_level")
This will produce the array
var difficultyLevels = ["Normal", "Beginner", "Intermediate", "Advanced"]
Likewise, the preceding distinct operations will return the list of possible unique values for that key.
After getting these lists, you can then create the pipeline objects using the forEach() method to populate the document keys for each given item in the list. You can then use the resulting document, which will look like this
printjson(groupOperator);
{
"$group" : {
"_id" : null,
"skills" : {
"$addToSet" : "$tagged_skills.name"
},
"Beginner_df" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$difficulty_level",
"Beginner"
]
},
1,
0
]
}
},
"Intermediate_df" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$difficulty_level",
"Intermediate"
]
},
1,
0
]
}
},
"Fast_Track_type" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$type",
"Fast Track"
]
},
1,
0
]
}
},
"PHP" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"PHP"
]
},
1,
0
]
}
},
"c++" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"c++"
]
},
1,
0
]
}
},
"java" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"java"
]
},
1,
0
]
}
},
"symphony" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"symphony"
]
},
1,
0
]
}
},
"C#" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"C#"
]
},
1,
0
]
}
},
"Scala" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"Scala"
]
},
1,
0
]
}
},
"javascript" : {
"$sum" : {
"$cond" : [
{
"$eq" : [
"$tagged_skills.name",
"javascript"
]
},
1,
0
]
}
}
}
}

MongoDB aggregate group by sum of distinct column

I have analytics collection with the below sample data.
{ "_id" : ObjectId("55f996a4e4b0cc9c0a392594"), "action" : "apiUploadFile", "assetId" : "55f996a4e4b0cc9c0a392593" },
{ "_id" : ObjectId("5603d384e4b0cf75af10be88"), "action" : "agAsset", "assetId" : "55f996a4e4b0cc9c0a392593"},
{ "_id" : ObjectId("5603d395e4b0cf75af10becc"), "action" : "aAD", "assetId" : "55f996a4e4b0cc9c0a392593" },
{ "_id" : ObjectId("5603d395e4b0cf75af10becd"), "action" : "mobCmd", "assetId" : "55f996a4e4b0cc9c0a392593", sessionId : "123"},
{ "_id" : ObjectId("5603d395e4b0cf75af10bece"), "action" : "mobCmd", "assetId" : "55f996a4e4b0cc9c0a392593", sessionId : "1234" },
{ "_id" : ObjectId("5603d395e4b0cf75af10becf"), "action" : "mobCmd", "assetId" : "55f996a4e4b0cc9c0a392593", sessionId : "1234" }
I need find sum of analytics group by 'assetId' and then for each 'action' type. I have come up with the below query
db.analytics.aggregate(
[
{
$match : {
'assetId' : { "$ne": null }
}
},
{$group :{
_id:
{
assId:'$assetId'
},
viewCount:{
$sum:{
$cond: [ { $eq: [ '$action', 'agAsset' ] }, 1, 0 ]
}
},
sessionCount:{
$sum:{
$cond: [ { $eq: [ '$action', 'mobCmd' ] }, 1, 0 ]
}
}
}
}]
)
This works great except for the fact that I can not find the 'sessionCount' using distinct 'sessionId'. For example here is the current output
{ "_id" : { "assId" : "55f996a4e4b0cc9c0a392593" }, "viewCount" : 1, "sessionCount" : 3 }
The expected output is
{ "_id" : { "assId" : "55f996a4e4b0cc9c0a392593" }, "viewCount" : 1, "sessionCount" : 2 }
I need find the sessionCount for action='mobCmd' and has distinct values for sessionId. How can use distinct inside $sum operation of the 'sessionCount' section?
You will need to group your documents on a compound _id field.
db.collection.aggregate([
{ "$match": { "assetId": { "$ne": null }}},
{ "$group": {
"_id": { "assId": "$assetId", "sessionId": "$sessionId" },
"viewCount": {
"$sum": {
"$cond": [
{ "$eq": [ "$action", "agAsset" ] },
1,
0
]
}
},
"sessionCount": {
"$sum": {
"$cond": [
{ "$eq": [ "$action", "mobCmd" ] },
1,
0
]
}
}
}}
])
Which yields:
{ "_id" : { "assId" : "55f996a4e4b0cc9c0a392593", "sessionId" : "1234" }, "viewCount" : 0, "sessionCount" : 2 }
{ "_id" : { "assId" : "55f996a4e4b0cc9c0a392593", "sessionId" : "123" }, "viewCount" : 0, "sessionCount" : 1 }
{ "_id" : { "assId" : "55f996a4e4b0cc9c0a392593" }, "viewCount" : 1, "sessionCount" : 0 }
Or use the $addToSet operator to return an array of unique sessionId and $unwind the array then regroup your documents.
db.collection.aggregate([
{ "$match": { "assetId": { "$ne": null }}},
{ "$group": {
"_id": "$assetId",
"sessionId": { "$addToSet": "$sessionId" },
"viewCount": {
"$sum": {
"$cond": [
{ "$eq": [ "$action", "agAsset" ] },
1,
0
]
}
}
}},
{ "$unwind": "$sessionId" },
{ "$group": {
"_id": "$_id",
"viewCount": { "$first": "$viewCount" },
"sessionCount": { "$sum": 1 }
}}
])
Which returns:
{ "_id" : "55f996a4e4b0cc9c0a392593", "viewCount" : 1, "sessionCount" : 2 }