Projection and group on nested object mongodb aggregation query - mongodb

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.

Related

MongoDB get size of unwinded array

I'm trying to return size of 'orders' and sum of 'item' values for each 'order' for each order from documents like the example document:
orders: [
{
order_id: 1,
items: [
{
item_id: 1,
value:100
},
{
item_id: 2,
value:200
}
]
},
{
order_id: 2,
items: [
{
item_id: 3,
value:300
},
{
item_id: 4,
value:400
}
]
}
]
I'm using following aggregation to return them, everything works fine except I can't get size of 'orders' array because after unwind, 'orders' array is turned into an object and I can't call $size on it since it is an object now.
db.users.aggregate([
{
$unwind: "$orders"
},
{
$project: {
_id: 0,
total_values: {
$reduce: {
input: "$orders.items",
initialValue: 0,
in: { $add: ["$$value", "$$this.value"] }
}
},
order_count: {$size: '$orders'}, //I get 'The argument to $size must be an array, but was of type: object' error
}
},
])
the result I expected is:
{order_count:2, total_values:1000} //For example document
{order_count:3, total_values:1500}
{order_count:5, total_values:2500}
I found a way to get the results that I wanted. Here is the code
db.users.aggregate([
{
$project: {
_id: 1, orders: 1, order_count: { $size: '$orders' }
}
},
{ $unwind: '$orders' },
{
$project: {
_id: '$_id', items: '$orders.items', order_count: '$order_count'
}
},
{ $unwind: '$items' },
{
$project: {
_id: '$_id', sum: { $sum: '$items.value' }, order_count: '$order_count'
}
},
{
$group: {
_id: { _id: '$_id', order_count: '$order_count' }, total_values: { $sum: '$sum' }
}
},
])
output:
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f1"), order_count: 2 }, total_values: 1000 }
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f2"), order_count: 3 }, total_values: 1500 }

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

Mongoose: $sum in $project return only zero

I have a query using $lookup that "join" two models and $project to select all fields that i need only, and in that $project I need to $sum a value called totalValue but only return zero:
My query
User.aggregate([{
$match: {
storeKey: req.body.store,
}
},
{
$group: {
_id: {
id: "$_id",
name: "$name",
cpf: "$cpf",
phone: "$phone",
email: "$email",
birthday: "$birthday",
lastName: "$lastname"
},
totalServices: {
$sum: "$services"
},
}
},
{
$lookup: {
from: "schedules",
"let": { "id": "$_id.phone" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$customer.phone", "$$id"] }}},
{ "$project": { "scheduleStart": 1, "scheduleEnd": 1 }}
],
"as": "user_detail"
}
},
{
$project: {
_id: 1,
name: 1,
name: 1,
cpf: 1,
phone: 1,
email: 1,
birthday: 1,
totalServices: 1,
totalValue: { $sum : "$user_detail.value" }, // here only return zero
count: {
$sum: 1
},
user_detail: 1
}
},
You need to $project your value field in the user_details projection to get it in the next aggregation stage
{ "$project": { "scheduleStart": 1, "scheduleEnd": 1, "value": 1 }}

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()

Group values by sub string in MongoDB

I have this documents in my collection :
{_id: "aaaaaaaa", email: "mail1#orange.fr"},
{_id: "bbbbbbbb", email: "mail2#orange.fr"},
{_id: "cccccccc", email: "mail3#orange.fr"},
{_id: "dddddddd", email: "mail4#gmail.com"},
{_id: "eeeeeeee", email: "mail5#gmail.com"},
{_id: "ffffffff", email: "mail6#yahoo.com"}
And i would like this result :
{
result: [
{domain: "orange.fr", count: 3},
{domain: "gmail.com", count: 2},
{domain: "yahoo.com", count: 1},
]
}
I'm not sure you can use the aggregator and $regex operator
Aggregation Framework
I don't believe that with the present document structure you can achieve the desired result by using the aggregation framework. If you stored the domain name in a separate field, it would have become trivial:
db.items.aggregate(
{
$group:
{
_id: "$emailDomain",
count: { $sum: 1 }
},
}
)
Map-Reduce
It's possible to implement what you want using a simple map-reduce aggregation. Naturally, the performance will not be good on large collections.
Query
db.emails.mapReduce(
function() {
if (this.email) {
var parts = this.email.split('#');
emit(parts[parts.length - 1], 1);
}
},
function(key, values) {
return Array.sum(values);
},
{
out: { inline: 1 }
}
)
Output
[
{
"_id" : "gmail.com",
"value" : 2
},
{
"_id" : "yahoo.com",
"value" : 1
},
{
"_id" : "orange.fr",
"value" : 3
}
]
Aggregation Framework
MongoDB 3.4(Released Nov 29, 2016) onwords in aggregation framework have many methods
[
{
$project: {
domain: {
$substr: ["$email", {
$indexOfBytes: ["$email", "#"]
}, {
$strLenBytes: "$email"
}]
}
},
{
$group: {
_id: '$domain',
count: {
$sum: 1
}
}
},
{
$sort: {
'count': -1
}
},
{
$group: {
_id: null,
result: {
$push: {
'domain': "$_id",
'count': '$count'
}
}
}
}
]
Results
{
_id: null,
result: [
{domain: "#orange.fr", count: 3},
{domain: "#gmail.com", count: 2},
{domain: "#yahoo.com", count: 1},
]
}