$push with $group in mongo aggregation - mongodb

I wrote this query but its not exactly i want how to use push operator for expected result-
Is it not possible to use push with addFields and project pipeline.
db.getCollection("event").aggregate([ {$match:{"name":"Add
to Cart"}}, {$group:{"_id":"browser",count:{$sum:1}}}]);
output:
{_id:chrome.count:3}
{_id:firefox,count:1}
{_id:edge,count:1}
expect output:
{
browser:[
{name:"chrome",count:3},
{name:"firefox",count:1},
{name:"egde",count:1}
]
}
my collection:
{
_id:1,
name:"Add to Cart"
"browser":"chrome"
}
{
_id:2,
name:"Searched",
"browser":"chrome"
}
{
_id:3,
name:"Add To Cart",
"browser":"edge"
}
{
_id:4,
name:"Item View",
"browser":"chrome"
}
{
_id:5,
name:"Add To Cart",
"browser":"Firefox"
}

You need to use one more $group stage here
db.collection.aggregate([
{ "$group": {
"_id": "$browser",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"browser": {
"$push": {
"name": "$_id",
"count": "$count"
}
}
}},
{ "$project": { "_id": 0 }}
])

Related

The field "$name" must be an accumulator object

I have a query and when I use a $group a error shows "the field "$name must be an accumulator object", if if remove the filed "$name" all works well and i have tried to use only "name" instead of "$name" and the error continues.
User.aggregate([
{
$match: {
"storeKey": req.body.store
}
},
{
$group: {
"_id": "$_id",
"name": "$name",
"count": {
"$sum": 1
},
"totalValue": {
"$sum": "$value"
}
}
},
{
$sort: sort
},
{
$skip: req.body.limit * req.body.page
},
{
$limit: req.body.limit
}
])...
There are some aggregation operators that can only be used in $group aggregation and named as $group accumulators
Just as you used $sum here you have to use for the name key as well
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" }, //$first accumulator
"count": { "$sum": 1 }, //$sum accumulator
"totalValue": { "$sum": "$value" } //$sum accumulator
}}
Accumulator is like array of Elements its Accumulates as Array.
$first -> gives 1st name that goes in the group of names
Example:
so if you have $_id same but different name ["Darik","John"]
specifying $first will give Darik & similarly $last will give John
$group: {
_id:"$_id",
"name" :{ $last: '$name' },
"count" : { $sum: 1 },
"totalValue": { "$sum": "$value" }
}
db.faq_feedback.aggregate({
$lookup:{
"from":"faq",
"localField":"question_id",
"foreignField":"_id",
"as":"faq"
}
},
{
$unwind:"$faq"
},
{
$project:{
"question_id":1,
"lang":"$faq.lang",
"feedback":"$faq.feedback",
"question":"$faq.question",
"yes":{
"$cond":[
{
"$eq":[
"$feedback",
"yes"
]
},
1,
0
]
},
"no":{
"$cond":[
{
"$eq":[
"$feedback",
"no"
]
},
1,
0
]
}
}
},
{
$group:{
"_id":"$question_id",
"yes":{
"$sum":"$yes"
},
"no":{
"$sum":"$no"
},
"question":{"$first":"$question"},
"lang":{"$first":"$lang"}
}
},
{
$limit:10000
},
{
$skip:0
})

find list of users based on multiple condition using aggregation

I want to know how to use aggregation to perform this.
find userId list who perform add to cart and item view event more then 2 times
Collection name event.
{
_id:1,
name:"Add To Cart",
userId:1
}
{
_id:2,
name:"Searched",
userId:2
}
{
_id:3,
name:"Add To Cart",
userId:1
}
{
_id:4,
name:"Item View",
userId:1
}
{
_id:5,
name:"Add To Cart",
userId:2
}
{
_id:6,
name:"Item View",
userId:1
}
my query-
db.getCollection("event").aggregate([ {$match:{$or:[{"name":"Add to
Cart"},{"name":"Item Viewed"}]}},
{$group:{_id: {userId:"$userId",name:"$name"},count:{$sum:1}}},
{$match:{count:{$gte:2}}},
{$group: {_id:"$_id.userId",event:{$push:"$_id.name"}}} ,
{$project:{size:{$size:"$event"}}},
{$match:{size:{$gte:2}}}
]).pretty();
output expected -from above collection
{userId:1}
I want only those userId who perform Add to cart and Item view event(both) more then 2 times.
Here is Full Query ,
db.Test.aggregate(
// Pipeline
[
{
$group: {
"_id": {
"userId": "$userId",
"name": "$name"
},
"count": {
"$sum": 1
}
}
},
{
$match: {
"count": {
"$gte": 2
}
}
},
{
$group: {
"_id": "$_id.userId",
"event": {
"$push": "$_id.name"
}
}
},
{
$project: {
"size": {
"$size": "$event"
}
}
},
{
$match: {
"size": {
"$gte": 2
}
}
},
]
);
You will get your userId in _id field.

Mongo db first groupby count not able to display

[
{ "$match": {
"created":{
"$gte": ISODate("2015-07-19T07:26:49.045Z")
},
"created":{
"$lte": ISODate("2015-07-20T07:37:56.045Z")
}
}},
{ "$group:{
"_id":{
"ln":"$l.ln",
"cid":"$cid"
},
"appCount":{ "$sum": 1 }
}},
{ "$group": {
"_id": { "ln":"$_id.ln" },
"cusappCount": { "$sum": 1 }
}},
{ "$sort":{ "_id.ln":1 } }
]
In above mongo db query I am not able to display the appcount in result.. I am able to display cusappCount. Could anyone please help me on this
The $match is wrong to start with and does not do what you think. It is only selecting the "second" statement:
"created":{
"$lte": ISODate("2015-07-20T07:37:56.045Z")
}
So your selections are incorrect to start with.
That and other corrections below:
[
{ "$match": {
"created": {
"$gte": ISODate("2015-07-19T07:26:49.045Z"),
"$lte": ISODate("2015-07-20T07:37:56.045Z")
}
}},
{ "$group":{
"_id": {
"ln":"$l.ln",
"cid":"$cid"
},
"appCount":{ "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.ln",
"cusappCount": { "$sum": "$appCount" },
"distinctCustCount": { "$sum": 1 }
}},
{ "$sort":{ "_id": 1 } }
]
Which seems to be what you are trying to do.
So your earier "count" is then passed to $sum when grouping at a "broader" level. The "second" count is just for the "distinct" items in the earlier key.
If you are trying to "retain" the values of "appCount", then the problem here is that your "grouping" is "taking away" the detail level that appears at. So for what it is woth, then this is where you use "arrays" in an output structure:
[
{ "$match": {
"created": {
"$gte": ISODate("2015-07-19T07:26:49.045Z"),
"$lte": ISODate("2015-07-20T07:37:56.045Z")
}
}},
{ "$group":{
"_id": {
"ln":"$l.ln",
"cid":"$cid"
},
"appCount":{ "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.ln",
"cusappCount": { "$sum": 1 },
"custs": { "$push": {
"cid": "$_id.cid", "appCount": "$appCount"
}}
}},
{ "$sort":{ "_id": 1 } }
]

Perform union in mongoDB

I'm wondering how to perform a kind of union in an aggregate in MongoDB. Let's imaging the following document in a collection (the structure is for the sake of the example) :
{
linkedIn: {
people : [
{
name : 'Fred'
},
{
name : 'Matilda'
}
]
},
twitter: {
people : [
{
name : 'Hanna'
},
{
name : 'Walter'
}
]
}
}
How to make an aggregate that returns the union of the people in twitter and linkedIn ?
{
{ name :'Fred', source : 'LinkedIn'},
{ name :'Matilda', source : 'LinkedIn'},
{ name :'Hanna', source : 'Twitter'},
{ name :'Walter', source : 'Twitter'},
}
There are a couple of approaches to this that you can use the aggregate method for
db.collection.aggregate([
// Assign an array of constants to each document
{ "$project": {
"linkedIn": 1,
"twitter": 1,
"source": { "$cond": [1, ["linkedIn", "twitter"],0 ] }
}},
// Unwind the array
{ "$unwind": "$source" },
// Conditionally push the fields based on the matching constant
{ "$group": {
"_id": "$_id",
"data": { "$push": {
"$cond": [
{ "$eq": [ "$source", "linkedIn" ] },
{ "source": "$source", "people": "$linkedIn.people" },
{ "source": "$source", "people": "$twitter.people" }
]
}}
}},
// Unwind that array
{ "$unwind": "$data" },
// Unwind the underlying people array
{ "$unwind": "$data.people" },
// Project the required fields
{ "$project": {
"_id": 0,
"name": "$data.people.name",
"source": "$data.source"
}}
])
Or with a different approach using some operators from MongoDB 2.6:
db.people.aggregate([
// Unwind the "linkedIn" people
{ "$unwind": "$linkedIn.people" },
// Tag their source and re-group the array
{ "$group": {
"_id": "$_id",
"linkedIn": { "$push": {
"name": "$linkedIn.people.name",
"source": { "$literal": "linkedIn" }
}},
"twitter": { "$first": "$twitter" }
}},
// Unwind the "twitter" people
{ "$unwind": "$twitter.people" },
// Tag their source and re-group the array
{ "$group": {
"_id": "$_id",
"linkedIn": { "$first": "$linkedIn" },
"twitter": { "$push": {
"name": "$twitter.people.name",
"source": { "$literal": "twitter" }
}}
}},
// Merge the sets with "$setUnion"
{ "$project": {
"data": { "$setUnion": [ "$twitter", "$linkedIn" ] }
}},
// Unwind the union array
{ "$unwind": "$data" },
// Project the fields
{ "$project": {
"_id": 0,
"name": "$data.name",
"source": "$data.source"
}}
])
And of course if you simply did not care what the source was:
db.collection.aggregate([
// Union the two arrays
{ "$project": {
"data": { "$setUnion": [
"$linkedIn.people",
"$twitter.people"
]}
}},
// Unwind the union array
{ "$unwind": "$data" },
// Project the fields
{ "$project": {
"_id": 0,
"name": "$data.name",
}}
])
Not sure if using aggregate is recommended over a map-reduce for that kind of operation but the following is doing what you're asking for (dunno if $const can be used with no issue at all in the .aggregate() function) :
aggregate([
{ $project: { linkedIn: '$linkedIn', twitter: '$twitter', idx: { $const: [0,1] }}},
{ $unwind: '$idx' },
{ $group: { _id : '$_id', data: { $push: { $cond:[ {$eq:['$idx', 0]}, { source: {$const: 'LinkedIn'}, people: '$linkedIn.people' } , { source: {$const: 'Twitter'}, people: '$twitter.people' } ] }}}},
{ $unwind: '$data'},
{ $unwind: '$data.people'},
{ $project: { _id: 0, name: '$data.people.name', source: '$data.source' }}
])

Return whole document from aggregation

I'm using the following query to fetch one most recent comment for every post in database:
db.comments.aggregate([
{
"$match": {
"post_id": {
"$in": [ObjectId("52c5ce24dca32d32740c1435"), ObjectId("52c5ce24dca32d32740c15ad")]
}
}
},
{
"$sort": {"_id": -1}
},
{
"$group": {
"_id": "$post_id",
"lastComment": {
"$first": "$_id"
}
}
}
])
I expect it to return the whole comment's document but it only returns the _id field of each document. So what would be the proper way to get all most recent comments as a whole document (or at least include some other fields)?
Currently you cannot get the whole comment document via single $first operator. But you can include other necessary fields (similar to _id field) during $group step:
{
"$group": {
_id: "$post_id",
lastComment: { "$first": "$_id" },
field_1: { "$first": "$field_1" },
field_2: { "$first": "$field_2" },
// ...
field_N: { "$first": "$field_N" }
}
}
According to this JIRA ticket: https://jira.mongodb.org/browse/SERVER-5916, the whole document will be available to return from aggregation operations from 2.5.3 version. It will be possible using new variables: $$ROOT or $$CURRENT:
{
"$group": {
_id: "$post_id",
lastComment: { "$first": "$$CURRENT" }
}
}
As suggested, we can do :
{
"$group": {
_id: "$post_id",
lastComment: { "$first": "$$CURRENT" }
}
}
and then do use { '$replaceRoot': { 'newRoot': '$lastComment' } } on any mongodb server 3.4 or above to unwrap object from {lastComment:{actualEntireObj}},{lastComment:{actualEntireObj}} to {},{} this way it will get embedded $$ROOT document to the top level and replaces all other fields like _id returning from $group stage of aggregation.
db.collection.aggregate([
{
"$match": {
"post_id": {
"$in": [ObjectId("52c5ce24dca32d32740c1435"), ObjectId("52c5ce24dca32d32740c15ad")]
}
}
},
{
"$sort": { "_id": -1 }
},
{
"$group": {
_id: "$post_id",
lastComment: { "$first": "$$CURRENT" }
}
},
{ '$replaceRoot': { 'newRoot': '$lastComment' } }
])