$group Stage
$group Stage
Level 6 — Aggregation Framework The aggregation pipeline stage that groups input documents by a specified identifier key and applies accumulator operations to compute aggregate summaries, serving as the direct equivalent of SQL's
GROUP BYclause.
1. Prerequisites
- Aggregation Pipeline (Concept) — The parent pipeline framework.
2. Term Category
Aggregation (Key Reduction & Grouping Stage): The $group stage reduces document streams into distinct groups by a specified group key expression and computes accumulated metrics for each group.
3. Explanation
Environment Context
- MongoDB Core (Executes in memory. Capped by a strict 100MB limit per stage. If the grouped data footprint exceeds 100MB, you must enable the
{ allowDiskUse: true }option to allow temporary files spillover).
(1) Design Motivation — "Why did we design this?"
Basic queries retrieve individual documents.
However, business reports require summaries:
- "What is our total revenue per country?"
- "How many items do we have in each warehouse?"
- "What is the average rating for each product?"
In PostgreSQL, you group rows using the GROUP BY clause:
SELECT category, SUM(stock) FROM products GROUP BY category;
We designed the $group stage in MongoDB to provide this grouping capability.
It takes the incoming stream of individual documents, partitions them into separate groups based on a key you define, and computes summary statistics (like sums or averages) for each group, outputting one consolidated document per group.
(2) The Structure of $group
The $group stage has a strict JSON structure:
{
$group: {
_id: <grouping_key_expression>, // MANDATORY: Defines the group boundary
<output_field_1>: { <accumulator_operator>: <field_expression> }
}
}
Rule 1: The Grouping Identifier (_id)
You must specify the _id field. This tells MongoDB which field values define a group.
- Group by field:
_id: "$category"(Finds unique values of thecategoryfield. Note the$prefix!). - Group all together:
_id: null(Groups all incoming documents into a single global group. Useful for calculating database-wide averages or sums, equivalent to runningSELECT SUM(amount) FROM ordersin SQL).
Rule 2: Field Path Dollar Prefixes
To reference a field's value in the grouping key or calculations, you must prefix it with $ (e.g. "$price"). Omitting the $ prefix treats the word as a literal string constant, grouping all documents under the same string value.
(3) Reality Metaphor (Pigeonhole Mail Sorting)
Imagine a mail carrier sorting incoming letters:
$groupStage: A wall containing labeled Pigeonhole Slots.- The mail carrier picks up a letter, reads the postal city key (
_id: "$city"), and slides the letter into the slot labeled"Boston"or"New York". - If they are tracking metrics (like counting letters), they click a mechanical tally clicker (
$sum: 1) attached to that specific slot. - Once finished sorting, each pigeonhole slot represents one group bundle containing the aggregated count.
- The mail carrier picks up a letter, reads the postal city key (
(4) Code Examples
Grouping and Summing
Group products by category and calculate total stock levels:
db.products.aggregate([
{
$group: {
_id: "$category", // Group by the 'category' field values
total_stock: { $sum: "$stock" } // Accumulator: sum the 'stock' field values
}
}
]);
// Output:
// { "_id": "Electronics", "total_stock": 450 }
// { "_id": "Clothing", "total_stock": 1200 }
4. Common Mistakes & Pitfalls
Mistake 1: Omitting the '$' prefix on the grouping key field path inside the '_id' declaration
The mistake: Writing the group stage as { $group: { _id: "category", count: { $sum: 1 } } } to count products.
Why it's wrong: Without the $ prefix, the query compiler treats the characters "category" as a literal string word.
It groups all documents under the identical key value "category".
The output will display only one single group with a count of all items, rather than splitting them by individual categories.
Fix: Always prefix the target grouping field path with a dollar sign: _id: "$category".
Mistake 2: Grouping Entire Collections Without Specifying _id: null in $group Stage
The mistake: Writing db.sales.aggregate([{ $group: { totalRevenue: { $sum: "$amount" } } }]) without _id.
Why it's wrong: The _id field is MANDATORY in $group stages! To calculate a single global aggregate across all documents, set _id: null.
Incorrect:
db.sales.aggregate([{ $group: { totalRevenue: { $sum: "$amount" } } }]); // ❌ Missing _id field!
Fix:
db.sales.aggregate([{ $group: { _id: null, totalRevenue: { $sum: "$amount" } } }]); // Global total
Mistake 3: Using Non-Accumulator Expressions Directly inside $group Output Fields
The mistake: Writing { $group: { _id: "$category", name: "$name" } }.
Why it's wrong: Output fields inside $group MUST use accumulator operators (e.g. $first, $last, $push, $sum). Direct field paths like name: "$name" are invalid.
Incorrect:
db.sales.aggregate([{ $group: { _id: "$category", name: "$name" } }]); // ❌ Missing accumulator!
Fix:
db.sales.aggregate([{ $group: { _id: "$category", firstName: { $first: "$name" } } }]);
5. Practice Exercises
Exercise 1: Basic Grouping and Document Counting
Scenario:
Group documents in collection users by role and calculate total user count per role.
Requirements:
- Use
$group: { _id: "$role", userCount: { $sum: 1 } }.
Answer
Implementation
db.users.aggregate([
{
$group: {
_id: "$role",
userCount: { $sum: 1 }
}
}
]);
Technical Explanation
$groupcollapses documents sharing the same_idgroup expression.{ $sum: 1 }increments the accumulator counter by 1 for each document in the group.- Foundation stage for analytical summary reports.
Exercise 2: Multi-Field Compound Group Keys
Scenario:
Group sales orders by BOTH year and status to analyze regional sales breakdowns.
Requirements:
- Group by compound
_id: { year: { $year: "$createdAt" }, status: "$status" }.
Answer
Implementation
db.orders.aggregate([
{
$group: {
_id: {
year: { $year: "$createdAt" },
status: "$status"
},
totalRevenue: { $sum: "$totalAmount" }
}
}
]);
Technical Explanation
- Compound group keys (
_id: { k1: v1, k2: v2 }) group document streams across multiple dimensions. - Evaluates date extraction expressions directly inside the group key.
- Enables multi-dimensional pivot reporting.
Exercise 3: Global Single-Document Aggregation
Scenario:
Calculate the total revenue ($sum) and average discount ($avg) across ALL orders in the collection without splitting by category.
Requirements:
- Use
_id: nullin$group.
Answer
Implementation
db.orders.aggregate([
{
$group: {
_id: null,
grandTotalRevenue: { $sum: "$totalAmount" },
averageDiscount: { $avg: "$discount" }
}
}
]);
Technical Explanation
- Setting
_id: nullcollapses the ENTIRE document stream into a single global summary output document. - Computes collection-wide KPI totals.
- Returns a single document response payload.
6. Related Terms
- Aggregation Pipeline (Concept) — The parent pipeline framework.
- Accumulator Operators (
$sum,$avg,$min,$max,$count,$push,$addToSet) — The calculation operators. $matchStage — Related concept:$matchStage.
7. Key Takeaways
$groupaggregates documents by a specified field key.- Direct NoSQL equivalent to SQL's
GROUP BYstatement. - The
_idfield is mandatory and defines the grouping key. - Set
_id: nullto calculate a single global sum or average. - Always prefix grouping and calculation fields with
$(e.g."$price"). - Runs in memory; requires
{ allowDiskUse: true }if data exceeds 100MB. - Outputs a single consolidated document per unique grouping key.