0Pricing
MongoDB Academy · 课时

$group:聚合并计算总计

您将按键对文档分组,并使用 $group 计算总和、平均值、计数及其他累加结果。

$group:聚合并计算总计 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

What Does $group Do?

The $group stage collapses multiple input documents into fewer output documents based on a grouping key. Documents that share the same key value are merged into a single output document, and accumulator operators compute aggregate values for each group. Think of it as MongoDB's equivalent of SQL's GROUP BY clause combined with aggregate functions like SUM and COUNT.

// Count orders per user
db.orders.aggregate([
  { $group: {
    _id: '$userId',         // group by userId
    orderCount: { $sum: 1 }  // count each document as 1
  }}
]);
// Output: one doc per unique userId with their order count

The _id Field in $group

Every $group stage requires an _id field that defines the grouping key. The _id can be a field reference ('$field'), a computed expression, an object with multiple fields (for compound grouping), or null (to aggregate the entire collection into one document). The _id value in the output is the group key.

// Group by single field
{ $group: { _id: '$status' } }

// Group by multiple fields (compound key)
{ $group: { _id: { year: { $year: '$createdAt' }, status: '$status' } } }

// Group all documents into one (grand total)
{ $group: { _id: null, grandTotal: { $sum: '$amount' } } }

// Group by computed expression
{ $group: { _id: { $toLower: '$category' } } }

$sum: Counting and Summing

$sum is the most used accumulator. Pass 1 to count documents in each group, or a field reference to sum numeric field values. You can also pass an expression that evaluates to a number. $sum ignores non-numeric values and missing fields (they count as zero), making it safe to use on optional numeric fields.

db.sales.aggregate([{
  $group: {
    _id: '$region',
    // Count documents in each group
    transactionCount: { $sum: 1 },
    // Sum the 'amount' field
    totalRevenue: { $sum: '$amount' },
    // Sum a computed expression
    totalWithTax: { $sum: { $multiply: ['$amount', 1.1] } }
  }
}]);

$avg, $min, $max

$avg computes the arithmetic mean, $min finds the smallest value, and $max finds the largest value across all documents in a group. All three work on numeric values and also on strings and dates (for min/max). They ignore null and missing field values.

db.orders.aggregate([{
  $group: {
    _id: '$productId',
    avgRating: { $avg: '$rating' },          // average rating
    minPrice: { $min: '$price' },            // lowest price ever sold
    maxPrice: { $max: '$price' },            // highest price ever sold
    firstOrder: { $min: '$createdAt' },      // earliest date
    latestOrder: { $max: '$createdAt' }      // most recent date
  }
}]);

$count in $group

While you typically count by using { $sum: 1 } in a $group stage, MongoDB also provides a standalone $count stage to count the total number of documents in the stream. The $count stage emits a single document with the count under the field name you specify. It's equivalent to a $group with _id: null and a $sum: 1.

// Count total published articles
db.articles.aggregate([
  { $match: { status: 'published' } },
  { $count: 'totalPublished' }  // emits { totalPublished: N }
]);

// Equivalent using $group:
db.articles.aggregate([
  { $match: { status: 'published' } },
  { $group: { _id: null, totalPublished: { $sum: 1 } } }
]);

Compound Grouping Keys

To group by multiple fields simultaneously, pass an object as the _id value. Each key in the object becomes part of the compound group key. The output documents have a nested _id object. This is the standard way to produce multi-dimensional aggregations like 'revenue by region and month'.

// Revenue breakdown by region AND year-month
db.sales.aggregate([
  { $group: {
    _id: {
      region: '$region',
      year: { $year: '$saleDate' },
      month: { $month: '$saleDate' }
    },
    revenue: { $sum: '$amount' },
    count: { $sum: 1 }
  }},
  { $sort: { '_id.year': 1, '_id.month': 1, '_id.region': 1 } }
]);
// Output: { _id: { region: 'EMEA', year: 2024, month: 3 }, revenue: 50000, count: 120 }

Accumulating Into Arrays With $push

The $push accumulator collects all values from a field across the grouped documents into an array. This is useful for gathering all order IDs under a user, all tags under a category, or all user IDs who purchased a product. The result array can contain duplicates; use $addToSet instead to collect unique values.

// Collect all order IDs per user
db.orders.aggregate([{
  $group: {
    _id: '$userId',
    orderIds: { $push: '$_id' },         // all order IDs for this user
    amounts: { $push: '$amount' },       // all amounts
    // Collect whole sub-documents
    orderSummaries: {
      $push: { orderId: '$_id', amount: '$amount', status: '$status' }
    }
  }
}]);

$first and $last Accumulators

$first and $last return the first and last field value encountered per group, in the order documents are processed. Because MongoDB does not guarantee document order unless you sort first, you should add a $sort stage before the $group stage to make $first/$last meaningful—for example, 'first order date per customer'.

// Most and least recent order per customer
db.orders.aggregate([
  { $sort: { createdAt: 1 } },  // sort BEFORE group for meaningful first/last
  { $group: {
    _id: '$customerId',
    firstOrder: { $first: '$createdAt' },
    firstOrderId: { $first: '$_id' },
    lastOrder: { $last: '$createdAt' },
    lastAmount: { $last: '$amount' }
  }}
]);

Grouping on Computed Expressions

The $group _id can be any expression, not just a field reference. You can group by a truncated date (year-week), a computed category, a mathematical bucket, or a substring of a field. This lets you create flexible analytical groupings without requiring pre-computed bucket fields in your stored documents.

// Group orders by price bucket: 0-99, 100-499, 500+
db.orders.aggregate([{
  $group: {
    _id: {
      $switch: {
        branches: [
          { case: { $lt: ['$amount', 100] }, then: 'small' },
          { case: { $lt: ['$amount', 500] }, then: 'medium' }
        ],
        default: 'large'
      }
    },
    count: { $sum: 1 },
    totalRevenue: { $sum: '$amount' }
  }
}]);

Two-Stage Grouping

Complex analytics often require two $group stages in sequence: the first groups at a detailed level (e.g., by user), and the second groups the first stage's results at a higher level (e.g., by region). This two-stage pattern avoids nested $group expressions and produces cleaner, more understandable pipelines.

// Average orders per user, per region
db.orders.aggregate([
  // Stage 1: sum per user
  { $group: {
    _id: { userId: '$userId', region: '$region' },
    userOrderCount: { $sum: 1 }
  }},
  // Stage 2: average of those sums, per region
  { $group: {
    _id: '$_id.region',
    avgOrdersPerUser: { $avg: '$userOrderCount' },
    uniqueUsers: { $sum: 1 }
  }}
]);

$group Performance: No Index Available

Unlike $match, the $group stage cannot use an index—it must process all documents passed to it. This is why reducing the input with an early $match is critical. For very large datasets where $group uses too much memory, set allowDiskUse: true to spill to disk. Alternatively, pre-computing and storing aggregated values (the Computed Pattern) avoids runtime grouping on hot paths.

// Allow disk use for large aggregations
db.orders.aggregate(
  [
    { $match: { year: 2024 } },
    { $group: { _id: '$productId', revenue: { $sum: '$amount' } } },
    { $sort: { revenue: -1 } }
  ],
  { allowDiskUse: true }  // spill to disk if memory limit exceeded
);

Quick Check

Test your understanding of the $group stage in the aggregation pipeline.

Lesson Recap

In this lesson you learned: $group collapses documents by a key defined in the _id field, accumulators like $sum, $avg, $min, $max, $push compute aggregate values per group, and two-stage grouping enables hierarchical analytics. Next up we explore $sort, $limit, and $skip to order and paginate aggregation results.

常见问题解答

「$group:聚合并计算总计」课时是免费的吗?

是的 — 「$group:聚合并计算总计」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「$group:聚合并计算总计」这节课中我会学到什么?

您将按键对文档分组,并使用 $group 计算总和、平均值、计数及其他累加结果。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「$group:聚合并计算总计」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 管道概念:阶段、运算符和表达式
  2. $match 和 $project:筛选与重塑
  3. $group:聚合并计算总计
  4. 管道中的 $sort、$limit 和 $skip
← 返回 MongoDB Academy