MongoDB Aggregation - $sum nested groups - mongodb

This is my Mongo event data in the events collection:
[
{ country: "USA", Region: "CA", City: "Los Angeles" },
{ country: "USA", Region: "NY", City: "New York" },
{ country: "USA", Region: "NY", City: "White Plains" },
{ country: "France", Region: "Ile-de-France", City: "Paris" },
{ country: "France", Region: "Bretagne", City: "Brest" },
{ country: "Germany", Region: "", City: "Berlin" }
]
If possible I would like to show the event summed by country and region. The desired output format would be:
[
{
country: "USA",
count: 3,
children: [
{ Region: "NY", count: 2 },
{ Region: "CA", count: 1 }
]
},
{
country: "France",
count: 2,
children: [
{ Region: "Ile-de-France", count: 1 },
{ Region: "Bretagne", count: 1 }
]
},
{
country: "Germany",
count: 1,
children: [ // Region undefined ]
}
]
Here is what I tried:
events.aggregate([
{
$group: {
_id: '$country', count: {$sum: 1}
}
},
{
$group: {
_id: '$_id.country',
children: {
$push: {
Region: '$_id.Region',
count: {$sum: 1}
}
}
}
}
]
The first stage groups and counts the country works on its own. The issue is summing the Region parameter. I even removed the $sum function from the first stage. All I am getting is:
[
{
_id: null,
children : [
{ count: 1 },
{ count: 1 },
{ count: 1 },
{ count: 1 },
...
]
}
]
Any advice would be appreciated!

$group - Group by country and Region.
$group - Group by country.
db.collection.aggregate([
{
$group: {
_id: {
country: "$country",
Region: "$Region"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.country",
children: {
$push: {
Region: "$_id.Region",
count: "$count"
}
}
}
}
])
Sample Mongo Playground

Related

Projection and group on nested object mongodb aggregation query

How to get the nested object in projection and group in mongodb aggregate query.
[
{
city: "Mumbai",
meta: {
luggage: 2,
scanLuggage: 1,
upiLuggage: 1
},
cash: 10
},
{
city: "Mumbai",
meta: {
luggage: 4,
scanLuggage: 3,
upiLuggage: 1
},
cash: 24
},
]
I want to $match the above on the basis of city, and return the sum of each luggage type.
My code is as follows but $project is not working -
City.aggregate([
{
$match: { city: 'Mumbai' }
},
{
$project: {
city: 1,
mata.luggage: 1,
meta.scanLuggage: 1,
meta.upiLuggage: 1
}
},
{
$group: {
id: city,
luggage: {$sum: '$meta.luggage'},
scanLuggage: {$sum: '$meta.scanLuggage'},
upiLuggage: {$sum: '$meta.upiLuggage'}
}
}
])
But the $project is throwing error. I want my output to look like -
{
city: 'Mumbai',
luggage: 6,
scanLuggage: 4,
upiLuggage: 2
}
You should specify nested fields in quotes when using in $project, and also for grouping key should be _id.
db.collection.aggregate([
{
$match: {
city: "Mumbai"
}
},
{
$project: {
city: 1,
"meta.luggage": 1,
"meta.scanLuggage": 1,
"meta.upiLuggage": 1
}
},
{
$group: {
_id: "$city",
luggage: {
$sum: "$meta.luggage"
},
scanLuggage: {
$sum: "$meta.scanLuggage"
},
upiLuggage: {
$sum: "$meta.upiLuggage"
}
}
}
])
This is the playground link.

How to $count and $group within MongoDB aggregation?

I would like to count the status and group them by country.
Data:
[
{ id: 100, status: 'ordered', country: 'US', items: [] },
{ id: 101, status: 'ordered', country: 'UK', items: [] },
{ id: 102, status: 'shipped', country: 'UK', items: [] },
]
Desired aggregation outcome:
[
{ _id: 'US', status: { ordered: 1} },
{ _id: 'UK', status: { ordered: 1, shipped: 1 } }
]
I can $count and $group, but I am not sure how to put this together. Any hint is appreciated.
Thanks,
bluepuama
$group by country and status, and count total
$group by only country and construct array of status and count in key-value format
$set to update status field to object using $arrayToObject
db.collection.aggregate([
{
$group: {
_id: { country: "$country", status: "$status" },
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.country",
status: { $push: { k: "$_id.status", v: "$count" } }
}
},
{ $set: { status: { $arrayToObject: "$status" } } }
])
Playground
You can do it with a single $group stage like so:
db.collection.aggregate([
{
$group: {
_id: "$country",
"shipped": {
$sum: {
$cond: [
{
$eq: [
"$status",
"ordered"
]
},
0,
1
]
}
},
"ordered": {
$sum: {
$cond: [
{
$eq: [
"$status",
"shipped"
]
},
0,
1
]
}
}
}
},
{
$project: {
_id: 1,
status: {
shipped: "$shipped",
ordered: "$ordered"
}
}
}
])
Mongo Playground

How Do I get saperated counts while grouping on multiple fields mongodb aggregation?

I have the following documents in my candidate collection.
candidates = [
{
name: "amit",
age: 21,
city: 'pune'
}
{
name: "rahul",
age: 23,
city: 'pune'
},
{
name: "arjun",
age: 21,
city: 'pune'
},
{
name: "rakesh",
age: 23,
city: 'pune'
},
{
name: "amit",
age: 22,
city: 'nashik'
}
]
I want to group by age and city fields and count the documents based on its sum independent of each other
I tried following query
candidate.aggregate([
{$group: {_id: {age: '$age', city: '$city'}, count: {$sum: -1}}}
{$sort: {count: -1}},
{$limit: 10}
])
which gives me count of combine results of age and city.
what I want instead
{
ages: [
{
age: 21,
count: 2
},
{
age: 23,
count: 2
},
{
age: 21,
count: 1
}
],
cities: [
{
city: 'pune',
count: 4
},
{
city: 'nashik',
count: 1
}
]
}
thanks for any help.
You need to use $facet stage, with a $group stage per age, and another per city.
Here's the query :
db.collection.aggregate([
{
$facet: {
byCity: [
{
$group: {
_id: {
city: "$city"
},
count: {
$sum: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$limit: 10
}
],
byAge: [
{
$group: {
_id: {
age: "$age",
},
count: {
$sum: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$limit: 10
}
]
}
},
])
Mongo playground

Add a field with increasing value in MongoDB Aggregation based on condition

Sample of my collection :
[
{
_id: "bmasndvhjbcw",
name: "lucas",
occupation: "scientist",
age: 55,
location: "texas",
joining_date: 2019-01-01T15:24:15.068+00:00
},
{
_id: "bmasndvhjbcx",
name: "mark",
occupation: "scientist",
age: 45,
location: "texas",
joining_date: 2019-01-01T15:24:15.068+00:00
},
{
_id: "bmasndvhjbca",
name: "stuart",
occupation: "lab assistant",
age: 25,
location: "texas",
joining_date: 2019-01-02T20:25:16.068+00:00
},
{
_id: "bmasndvhjbcq",
name: "cooper",
occupation: "physicist",
age: 69,
location: "texas"
}
]
Which ever docs has joining_date column need to add a field with increasing value by checking the date like joining_date_count:1
if the dates are same like in two cases mark and lucas . count should consider it as different values and increase the count.
Expected Output :
[
{
_id: "bmasndvhjbcw",
name: "lucas",
occupation: "scientist",
age: 55,
location: "texas",
joining_date: 2019-01-01T15:24:15.068+00:00,
joining_date_count:1
},
{
_id: "bmasndvhjbcx",
name: "mark",
occupation: "scientist",
age: 45,
location: "texas",
joining_date: 2019-01-01T15:24:15.068+00:00,
joining_date_count:2
},
{
_id: "bmasndvhjbca",
name: "stuart",
occupation: "lab assistant",
age: 25,
location: "texas",
joining_date: 2019-01-02T20:25:16.068+00:00,
joining_date_count:3
},
{
_id: "bmasndvhjbcq",
name: "cooper",
occupation: "physicist",
age: 69,
location: "texas"
}
]
This aggregation adds a field with a counter:
db.collection.aggregate( [
{
$match: {
joining_date: { $exists: true }
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
_id: 0,
R: {
$map: {
input: { $range: [ 0, { $size: "$docs" } ] },
in: {
$mergeObjects: [
{ joining_date_count: { $add: [ "$$this", 1 ] } },
{ $arrayElemAt: [ "$docs", "$$this" ] }
]
}
}
}
}
},
{
$unwind: "$R"
},
{
$replaceRoot: { newRoot: "$R" }
}
] )
You can try below query :
db.collection.aggregate([
/** Sort on joining_date field which will arrange docs with missing field at top & ascending where field exists */
{
$sort: {
joining_date: 1
}
},
/** group on empty & push every doc in collection to an array field named data */
{
$group: {
_id: "",
data: {
$push: "$$ROOT"
}
}
},
/** split data array into two array one has doc which doesn't field & other has docs which does have field */
{
$addFields: {
data: {
$reduce: {
input: "$data",
initialValue: {
missingField: [],
fieldExists: []
},
in: {
missingField: {
$cond: [
{
"$ifNull": [
"$$this.joining_date",
false
]
},
"$$value.missingField",
{
$concatArrays: [
"$$value.missingField",
[
"$$this"
]
]
}
]
},
fieldExists: {
$cond: [
{
"$ifNull": [
"$$this.joining_date",
false
]
},
{
$concatArrays: [
"$$value.fieldExists",
[
"$$this"
]
]
},
"$$value.fieldExists"
]
}
}
}
}
}
},
/** Add new field 'joining_date_count' to docs based on that doc index in fieldExists array &
* finally concatinate missingField with newly formed fieldExists array */
{
$addFields: {
"data": {
$concatArrays: [
"$data.missingField",
{
$map: {
input: "$data.fieldExists",
in: {
$mergeObjects: [
"$$this",
{
joining_date_count: {
$add: [
1,
{
$indexOfArray: [
"$data.fieldExists",
"$$this"
]
}
]
}
}
]
}
}
}
]
}
}
},
/** unwind data array */
{
$unwind: "$data"
},
/** replace each docs root as data field */
{
$replaceRoot: {
newRoot: "$data"
}
}
])
Test : MongoDB-Playground

MongoDB - Help needed to make some aggregation

I am having a bad time trying to do an aggregation in MongoDB.
I need to cross some infos from each user and as a final result I want a list of users (where there is only one object for each user) and for each object there is some lists with distinct information.
1 - The createdAtList array must be ordered from the oldest to the newest date. The sumOfTotal means the current position total summed up with the previous sumOfTotal (Exemplified in the code below), not just the sum of the total's
2 - The categotyList must be ordered like: category1, category2, category3 ...
3 - The desired final result must be ordered like: user1, user2, user3 ...
Basically I need some help to do the following:
//List of docs from my collection:
[
{
_id: "doc1",
user: "user1",
category: "category1",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc2",
user: "user1",
category: "category2",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc3",
user: "user1",
category: "category1",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc4",
user: "user1",
category: "category2",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc5",
user: "user1",
category: "category3",
createdAt: "2017-11-11T00:00:00.000Z"
}
]
//Desired result:
{
user: "user1",
createdAtList: [ //list ordered by createdAt
{
createdAt: "2017-11-11T00:00:00.000Z",
total: 1,
sumOfTotal: 0
}
{
createdAt: "2017-12-12T00:00:00.000Z",
total: 2,
sumOfTotal: 3 //summed up with the previous
}
{
createdAt: "2018-01-01T00:00:00.000Z",
total: 2,
sumOfTotal: 5 //summed up with the previous
}
],
categotyList: [ //list ordered by category
{
category: "category1",
total: 2
},
{
category: "category2",
total: 2
},
{
category: "category3",
total: 1
}
]
},
...
Is possible to do this in the same aggregate?
I do not think it really makes sense to have the createdAtList.sumOfTotal field. I do not think the fields in an array should be dependent upon a particular order of the elements. If you want some field to contain the sum of the createdAtList.total field, I think there should only be one field (outside of the array). That being said, here is the query I came up with to give you the desired results (using "users" as the name of the collection):
db.users.aggregate([
{
$group: {
_id: {
user: "$user",
createdAt: "$createdAt"
},
total: { $sum: 1 },
category: { $push: "$category" }
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAt: "$_id.createdAt",
total: "$total",
category: 1
}
},
{ $unwind: "$category" },
{
$group: {
_id: {
user: "$user",
category: "$category"
},
catTotal: { $sum: 1 },
createdAtList: {
$push: {
createdAt: "$createdAt",
total: "$total"
}
}
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAtList: 1,
category: "$_id.category",
catTotal: 1
}
},
{ $unwind: "$createdAtList" },
{
$group: {
_id: "$user",
createdAtList: {
$addToSet: "$createdAtList"
},
categoryList: {
$addToSet: {
category: "$category",
total: "$catTotal"
}
}
}
},
{ $unwind: "$createdAtList" },
{ $sort: { "createdAtList.createdAt": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$push: "$createdAtList"
},
categoryList: {
$first: "$categoryList"
}
}
},
{ $unwind: "$categoryList" },
{ $sort: { "categoryList.category": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$first: "$createdAtList"
},
categoryList: {
$push: "$categoryList"
}
}
},
{
$project: {
_id: 0,
user: "$_id",
createdAtList: 1,
sumOfTotal: { $sum: "$createdAtList.total" },
categoryList: 1
}
},
{ $sort: { user: 1 } },
]).pretty()