I am new to mongoDB and having trouble getting the sum of values. I would like to get the sum of Male grouping by Date.
How can I achieve this? I tried the below query, but did not work. I am having trouble retrieving the value of Male.
db.sales.aggregate(
[
{
$group : {
_id: "$date",
TotalMaleValue: { $sum: "$follower_demographics.gender.value" }
}
}
]
)
My documents look like this:
{
"_id" : ObjectId("566dd67aef3ccf85743c4b10"),
"date" : "somedate",
"follower_demographics" : {
"gender" : [
{
"key" : "Male",
"value" : 480
},
{
"key" : "Female",
"value" : 1705
}
]
}
}
Any help is appreciated.
If you are using MongoDB-3.2 or newer then the best way to do this is to $project your documents and use the $filter operator to return an array with only those sub-documents where "key" is "Male". The next stage in the pipeline will then be the $unwind stage where you denormalize your array. The final stage is the $group stage where you use the $sum accumulator operator to calculate sum of "value".
Of course the $match stage in the beginning of the the pipeline let you select only those documents that match your criteria. This can reduce the size of documents to process in the next stage of the pipeline.
db.sales.aggregate([
{ '$match': {
'follower_demographics.gender': { '$elemMatch': {'key': 'Male' } }
}},
{
'$project': {
'date': 1,
'gender': {
'$filter': {
'input': '$follower_demographics.gender',
'as': 'g',
'cond': { '$eq': [ '$$g.key', 'Male' ] }
}
}
}
},
{ '$unwind': '$gender' },
{ '$group': {
'_id': '$date',
'TotalMaleValue': { '$sum': '$gender.value' }
}}
])
Prior to MongoDB 3.2 you need to use the little known $redact operator after $match to return an array with only those sub-documents where "key" is "Male".
db.sales.aggregate([
{ '$match': {
'follower_demographics.gender': { '$elemMatch': { 'key': 'Male' }}
}},
{ '$redact': {
'$cond': [
{ '$or': [ { '$eq': [ '$key', 'Male' ] }, { '$not': '$key' } ] },
'$$DESCEND',
'$$PRUNE'
]
}},
{ '$unwind': '$follower_demographics.gender' },
{ '$group': {
'_id': '$date',
'TotalMaleValue': { '$sum': '$follower_demographics.gender.value' }
}}
])
Note that, gender is an array. So you need to modify your query a bit to group by an object inside an array.
$unwind, the follower_demographics.gender field. This would now give you a one document each for an object inside the gender array.
$match, only those documents, that you are interested in, i.e those having the gender - value as Male.
Now, $group by the gender- key and get a $sum of the value field.
Sample code:
db.sales.aggregate([
{$match:{"follower_demographics.gender.key":"Male"}},
{$unwind:"$follower_demographics.gender"},
{$match:{"follower_demographics.gender.key":"Male"}},
{$group:{"_id":"$date",
"sum_of_males":{$sum:"$follower_demographics.gender.value"}}}
])
Related
I'm running a Mongo aggregation which "merges" together 2 different documents.
Everything works fine, matching etc.
Right now I'm trying to match on two fields of a sub-document, but it looks like it results in an OR instead of and AND.
Let me give some contest:
Main Document
{
_id: 1234567890a,
title: "mainTitle",
country: "ITA"
}
First sub-Document
{
_id:1234567890b,
mainDocumentId: 1234567890a,
someField: "123",
property: "car"
}
What I mean to do is to aggregate these 2 documents and filter on someField $eq: 123 AND property $eq: car . If there is a sub-document like:
{
_id:1234567890b,
mainDocumentId: 1234567890a,
someField: "123",
property: "bus"
}
The pipeline shouldn't consider it
My aggregation looks like this:
db.mycollection.aggregate([{$lookup: {
from: 'sub_collection',
localField: '_id',
foreignField: 'mainDocumentId',
as: 'SubCollection'
}},
{ $match : { "SubCollection.someField" : '123' , 'SubCollection.property':'car'} },
{ $unwind : "$_id" },
{ $sort : { "createdAt" : -1}},
{ $skip : 0},
{ $limit : 40}
])
The result I get is every document with a subdocument which matches OR the first OR the second condition.
I tried with $elemMatch but this isn't its purpose and in fact it doesn't work.
I also tried to specify the $and in the $match stage
db.mycollection.aggregate([{$lookup: {
from: 'sub_collection',
localField: '_id',
foreignField: 'mainDocumentId',
as: 'SubCollection'
}},
{ $match : {
$and: [
{
"SubCollection": {
$elemMatch: {
"someField": '123'
}
}
},
{
"SubCollection": {
$elemMatch: {
"property": { $eq : 'car' }
}
}
}
]}
},
{ $unwind : "$_id" },
{ $sort : { "createdAt" : -1}},
{ $skip : 0},
{ $limit : 40}
])
I feel like it's a pretty simple task, but clearly I'm missing something: any tip will be appreciated!
Thank you!
- EDIT -
This is my output based on the $lookup I posted above:
{
_id: 1234567890a,
title: "mainTitle",
country: "ITA",
subDocument: [
{
_id:1234567890b,
mainDocumentId: 1234567890a,
someField: "123",
property: "car"
},
{
_id:1234567890b,
mainDocumentId: 1234567890a,
someField: "123",
property: "bus"
}
]
}
$match is not the correct stage for filtering array elements. You need to use $project or $addFields, along $filter operator, to filter out array elements. Like this:
db.collection.aggregate([
{
"$project": {
subDocument: {
"$filter": {
"input": "$subDocument",
"as": "doc",
"cond": {
"$and": [
{
"$eq": [
"$$doc.someField",
"123"
]
},
{
"$eq": [
"$$doc.property",
"car"
]
}
]
}
}
}
}
}
]);
See it in action here. You can add the above $project stage in your pipeline.
Recently I am facing a challenege while creating a query in Mongo Compass. Below is the scenario.
I have a set of documents in mongo db like below:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
},
{
'stream_id: 'ECE',
'stream_name': 'ELECTRONICS'
},
]
},
{
'grade' : ['B'],
'stream': [ {
'stream_id: 'IT',
'stream_name': 'INFORMATION_TECH'
}
]
}
]
}
I need to find the 'PEOPLE' element which has grade as 'A' and stream_name as 'CSE'. So basically I want this output:
{
_id :1,
'people':[
{
'grade' : ['A','B'],
'stream': [ {
'stream_id: 'CSE',
'stream_name': 'COMPUTER'
}
]
]}
I have tried all the $elemMatch features but it's returning the whole document not only that particular index of the array. Please if anyone is aware of mongo compass, let me know.
Mongo is fun it seems :)
You can use aggregations
$unwind to deconstruct the array
$match to get necessary documents, others will be eliminated
$filter to filter the stream, since we get all the documents that passes the condition, we need to filter the stream objects which equal to condition
$group to reconstruct the array that we already deconstructed in first step
here is the code
db.collection.aggregate([
{ $unwind: "$people" },
{
$match: {
$expr: {
$and: [
{ $in: [ "A", "$people.grade" ] },
{ $in: [ "CSE", "$people.stream.stream_id" ] }
]
}
}
},
{
$addFields: {
"people.stream": {
$filter: {
input: "$people.stream",
cond: { $eq: [ "$$this.stream_id", "CSE" ] }
}
}
}
},
{
$group: {
_id: "$_id",
people: { $push: "$people" }
}
}
])
Working Mongo playground
My document structure looks like this:
{
"_id" : ObjectId("5aeeda07f3a664c55e830a08"),
"profileId" : ObjectId("5ad84c8c0e71892058b6a543"),
"list" : [
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
],
}
I want to count array of
list field. And apply condition before slicing that
if the list<=10 then slice all the elements of list
else 10 elements.
P.S I used this query but is returning null.
db.getCollection('post').aggregate([
{
$match:{
profileId:ObjectId("5ada84c8c0e718s9258b6a543")}
},
{$project:{notifs:{$size:"$list"}}},
{$project:{notifications:
{$cond:[
{$gte:["$notifs",10]},
{$slice:["$list",10]},
{$slice:["$list","$notifs"]}
]}
}}
])
Your first $project stage effectively wipes out all result fields but the one(s) that it explicitly projects (only notifs in your case). That's why the second $project stage cannot $slice the list field anymore (it has been removed by the first $project stage).
Also, I think your $cond/$slice combination can be more elegantly expressed using the $min operator. So there's at least the following two fixes for your problem:
Using $addFields:
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $addFields: { notifs: { $size: "$list" } } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ "$notifs", 10 ] } ]
}
}}
])
Using a calculation inside the $project - this avoids a stage so should be preferable.
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ { $size: "$list" }, 10 ] } ]
}
}}
])
I have a collection(named menucategories) in MongoDB 3.2.11:
{
"_id" : ...
"menus" : [
{
"code":0
},
{
"code":1
},
{
"code":2
},
{
"code":3
}
]
},
{
"_id" : ...
"menus" : [
{
"code":4
},
{
"code":5
},
{
"code":6
},
{
"code":7
}
]
},
{
"_id" : ...
"menus" : [
{
"code":8
},
{
"code":9
},
{
"code":10
},
{
"code":11
}
]
}
Every menucategory has array named menus. And every menu(element of the array) has code. The 'code' of menus is unique in every menu. I wanna get the maximum value of menu's code(in this case, 11). How can I achieve this?
If you want to find maximum value of code from all menus code then probable query will be as follows:
db.menucategories.aggregate([
{ $unwind: '$menus' },
{ $group: { _id: null, max: { $max: '$menus.code' } } },
{ $project: { max: 1, _id:0 } }
])
Click below links for more information regarding different operators:
$unwind, $group, $project
You don't need to use the $unwind aggregation pipeline operator here because starting from MongoDB 3.2, some accumulator expressions are available in the $project stage.
db.collection.aggregate([
{"$project": {"maxPerDoc": {"$max": "$menus.code"}}},
{"$group": {"_id": null, "maxValue": {"$max": "$maxPerDoc"}}}
])
Responding a previous now deleted comment, you don't need to put your pipeline in an array so the following query will work as well.
db.collection.aggregate(
{"$project": {"maxPerDoc": {"$max": "$menus.code"}}},
{"$group": {"_id": null, "maxValue": {"$max": "$maxPerDoc"}}}
)
Try with aggregation:
db.collection.aggregate({ $group : { _id: 1, max: { $max: {$max : "$menus.code"}}}});
No need of any unwind, if you need find only maximum value.
Say, I have following documents:
{name: 'A', fav_fruits: ['apple', 'mango', 'orange'], 'type':'test'}
{name: 'B', fav_fruits: ['apple', 'orange'], 'type':'test'}
{name: 'C', fav_fruits: ['cherry'], 'type':'test'}
I am trying to query to find the total count of fav_fruits field on overall documents returned by :
cursor = db.collection.find({'type': 'test'})
I am expecting output like:
cursor.count() = 3 // Getting
Without much idea of aggregate, can mongodb aggregation framework help me achieve this in any way:
1. sum up the lengths of all 'fav_fruits' field: 6
and/or
2. unique 'fav_fruit' field values = ['apple', 'mango', 'orange', 'cherry']
You need to $project your document after the $match stage and use the $size operator which return the number of items in each array. Then in the $group stage you use the $sum accumulator operator to return the total count.
db.collection.aggregate([
{ "$match": { "type": "test" } },
{ "$project": { "count": { "$size": "$fav_fruits" } } },
{ "$group": { "_id": null, "total": { "$sum": "$count" } } }
])
Which returns:
{ "_id" : null, "total" : 6 }
To get unique fav_fruits simply use .distinct()
> db.collection.distinct("fav_fruits", { "type": "test" } )
[ "apple", "mango", "orange", "cherry" ]
Do this to get just the number of fruits in the fav_fruits array:
db.fruits.aggregate([
{ $match: { type: 'test' } },
{ $unwind: "$fav_fruits" },
{ $group: { _id: "$type", count: { $sum: 1 } } }
]);
This will return the total number of fruits.
But if you want to get the array of unique fav_fruits along with the total number of elements in the fav_fruits field of each document, do this:
db.fruits.aggregate([
{ $match: { type: 'test' } },
{ $unwind: "$fav_fruits" },
{ $group: { _id: "$type", count: { $sum: 1 }, fav_fruits: { $addToSet: "$fav_fruits" } } }
])
You can try this. It may helpful to you.
db.collection.aggregate([{ $match : { type: "test" } }, {$group : { _id : null, count:{$sum:1} } }])