MongoDB aggregation, distinct several fields and count them - mongodb

The best way I can describe what I want to achieve is using an example. Basically I would have a list of cars say:
[
{
_id: 1,
make: 'Toyota',
model: 'Prius'
},
{
_id: 2,
make: 'Toyota',
model: 'Prius'
},
{
_id: 3,
make: 'Toyota',
model: 'Yaris'
},
{
_id: 4,
make: 'Lexus',
model: 'RX400'
}
]
And now I want to group/distinct them by make and model (and possibly more fields) and count the totals. The final result should look something like:
{
makes: [
{
name: 'Toyota',
total: 3
}, {
name: 'Lexus',
total: 1
}
],
models: [
{
name: 'Prius',
total: 2
},
{
name: 'Yaris',
total: 1
},
{
name: 'RX400',
total: 1
}
]
}
I'm completely stuck with this one. So far, the only way I can achieve this is by calling several async aggregation calls for each field. However, I would prefer to do it in a single aggregation call, if that is possible at all (unless it's not a good idea performance wise).

Use $facet for this:
db.collection.aggregate([
{ "$facet": {
"makes": [
{ "$group": {
"_id": "$make",
"total": { "$sum": 1 }
} },
{ "$project": {
"_id": 0,
"name": "$_id",
"total": 1
} }
],
"models": [
{ "$group": {
"_id": "$model",
"total": { "$sum": 1 }
} },
{ "$project": {
"_id": 0,
"name": "$_id",
"total": 1
} }
]
} }
])

Related

Mongodb aggregation pipeline, combine result from two facet pipeline

I'm having a claim type:
type TClaim: {
insuredId: number,
treatmentInfo: { amount: number }[]
}
and a list of claims:
[
{
insuredId: 1,
treatmentInfo: [{amount: 1}, {amount: 2}]
},
{
insuredId: 1,
treatmentInfo: [{amount: 3}, {amount: 4}]
},
{
insuredId: 2,
treatmentInfo: [{amount: 1}, {amount: 2}]
}
]
I want to get the result like:
[{insuredId: 1, numberOfClaims: 2, amount: 10},{insuredId: 2, numberOfClaims: 1, amount: 3}]
I'm using the $facet operator in mongodb aggregation, one for counting numberOfClaims and one for calculating the amount of each insurer. But I can't combine it to get the result that I want.
$facet: {
totalClaims: [ { $group: { _id: '$insuredId', totalClaims: { $count: {} } } } ],
amount: [ { $unwind: { path: '$treatmentInfo'}},
{ $group:
{ _id: '$insuredId',
amount: { $sum: '$treatmentInfo.amount',
},
},
},
]
Is there a reason why you want to use $facet? - I am just curious
You just need to add a new fields that sums up all the amount in the array first and then do a group stage by insuredId. The query is pretty much self-explanatory.
db.collection.aggregate([
{
"$addFields": {
"totalAmount": {
"$sum": "$treatmentInfo.amount"
}
}
},
{
"$group": {
"_id": "$insuredId",
"numberOfClaims": {
"$sum": 1
},
"amount": {
"$sum": "$totalAmount"
}
}
}
])
Result:
[
{
"_id": 1,
"amount": 10,
"numberOfClaims": 2
},
{
"_id": 2,
"amount": 3,
"numberOfClaims": 1
}
]
MongoDB Playground

get rank in mongodb with date range

I have following stat data stored daily for users.
{
"_id": {
"$oid": "638df4e42332386e0e06d322"
},
"appointment_count": 1,
"item_id": 2,
"item_type": "user",
"company_id": 5,
"created_date": "2022-12-05",
"customer_count": 1,
"lead_count": 1,
"door_knocks": 10
}
{
"_id": {
"$oid": "638f59a9bf33442a57c3aa99"
},
"lead_count": 2,
"item_id": 2,
"item_type": "user",
"company_id": 5,
"created_date": "2022-12-06",
"video_viewed": 2,
"door_knocks": 9
}
And I'm using the following query to get the items by rank
user_stats_2022_12.aggregate([{"$match":{"company_id":5,"created_date":{"$gte":"2022-12-04","$lte":"2022-12-06"}}},{"$setWindowFields":{"partitionBy":"$company_id","sortBy":{"door_knocks":-1},"output":{"item_rank":{"$denseRank":{}},"stat_sum":{"$sum":"$door_knocks"}}}},{"$facet":{"metadata":[{"$count":"total"}],"data":[{"$skip":0},{"$limit":100},{"$sort":{"item_rank":1}}]}}])
It's giving me the rank but with the above data, the record with item_id: 2 are having different rank for same item_id. So I wanted to group them by item_id and then applied rank.
It's a little messy, but here's a playground - https://mongoplayground.net/p/JrJOo4cl9X1.
If you're going to sort by knocks after grouping, I'm assuming that you'll want the sum of door_knocks for a given item_id for this sort.
db.collection.aggregate([
{
$match: {
company_id: 5,
created_date: {
"$gte": "2022-12-04",
"$lte": "2022-12-06"
}
}
},
{
$group: {
_id: {
item_id: "$item_id",
company_id: "$company_id"
},
docs: {
$push: "$$ROOT"
},
total_door_knocks: {
$sum: "$door_knocks"
}
}
},
{
$setWindowFields: {
partitionBy: "$company_id",
sortBy: {
total_door_knocks: -1
},
output: {
item_rank: {
"$denseRank": {}
},
stat_sum: {
"$sum": "$total_door_knocks"
}
}
}
},
{
$unwind: "$docs"
},
{
$project: {
_id: "$docs._id",
appointment_count: "$docs.appointment_count",
company_id: "$docs.company_id",
created_date: "$docs.created_date",
customer_count: "$docs.customer_count",
door_knocks: "$docs.door_knocks",
item_id: "$docs.item_id",
item_type: "$docs.item_type",
lead_count: "$docs.lead_count",
item_rank: 1,
stat_sum: 1,
total_door_knocks: 1
}
},
{
$facet: {
metadata: [
{
"$count": "total"
}
],
data: [
{
"$skip": 0
},
{
"$limit": 100
},
{
"$sort": {
"item_rank": 1
}
}
]
}
}
])

MongoDB multiple nested groups

I have documents in mongodb like this
{
_id: "5cfed55974c7c52ecc33ada8",
name: "Garona",
realm: "Blackrock",
faction: "Horde",
race: "Orc",
class: "Rogue",
guild: "",
level: 33,
lastSeen: "2019-06-10T00:00:00.000Z",
__v: 0
},
{
_id: "5cfed55974c7c52ecc33ade8",
name: "Muradin",
realm: "Alleria",
faction: "Alliance",
race: "Dwarf",
class: "Warrior",
guild: "Stormstout Brewing Co",
level: 42,
lastSeen: "2019-06-11T00:00:00.000Z",
__v: 0
}
What I'm trying to do, is to group by a fields and get a sum of it. So far I figured it out to do it for one field at once like so
{
$group: {
_id: {
classes: '1',
class: '$class'
},
total: { $sum: 1 }
}
},
{
$group: {
_id: '$_id.classes',
total: { $sum: '$total' },
classes: {
$push: {
class: '$_id.class',
total: '$total'
}
}
}
}
Which produces something like this
{
_id: "1",
total: 40,
classes: [
{
class: "Warrior",
total: 17
},
{
class: "Rogue",
total: 23
}
}
But I want to do it for more than one field at once, so that I can get an output like this.
{
_id: "1",
total: 40,
classes: [
{
class: "Warrior",
total: 17
},
{
class: "Rogue",
total: 23
},
factions: [
{
faction: "Alliance",
total: 27
},
{
faction: "Horde",
total: 13
}
}
No I'm wondering if it is even possible to do it in one query in an easy way or if I would be better to do a seperate query for each field.
You can do this by using the $facet aggregation stage
Processes multiple aggregation pipelines within a single stage on the same set of input documents. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.
I only slightly modified your original pipeline, and then just copied it for the 'factions' field.
The last 3 stages in my solution aren't really necessary, they just clean up the output a little bit.
You can probably take it from here, good luck.
db.collection.aggregate([
{
"$facet": {
"classes": [
{
$group: {
_id: "$class",
total: {
$sum: 1
}
}
},
{
$group: {
_id: null,
total: {
$sum: "$total"
},
"classes": {
$push: {
class: "$_id",
total: "$total"
}
}
}
}
],
"factions": [
{
$group: {
_id: "$faction",
total: {
$sum: 1
}
}
},
{
$group: {
_id: null,
total: {
$sum: "$total"
},
"factions": {
$push: {
faction: "$_id",
total: "$total"
}
}
}
}
]
}
},
{
$unwind: "$classes"
},
{
$unwind: "$factions"
},
{
$project: {
"classes._id": 0,
"factions._id": 0
}
}
])
Output
[
{
"classes": {
"classes": [
{
"class": "Warrior",
"total": 1
},
{
"class": "Rogue",
"total": 1
}
],
"total": 2
},
"factions": {
"factions": [
{
"faction": "Alliance",
"total": 1
},
{
"faction": "Horde",
"total": 1
}
],
"total": 2
}
}
]

count the number of members before group them

here is my document:
member: [
{
name: 'Jack',
group: 0
},
{
name: 'Rose',
group: 0
},
{
name: 'Tom',
group: 1
}
]
first, count the number of members,then group them.
return:
{
count: 3,
member: [
[
{name: 'Jack'},
{name: 'Rose'}
],
[
{name: 'Tom'}
]
]
}
how to do?thank you very much!
First you should calculate member array size using $size in $project, after that unwind member and group them check below query :
db.collectionName.aggregate({
"$project": {
"count": {
"$size": "$member"
},
"member": 1
}
}, {
"$unwind": "$member"
}, {
"$group": {
"_id": "$member.group",
"data": {
"$push": {
"name": "$member.name"
}
},
"count": {
"$first": "$count"
}
}
}, {
"$group": {
"_id": "$count",
"member": {
"$push": "$data"
}
}
}).pretty()

Aggregate Field Values to Separate Key Names

I have a collection in MongoDB with sample data something like this (simplified):
{
_id: 1,
username: "ted",
content: "4125151",
status: "complete"
}
{
_id: 2,
username: "sam",
content: "4151",
status: "new"
}
{
_id: 3,
username: "ted",
content: "511",
status: "new"
}
{
_id: 4,
username: "ted",
content: "411",
status: "in_progress"
}
{
_id: 5,
username: "pat",
content: "1sds51",
status: "complete"
}
{
_id: 6,
username: "ted",
content: "4151",
status: "in_progress"
}
{
_id: 7,
username: "ted",
content: "4125",
status: "in_progress"
}
I need to aggregate the data such that for each user, I get a count for each status value as well as a total number of records. The result should look like this:
[
{
username: “pat”,
new: 0,
in_progress: 0,
complete: 1,
total: 1
},
{
username: “sam”,
new: 1,
in_progress: 0,
complete: 0,
total: 1
},
{
username: “ted”,
new: 1,
in_progress: 3,
complete: 1,
total: 5
}
]
Or any format that will effectively serve the same purpose which is, I want to be able to use with ngRepeat to display on the front end in this format:
User New In Progress Complete Total
pat 0 0 1 1
sam 1 0 0 1
ted 1 3 1 5
I can perform this aggregation:
{
"$group": {
"_id": {
"username": "$username",
"status": "$status"
},
"count": {
"$sum": 1
}
}
}
This gives me the individual count for each user/status combination that has at least one record. But then I have to piece it together to get it in the format that I can use on the front end. This is not at all ideal.
Is there a way to perform the aggregation to get the data in the format that I need?
What you want is a "conditional" aggregation of the values to produce a distinct field property for each status.
This is pretty simple to do using the $cond operator:
[
{ "$group": {
"_id": "$username",
"new": { "$sum": { "$cond": [{ "$eq": [ "$status", "new" ] },1,0 ] } },
"complete": { "$sum": { "$cond": [{ "$eq": [ "$status", "complete" ] },1,0 ] } },
"in_progress": { "$sum": { "$cond": [{ "$eq": [ "$status", "in_progress" ] },1,0 ] } },
"total": { "$sum": 1 }
}}
]
Presuming of course those are the only "status" values, but if they are not then just add an additional $project to sum the fields you want:
[
{ "$match": { "status": { "$in": [ "new", "complete", "in_progress" ] } } },
{ "$group": {
"_id": "$username",
"new": { "$sum": { "$cond": [{ "$eq": [ "$status", "new" ] },1,0 ] } },
"complete": { "$sum": { "$cond": [{ "$eq": [ "$status", "complete" ] },1,0 ] } },
"in_progress": { "$sum": { "$cond": [{ "$eq": [ "$status", "in_progress" ] },1,0 ] } }
}},
{ "$project": {
"new": 1,
"complete": 1,
"in_progress": 1,
"total": { "$add": [ "$new", "$complete", "$in_progress" ] }
]
Or just include that $add within the $group with the same calculations for the separate fields. But the $match is probably just the best idea if there are indeed other status values you don't want.
Another answer using $group twice and a $push, In this below query you need to compute the final total on UI side.
db.collection.aggregate([
{
"$group": {
"_id": {
"username": "$username",
"status": "$status"
},
"statuscount": {
"$sum": 1
}
}
},
{
"$group": {
"_id": "$_id.username",
"finalstatus": {
"$push": {
"Status": "$_id.status",
"statuscount": "$statuscount"
}
}
}
}
])