$match and $group
Learn how to filter documents with $match and group them with $group to calculate counts, sums, and averages in MongoDB aggregation pipelines.
$match and $group
These are the two most important and most used stages in any aggregation pipeline. You will use them in almost every pipeline you ever write.
$match— filters documents, just likefind()$group— groups documents together and calculates things like counts, sums, and averages
$match
$match filters documents in the pipeline. Only documents that pass the filter move on to the next stage. Documents that do not match are discarded.
It works exactly like the filter in find() — all the same operators work: $eq, $gt, $in, $and, $or, and everything else you learned in the querying section.
Syntax
{ $match: { filter } }Example — Match enrolled students only
db.students.aggregate([
{ $match: { enrolled: true } }
])This returns all enrolled students — same as db.students.find({ enrolled: true }). On its own, $match is just a filter. It becomes powerful when combined with other stages.
Example — Match 10th grade enrolled students
db.students.aggregate([
{
$match: {
grade: "10th",
enrolled: true
}
}
])Example — Match students older than 15
db.students.aggregate([
{
$match: {
age: { $gt: 15 }
}
}
])Why use $match in a pipeline instead of find()?
When you need to filter before other stages like $group or $lookup, you must use $match inside the pipeline. You cannot mix find() and aggregate().
// Wrong — you cannot do this
db.students.find({ enrolled: true }).aggregate([...])
// Correct — filter inside the pipeline with $match
db.students.aggregate([
{ $match: { enrolled: true } },
{ $group: { ... } }
])Always put $match as the first stage in your pipeline when you need to filter. This reduces the number of documents all subsequent stages have to process — making the whole pipeline faster.
$group
$group groups documents by a field and lets you calculate things across each group — count how many, sum a field, find the average, get the minimum or maximum.
Syntax
{
$group: {
_id: "$fieldToGroupBy",
outputField: { $accumulatorOperator: expression }
}
}_id— required. The field to group by. All documents with the same value for this field end up in the same group.outputField— a name you choose for the calculated result.$accumulatorOperator— how to calculate the result —$sum,$avg,$min,$max,$count,$push, etc.
Group by a single field
// Count students per grade
db.students.aggregate([
{
$group: {
_id: "$grade",
totalStudents: { $sum: 1 }
}
}
])Result:
[
{ _id: "9th", totalStudents: 41 },
{ _id: "10th", totalStudents: 45 },
{ _id: "11th", totalStudents: 38 }
]$sum: 1 adds 1 for each document in the group — effectively counting them.
$sum
$sum adds up values across all documents in a group.
Count documents — $sum: 1
// Count how many students are in each grade
db.students.aggregate([
{
$group: {
_id: "$grade",
count: { $sum: 1 }
}
}
])Sum a field value
// Total fees collected per grade
db.students.aggregate([
{
$group: {
_id: "$grade",
totalFees: { $sum: "$feesPaid" }
}
}
])Result:
[
{ _id: "9th", totalFees: 410000 },
{ _id: "10th", totalFees: 495000 },
{ _id: "11th", totalFees: 418000 }
]$avg
$avg calculates the average value of a field across all documents in a group.
// Average age of students per grade
db.students.aggregate([
{
$group: {
_id: "$grade",
averageAge: { $avg: "$age" }
}
}
])Result:
[
{ _id: "9th", averageAge: 14.8 },
{ _id: "10th", averageAge: 15.9 },
{ _id: "11th", averageAge: 16.7 }
]// Average exam score per grade — only enrolled students
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: "$grade",
averageScore: { $avg: "$examScore" }
}
}
])$min and $max
$min returns the smallest value in the group. $max returns the largest.
// Youngest and oldest student per grade
db.students.aggregate([
{
$group: {
_id: "$grade",
youngestAge: { $min: "$age" },
oldestAge: { $max: "$age" }
}
}
])Result:
[
{ _id: "9th", youngestAge: 13, oldestAge: 16 },
{ _id: "10th", youngestAge: 14, oldestAge: 17 },
{ _id: "11th", youngestAge: 15, oldestAge: 18 }
]// Highest and lowest exam score per grade
db.students.aggregate([
{
$group: {
_id: "$grade",
highestScore: { $max: "$examScore" },
lowestScore: { $min: "$examScore" }
}
}
])Multiple Accumulators in One $group
You can calculate multiple things in a single $group stage:
// Full summary per grade
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: "$grade",
totalStudents: { $sum: 1 },
averageAge: { $avg: "$age" },
averageScore: { $avg: "$examScore" },
highestScore: { $max: "$examScore" },
lowestScore: { $min: "$examScore" },
totalFees: { $sum: "$feesPaid" }
}
}
])Result:
[
{
_id: "10th",
totalStudents: 45,
averageAge: 15.9,
averageScore: 81.2,
highestScore: 98,
lowestScore: 42,
totalFees: 495000
},
...
]One pipeline stage — six pieces of information per grade.
Group by Multiple Fields
You can group by more than one field by passing an object to _id:
// Count students per grade AND per city
db.students.aggregate([
{
$group: {
_id: {
grade: "$grade",
city: "$address.city"
},
count: { $sum: 1 }
}
}
])Result:
[
{ _id: { grade: "10th", city: "Lahore" }, count: 18 },
{ _id: { grade: "10th", city: "Karachi" }, count: 15 },
{ _id: { grade: "11th", city: "Lahore" }, count: 12 },
...
]Group All Documents Together
Pass _id: null to group all documents into a single group — useful for calculating totals across the entire collection:
// Total students, average age, total fees across the whole school
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: null,
totalStudents: { $sum: 1 },
averageAge: { $avg: "$age" },
totalFees: { $sum: "$feesPaid" }
}
}
])Result:
[
{
_id: null,
totalStudents: 124,
averageAge: 15.8,
totalFees: 1323000
}
]Combining $match and $group
This is the most common pattern in aggregation — filter first, then group:
// Average score of enrolled students per grade
// only for grades with more than 30 students
db.students.aggregate([
// Stage 1 — filter enrolled students
{ $match: { enrolled: true } },
// Stage 2 — group by grade
{
$group: {
_id: "$grade",
totalStudents: { $sum: 1 },
averageScore: { $avg: "$examScore" }
}
},
// Stage 3 — filter groups — only grades with more than 30 students
{ $match: { totalStudents: { $gt: 30 } } }
])Notice the second $match — you can use $match multiple times in a pipeline. The first one filters the input documents, the second one filters the grouped results.
School System Examples
// How many students per grade
db.students.aggregate([
{ $group: { _id: "$grade", count: { $sum: 1 } } }
])
// How many active teachers per subject
db.teachers.aggregate([
{ $match: { active: true } },
{ $group: { _id: "$subject", count: { $sum: 1 } } }
])
// Average exam score per grade — enrolled students only
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: "$grade",
averageScore: { $avg: "$examScore" }
}
}
])
// Total and average fees collected per grade
db.students.aggregate([
{ $match: { hasPaidFees: true } },
{
$group: {
_id: "$grade",
totalFees: { $sum: "$feesPaid" },
averageFees: { $avg: "$feesPaid" },
studentCount: { $sum: 1 }
}
}
])
// Highest and lowest scoring student age per grade
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: "$grade",
topScore: { $max: "$examScore" },
bottomScore: { $min: "$examScore" }
}
}
])
// School-wide summary
db.students.aggregate([
{ $match: { enrolled: true } },
{
$group: {
_id: null,
totalStudents: { $sum: 1 },
averageAge: { $avg: "$age" },
averageScore: { $avg: "$examScore" },
totalFees: { $sum: "$feesPaid" }
}
}
])Quick Reference
| Accumulator | What it does | Example |
|---|---|---|
$sum: 1 | Count documents in group | count: { $sum: 1 } |
$sum: "$field" | Add up field values | total: { $sum: "$feesPaid" } |
$avg | Average of field values | avg: { $avg: "$score" } |
$min | Smallest value in group | min: { $min: "$age" } |
$max | Largest value in group | max: { $max: "$age" } |
When you need a second filter after grouping — like only showing grades with more than 30 students — add another $match stage after $group. The second $match filters the grouped results, not the original documents. This is a very common and powerful pattern.
Aggregation Pipeline
Learn what aggregation is, how the pipeline works, and why it is the most powerful tool in MongoDB for analyzing and transforming data.
$project, $sort, $limit, and $skip
Learn how to reshape documents with $project, sort results with $sort, and control output size with $limit and $skip in MongoDB aggregation pipelines.