Advanced Aggregation Pipelines: Complex Expressions and Transformations

Advanced Pipeline Aggregation—Mastering complex expressions and type conversions can solve 90% of data analysis scenarios.

Advanced Learning Path for Aggregation Pipelines: This course begins with conditional expressions ($cond/$switch/$ifNull), then moves on to date operations, type conversions, string operations, and array operations, culminating in a comprehensive hands-on exercise. Each topic is independent yet interconnected—conditional expressions are used in "determining weekdays/weekends" within date operations, type conversions are used in "formatting dates with $toString," and string operations are used in "concatenating and displaying text." Understanding these connections helps build a comprehensive knowledge network.

The Expression System of the Aggregation Pipeline: Expressions in the MongoDB aggregation pipeline are divided into five categories: 1. Boolean expressions ($cond/$switch/$ifNull/$and/$or/$not): conditional checks and logical combinations; 2. Comparison expressions ($eq/$gt/$gte/$lt/$lte/$ne/$cmp): value comparisons; 3. Arithmetic expressions ($add/$subtract/$multiply/$divide/$mod/$round): Numerical calculations; 4. String expressions ($concat/$substr/$toUpper/$toLower/$split): Text processing; 5. Array expressions ($map/$filter/$reduce/$arrayElemAt/$size): Array transformations. By mastering these five types of expressions, you can combine them to create data transformation logic of any complexity.

Mnemonic for Classifying Expressions: The five types of expressions can be memorized using the following process: "Conditional → Comparison → Calculation → Text → Array"—1. First, evaluate the condition (Boolean): Is a calculation needed? Which branch should be selected? 2. Next, compare: Does the value meet the condition? 3. Then calculate: Perform arithmetic operations if the condition is met; 4. Then format: Convert the calculation result into display text; 5. Then process the collection: Perform batch operations on the array. This process corresponds exactly to the typical steps in report calculation—determine grouping conditions → compare thresholds → calculate statistical values → format the output → process array fields.

The Power of Combining Expressions: The true power of the aggregation pipeline lies in combining expressions—while a single expression performs a simple transformation, combining them can solve complex business problems. Example: Calculate the "User Tier Label" = $switch(condition: $gte(totalSpent, 10000) → 'VIP') → $concat(label + totalSpent string) → output "VIP ¥15,800". This chain combines four types of expressions: $switch (condition), $gte (comparison), $concat (string concatenation), and $toString (type conversion). Understanding expression composition is the dividing line between “knowing how to use the aggregation pipeline” and “mastering the aggregation pipeline”—the former knows only individual operators, while the latter can combine them to build any data pipeline.

1. What You'll Learn


100%
graph LR
    A[Document] -->|$cond<br/>Sanyuan| B[Conditional Projection]
    A -->|$switch<br/>Multi-branch| C[Category Tags]
    A -->|$ifNull<br/>Handling Null Values| D[Default Value Replacement]
    A -->|$dateToString<br/>Date Format| E[Date Strings]
    A -->|$toInt/$toDecimal<br/>Type Conversion| F[Type Conversion]

    style B fill:#d4edda
    style C fill:#d4edda

2. Conditional Expressions

Concept Explanation: A conditional expression serves as the "logical control flow" in an aggregation pipeline, allowing each value in a document to be dynamically calculated based on conditions. $cond is a ternary expression (if-then-else), $switch is a multi-branch match (similar to switch-case), and $ifNull is a null value replacement (providing a default value). They are widely used in $project, $addFields, and $group.

Decision Tree for Selecting Conditional Expressions: Which conditional expression to choose depends on the scenario— 1. Only need to handle null → $ifNull (simplest, e.g., {$ifNull: ['$nickname', '$username']}); 2. Need an if-else choice → $cond (e.g., {$cond: [{$gte: ['$age', 18]}, 'adult', 'minor']}); 3. Need 3 or more branches → $switch (e.g., tiering by spending amount: VIP/Silver/Bronze); 4. Need to "return a default value if not found" → $switch + default (similar to SQL’s CASE WHEN ... ELSE). Decision principle: Use $ifNull instead of $cond whenever possible; use $cond instead of $switch whenever possible—prioritize simplicity and introduce complexity only when necessary.

How It Works: $cond evaluates an if condition for each document and returns either the then or else value. Nested $cond can be used to implement multi-level conditions, but this results in poor readability—in such cases, using $switch provides greater clarity. $ifNull Checks whether a field is null or undefined; if so, it replaces it with a fallback value. All conditional expressions are executed on a per-document basis and do not affect other documents.

100%
graph TB
    A[Conditional Expression] --> B[$cond<br/>Sanyuan if-else]
    A --> C[$switch<br/>Multi-branch matching]
    A --> D[$ifNull<br/>Replacing Null Values]
    
    B --> E["price >= 1000 → 'expensive'<br/>else price >= 100 → 'medium'<br/>else → 'cheap'"]
    C --> F["status = 'paid' → 'Paid'<br/>status = 'shipped' → 'Shipped'<br/>default → 'Unknown'"]
    D --> G["nickname is null<br/>→ Replace with username"]
    
    style B fill:#d4edda
    style C fill:#cce5ff
    style D fill:#fff3cd
Expression Syntax Use Case Readability
$cond {if, then, else} 2 Branch Conditions Medium
Nested $cond Nested within "else" $cond 3+ branches Poor
$switch {branches, default} 3+ branches Good
$ifNull { $ifNull: [expr, default] } Handling of null values Good

Computational Model of Conditional Expressions: Conditional expressions ($cond/$switch/$ifNull) are evaluated on a per-document basis within the aggregation pipeline—each document calculates its own conditional result independently, and different documents do not affect one another. This means that $cond does not short-circuit the calculations of other documents, and there is no cross-document state sharing. Understanding this helps avoid common errors: it is not possible to reference fields from other documents within a $cond; you must first use $lookup to establish a relationship before performing the calculation.

Performance Comparison of Conditional Expressions: Performance differences among the three conditional expressions—1. $ifNull: Fastest (checks only for null/undefined; single comparison); 2. $cond: Moderate (ternary condition; each if/then/else branch executes the expression once); 3. $switch: Slowest (evaluates each branch’s case sequentially until a match is found). The performance difference is negligible at the single-document level (in the microseconds), but the cumulative effect is significant in aggregation pipelines handling millions of documents. Optimization recommendations: 1. Use $ifNull instead of $cond for simple null replacements ({ifNull: ['$field', default]} vs {cond: [{eq: ['$field', null]}, default, '$field']}); 2. Sort the branches of $switch by hit probability (place the most frequently hit case first to reduce the average number of evaluations); 3. When $cond is nested more than 3 levels deep, switch to using $switch (which offers better readability and performance).

Internal Working Principles of the $group Accumulator: The $group accumulator ($sum/$avg/$min/$max/$push/$addToSet) maintains a state variable in memory, which is updated each time a document is processed. $sum starts at 0 and adds the field value for each document; $avg maintains two variables—sum and count—and calculates the average at the end; $push appends to an array; $addToSet appends unique values. Understanding how accumulators work helps in designing efficient $group operations—avoid using $push within $group to collect large arrays (which consumes a lot of memory); instead, use $sum for counting or apply $project first to reduce the number of fields.

Aggregation vs. MapReduce: In its early days, MongoDB used MapReduce for complex data analysis; the aggregation pipeline is a more modern alternative. Advantages of the aggregation pipeline: 1. Declarative—you only need to describe “what to do” rather than “how to do it,” and the optimizer automatically selects the execution plan; 2. Pipeline-based—stages are chained together, with each stage focusing solely on its own logic; 3. Performance—Aggregation pipelines are implemented in C++, while MapReduce is interpreted and executed in JavaScript, resulting in a performance difference of 10 to 100 times. MongoDB 5.0 has deprecated MapReduce; the aggregation pipeline is the only recommended analytics tool.

Advanced Learning Path for Aggregation Pipelines: The advanced Aggregation Pipeline covers five major categories of operations: conditional expressions ($cond/$switch/$ifNull), date operations ($year/$dateToString), type conversions ($toString/$toInt/$toDecimal), string operations ($concat/$substr/$toUpper), and array operations ($arrayElemAt/$size/$map/$filter). These five categories are not isolated; they are interwoven in actual pipelines—$group for aggregation → $switch for conditional branching → $dateToString for formatting → output. Mastering these advanced operations is the foundation for building complex reports.

Decision Trees for Conditional Expressions: Deciding which conditional expression to use—1. Just checking for null values? → $ifNull (most concise, specifically designed for this purpose); 2. Only two branches? → $cond (object syntax {if, then, else} is more readable; array syntax [condition, true value, false value] is more compact); 3. Three or more branches? → $switch (matches branches one by one using an array, with a default fallback; readability is far superior to nested $cond); 4. Need to check for multiple null values at once? → Chain $ifNull calls ($ifNull: [$a, $ifNull: [$b, 'default']]) or use $switch + $eq: [null, '$field']. Choosing the right expression greatly improves pipeline readability.

Performance Comparison of Conditional Expressions: The performance of the three conditional expressions, from fastest to slowest, is: $ifNull > $cond > $switch. $ifNull is the fastest because it checks only one condition (null or undefined); $cond is slightly slower because it requires evaluating an if expression; $switch is the slowest because it must evaluate each case in the branches array one by one. However, the performance difference is typically less than 1 ms per thousand documents, and readability is more important than performance—a nested $cond with 100 branches is harder to maintain than a $switch, and in this case, the readability benefits of $switch far outweigh the performance cost.

(1) The $cond ternary operator

Guide to Choosing Conditional Expressions: Criteria for selecting among the three types of conditional expressions—$cond is suitable for two-branch conditions (e.g., "price >= 1000 → expensive/cheap"); its syntax is concise but nesting makes it hard to read; While nested $cond statements can theoretically support multiple branches, they become difficult to maintain beyond three levels; $switch is the best choice for 3+ branches (e.g., mapping order status to Chinese labels), as the branches array is clear and easy to read, and default handles unmatched cases; $ifNull is specifically designed to handle null value substitutions (e.g., using username when nickname is empty) and is an essential tool for defensive programming.

JAVASCRIPT
// === Similar if-else ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      priceLevel: {
        $cond: {
          if: { $gte: ['$price', 1000] },
          then: 'expensive',
          else: {
            $cond: {
              if: { $gte: ['$price', 100] },
              then: 'medium',
              else: 'cheap'
            }
          }
        }
      }
    }
  }
]);

(2) $ifNull: Handling Null Values

Common Use Cases for $ifNull: There are three typical scenarios for using $ifNull in data analysis—1. Default value substitution: $ifNull: ['$nickname', '$username'] Use the username when the display name does not exist; 2. Calculation protection: $ifNull: ['$discount', 0] Treat a discount of null as 0 (to prevent null from being included in arithmetic operations and resulting in a null result); 3. Marking missing data: $ifNull: ['$lastLoginAt', 'never'] Mark users who have never logged in as 'never'. The common thread among these scenarios is that null acts as a "black hole" for data—any operation involving null returns null, and $ifNull is the only "escape route."

Null Propagation Issue: Null propagation in MongoDB aggregation pipelines is implicit and dangerous—if any expression has a null input, the entire expression returns null. For example: $add: ['$price', '$tax']. If tax is null, the result is null rather than price + 0. This is not a bug but standard SQL behavior (null + anything = null), yet in data analysis, it often results in an entire column of data becoming null. Defense strategies: 1. Use $ifNull in the $project/$addFields stage to handle all fields that may be null; 2. Set default values when inserting data (e.g., tax: {type: Number, default: 0}); 3. Use $convert with onNull to uniformly handle null values during type conversion.

Troubleshooting Null Propagation: Steps to troubleshoot unexpected null values in aggregation pipeline outputs—1. Check stage by stage: Add only one $addFields at a time to see at which stage the null value first appears; 2. Check source fields: Use $project to output suspicious fields separately (e.g., {tax: 1, price: 1}) to confirm whether the original value is null; 3. Check the calculation chain: In a chained calculation such as $add$multiply$divide, if any step is null, it will propagate to all subsequent steps; 4. Defensive coding practice: Wrap all arithmetic expressions in $ifNull—for example, $add: [{$ifNull: ['$price', 0]}, {$ifNull: ['$tax', 0]}]. Once this becomes a habit, null propagation issues will rarely occur.

JAVASCRIPT
// === Replace null/undefined ===
db.users.aggregate([
  {
    $project: {
      name: 1,
      displayName: {
        $ifNull: ['$nickname', '$username']  // nickname Use when empty username
      }
    }
  }
]);

(3) $switch Multi-Conditional Branch

$switch Branch Order and Performance: $switch evaluates branches in the order they are declared, and the first branch that matches returns the result—therefore, you should place the branch most likely to match first to reduce unnecessary condition evaluations. Compared to nested $cond statements, $switch offers significantly better readability—when there are three or more branches, nested $cond statements result in excessively deep indentation, whereas the flat structure of $switch is immediately clear. The default branch in $switch cannot be omitted—if no branch matches and there is no default, MongoDB will throw an error.

Applications of $switch in Data Cleaning: $switch has three typical uses in ETL data cleaning—1. Enumeration mapping: Converting internal database codes into readable labels (status: 'A' → 'Active', 'I' → 'Inactive', 'D' → 'Deleted'); 2. Bucketing: Discretizing continuous values (amount < 100 → 'Small', 100–1000 → 'Medium', > 1000 → 'Large'; similar to the $bucket function but more flexible, allowing for custom boundaries and labels); 3. Multi-condition composite scoring: Assign a rating based on a combination of multiple fields (VIP criteria: spending > 10,000 and registered for > 1 year; Gold criteria: spending > 1,000 or registered for > 3 years; otherwise, Bronze). The flexibility of $switch makes it a versatile tool for data transformation.

Applications of Conditional Expressions in ETL: Conditional expressions are a core tool for data cleansing in ETL—1. Data classification: $switch converts order statuses to business labels (pending → pending payment, paid → paid, shipped → shipped); 2. Exception handling: $ifNull replaces missing fields with default values, and $cond marks abnormal values as "requires manual review"; 3. Data Masking: $cond replaces the middle four digits of a phone number with ** ({$concat: [{$substr: ['$phone', 0, 3]}, '**', {$substr: ['$phone', 7, 4]}]}); 4. Business Rules: $switch maps the user’s spending amount to a tier label. In ETL pipelines, conditional expressions are typically placed in the $addFields stage.

Deciding Which Conditional Expression to Use: Choosing Between $cond, $switch, and $ifNull — 1. Use $cond for a two-way choice: when there are only two branches (true or false) (e.g., isVIP: {$cond: [{$gte: ['$spend', 10000]}, true, false]}); 2. Use $switch for multiple branches: 3 or more conditional branches (e.g., tier determination, status mapping); 3. Use $ifNull for null value handling: only needs to handle null/undefined cases (e.g., {$ifNull: ['$nickname', '$username']}); 4. Use $switch for nested conditions: Avoid nesting $cond (more than three levels of nesting severely compromises readability); instead, use the flat structure of $switch. Rule of thumb—use $cond for two branches, $switch for three or more branches, and $ifNull for null handling.

JAVASCRIPT
// === Similar switch-case ===
db.orders.aggregate([
  {
    $project: {
      orderId: '$_id',
      statusLabel: {
        $switch: {
          branches: [
            { case: { $eq: ['$status', 'pending'] }, then: 'Payable' },
            { case: { $eq: ['$status', 'paid'] }, then: 'Paid' },
            { case: { $eq: ['$status', 'shipped'] }, then: 'Shipped' },
            { case: { $eq: ['$status', 'delivered'] }, then: 'Delivered' }
          ],
          default: 'Unknown Status'
        }
      }
    }
  }
]);

3. Date Operations

Concept Explanation: Date operations form the foundation of time-series data analysis. MongoDB provides three types of date operators: (1) Extraction operators ($year/$month/$dayOfMonth/$hour, etc.), which extract time components from a Date field; (2) Formatting operators ($dateToString), which convert a Date field into a string in a specified format; (3) Arithmetic operators ($add/$subtract), which perform addition and subtraction operations on dates.

How It Works: The extraction operator reads the corresponding components directly from the BSON Date type and supports the timezone parameter for handling time zones. $dateToString outputs a string using format specifiers similar to strftime. Date operations are based on millisecond timestamps: $add adds a number of milliseconds, and $subtract calculates the time difference, then divides it by a constant to convert it to days or hours.

Choosing a Time Zone Strategy: There are two strategies for handling time zones in date operations—1. Uniform UTC at the storage layer (recommended): All dates are stored in UTC and converted to local time using the timezone parameter during queries; 2. Time zone included at the storage layer: Each document records a timezone field, which is referenced during queries. Advantages of Strategy 1: Query conditions do not need to account for time zone conversions, and date comparisons are straightforward and accurate; cross-time-zone queries do not require complex time zone conversions. Strategy 2 is suitable for auditing scenarios that require precise recording of “when and in which time zone a user performed an operation.”

Date Index Optimization: Queries by date range (such as "orders from the last 7 days") are the most common time-series queries. Key optimization points: 1. The createdAt field is indexed by default (timestamps: true + default index); 2. Use $gte and $lt for date range queries instead of $where or aggregations; 3. The composite index {status: 1, createdAt: -1} covers both “filtering by status” and “sorting by date”; 4. $dateToString does not use the index—it converts Date to a string before comparing it. You should first use $match to check the date range, then use $dateToString for formatting.

Common Patterns for Date-Grouped Statistics: Date grouping is at the heart of time-series analysis—statistics grouped by day, week, month, quarter, or year. Designing grouping keys: 1. Grouping by month: {_id: {year: {$year: '$date'}, month: {$month: '$date'}}}; 2. Grouping by week: {_id: {year: {$year: '$date'}, week: {$week: '$date'}}}; 3. Grouping by day: $dateToString: {format: '%Y-%m-%d', date: '$date'} as the _id. Note: The $dateToString method does not use indexes, but its syntax is the most concise. For monthly and weekly grouping, extraction functions can utilize composite indexes to filter data before grouping.

Techniques for Filling in Missing Dates: Statistical results grouped by day may be missing certain dates (e.g., if there were no orders on a particular day, the results will not include an entry for that date), which can cause breaks in a continuous line chart rendered by the front end—1. Application-layer filling: Use Node.js to iterate through the date range and fill in missing dates with a value of 0 (most common; concise code); 2. $densify stage (MongoDB 6.1+): {$densify: {field: 'date', range: {step: 1, unit: 'day', bounds: 'full'}}} automatically fills in missing dates (pure pipeline solution, no application-layer processing required); 3. Auxiliary collection: Create a calendar collection to pre-store all dates, then use $lookup to populate missing dates via a join (compatible with older versions but has high maintenance costs). Option 2 is the most elegant but requires MongoDB 6.1 or later; Option 1 is the most versatile.

Best Practices for Date Range Queries: The correct way to query "the last N days"—1. Use $gte + new Date(Date.now() - N86400000) (calculated in JavaScript, pass the Date object to MongoDB); 2. Use $gte + ISODate() (Mongo shell syntax); 3. Use $match + $expr + $gte: ['$createdAt', {$subtract: ['$$NOW', N86400000]}] in the aggregation pipeline (pure pipeline expression; $$NOW refers to the server’s current time). Option 3 is the most flexible—it does not rely on application-level time calculations and is self-contained within the pipeline.

100%
graph LR
    A[Date Field<br/>2026-07-01T10:30:00Z] --> B[$year → 2026]
    A --> C[$month → 7]
    A --> D[$dayOfMonth → 1]
    A --> E[$hour → 10]
    
    A --> F["$dateToString<br/>%Y-%m-%d → '2026-07-01'"]
    
    A --> G["$add[date, 7*86400000]<br/>→ 2026-07-08"]
    A --> H["$subtract[now, date]<br/>→ Difference in the number of days"]
    
    style B fill:#d4edda
    style F fill:#cce5ff
    style G fill:#fff3cd

Accuracy Issues with Date Calculations: $subtract When calculating the difference between dates, the result is returned in milliseconds. Dividing this value by 86,400,000 to convert it to days can lead to accuracy issues—daylight saving time changes can cause a day to be less than exactly 24 hours. For precise calculations, use $dateToString to extract the date portion and compare it, or use the difference in $dayOfYear for an approximation. In most business scenarios, millisecond precision is sufficient, but special attention is required for financial and time-and-attendance systems.

Common Errors in Date Manipulation: 1. Time zone confusion—storing data in UTC but forgetting to convert it when displaying local time; 2. Months start at 1, but JavaScript Date starts at 0 (MongoDB’s $month returns 1–12, which differs from JavaScript); 3. $dayOfWeek returns 1 for Sunday (U.S. convention; Chinese users may expect 1 to be Monday); 4. $week returns the week number within the year, but different countries have different definitions of which day a week starts on (ISO 8601 specifies Monday as the start of the week, while MongoDB defaults to Sunday); 5. $add, when adding milliseconds, does not account for leap seconds (this has no impact on the vast majority of applications).

Performance Optimization for Date Operations: Date operations do not use indexes in the aggregation pipeline—extraction functions such as $year and $month first convert the Date to a numeric value before performing comparisons, so they cannot utilize the B-tree index on the Date field. Therefore: 1. For queries by date range, use $gte/$lt (which compares directly on the Date field and uses the index) instead of $month === 7; 2. When grouping by month, filter using {$gte: startOfMonth, $lt: startOfNextMonth} before calling $group; 3. Use $dateToString only during the final output stage (for formatting), not during the filtering stage.

Summary of Best Practices for Dates: The Golden Rules for Date Operations in MongoDB—1. Always store dates in UTC (to avoid time zone confusion; convert to the desired time zone at the application layer or using the timezone parameter in $dateToString when displaying); 2. Always use $gte/$lt for queries (to utilize indexes; avoid filtering with extraction functions like $month or $year); 3. Use $dateToString or $dateFromParts to construct group keys (e.g., "$dateToString: {format: '%Y-%m', date: '$createdAt'}" to group by month); 4. Perform formatting only at the output layer (as the final step in $project/$addFields); 5. Specify the time zone in the timezone parameter of $dateToString (IANA format, e.g., 'Asia/Shanghai'); do not manually offset the hour value at the application layer (daylight saving time will cause the offset to change).

(1) Date Extraction

Time Zone Handling Strategy: MongoDB stores dates as UTC timestamps (without time zone information), and date extraction operators return UTC values by default. For scenarios requiring local time: 1. The timezone parameter of $dateToString (e.g., 'Asia/Tokyo') can directly output a local time string; 2. Extraction operators such as $hour and $month also support the timezone parameter; 3. Performing time zone conversion at the application layer offers greater flexibility but increases the amount of code. Production recommendation: Store data uniformly in UTC, and perform the conversion using $dateToString or at the application layer when displaying it.

Common Pitfalls in Time Zone Handling: Time zones are the most error-prone area in aggregation pipelines—1. Daylight Saving Time (DST) Pitfall: The U.S. and Europe observe DST, so the UTC offset for the same city varies by month (New York is UTC-5 during standard time and UTC-4 during daylight saving time). MongoDB’s timezone parameter automatically handles DST, but manual offset calculations can lead to errors; 2. Cross-Day Grouping Pitfall: 02:00 on June 1, 2026 (UTC+8) corresponds to 18:00 on May 31, 2026 (UTC). Using $dayOfMonth without specifying a time zone will group the date as the 31st rather than the 1st; 3. Leap second/leap year pitfalls: When adding a month with $dateAdd, January 31 + 1 month = February 28 (not March 3); MongoDB handles this automatically; 4. Time zone database updates: MongoDB includes a built-in IANA time zone database, but older versions may lack the latest time zone rules, so you need to upgrade MongoDB regularly.

Best Practices for Date Operations: 1. Use $gte/$lt for date range queries instead of extraction functions like $dayOfMonth (the former can use indexes, while the latter cannot); 2. When grouping by month, use {_id: {year: {$year: '$date'}, month: {$month: '$date'}}} instead of $dateToString (the former can utilize indexes); 3. Date calculations (adding or subtracting days) are more efficient in the aggregation pipeline than at the application layer (to avoid transferring large amounts of date fields).

JAVASCRIPT
// === $year / $month / $dayOfWeek / $hour ===
db.orders.aggregate([
  {
    $project: {
      year: { $year: '$createdAt' },
      month: { $month: '$createdAt' },
      day: { $dayOfMonth: '$createdAt' },
      weekday: { $dayOfWeek: '$createdAt' },  // 1=Sunday
      hour: { $hour: '$createdAt' }
    }
  }
]);

(2) Date Formatting

Quick Reference for $dateToString Format Specifiers: $dateToString uses strftime-style format specifiers—%Y (four-digit year), %m (two-digit month), %d (two-digit day), %H (24-hour hour), %M (minutes), %S (seconds), %L (milliseconds), %j (day of the year), %U (week of the year). Common combinations: '%Y-%m-%d' (date key), '%Y-%m' (month key), '%Y-W%U' (week key), '%Y-%m-%d %H:00' (hour key). The timezone parameter accepts the Olsen format (e.g., 'Asia/Shanghai') or a UTC offset (e.g., '+08:00').

Typical Applications of Date Formatting: The most common use of date formatting is for grouping statistics by time—using $dateToString to generate date keys, then using $group to aggregate by those keys. For example: tracking sales trends by month ($dateToString$group$sort), identifying peak traffic by hour, and measuring user retention by week. Key Optimization: If you only need to group by month, using {year: {$year}, month: {$month}} is more efficient than $dateToString (the former can utilize indexes, while the latter converts the date to a string and cannot use indexes).

JAVASCRIPT
// === $dateToString Date Format ===
db.orders.aggregate([
  {
    $project: {
      orderDate: {
        $dateToString: {
          format: '%Y-%m-%d %H:%M:%S',
          date: '$createdAt',
          timezone: 'Asia/Tokyo'
        }
      }
    }
  }
]);
// { orderDate: '2026-07-01 10:30:00' }
Format Specifier Meaning
%Y 4-digit year
%m 2-digit month
%d 2-digit date
%H 24-hour clock
%M Minutes
%S Seconds

(3) Date Operations

Business Scenarios for Date Calculations: Date calculations are extremely common in e-commerce and SaaS systems—1. Order expiration checks: $add[createdAt, 3086400000] calculates the payment deadline; orders are automatically canceled if this date is exceeded; 2. User activity analysis: $subtract[now, lastLoginAt] calculates the number of days since the last login; users with more than 30 days since their last login are marked as inactive; 3. Subscription renewal reminders: $subtract[expiryDate, now] calculates the number of days remaining; if <7 days, a reminder is sent; 4. Reporting time windows: $match {createdAt: {$gte: $add[now, -3086400000]}} retrieves data from the last 30 days.

Time Unit Conversion for Date Operations: The underlying unit for date operations in MongoDB is the millisecond—1 day = 86,400,000 milliseconds, 1 hour = 3,600,000 milliseconds. Common conversion errors: 1. Forgetting to multiply by 1,000 (using 86,400 instead of 86,400,000), resulting in a 1,000-fold difference; 2. Using 30*86,400,000 to represent “30 days” is an approximation (since the number of days varies by month); for precise date calculations, use $dateAdd (MongoDB 5.0+) instead of $add; 3. Time zone issue: Operators such as $hour and $dayOfMonth default to UTC; to use local time, add the timezone parameter (e.g., {$hour: {date: '$createdAt', timezone: 'Asia/Shanghai'}}). Time calculations are a common source of bugs—be sure to validate critical date logic with unit tests.

JAVASCRIPT
// === $add / $subtract Date Addition and Subtraction ===
db.orders.aggregate([
  {
    $project: {
      createdAt: 1,
      expiryDate: { $add: ['$createdAt', 7 * 24 * 60 * 60 * 1000] },  // Add 7 days
      daysSinceCreated: {
        $divide: [
          { $subtract: [new Date(), '$createdAt'] },
          1000 * 60 * 60 * 24
        ]
      }
    }
  }
]);

4. Type Conversion

Concept Explanation: MongoDB is a weakly typed database; fields within the same collection may have different data types (for example, price could be a String or a Number). The aggregation pipeline provides conversion operators such as $toString/$toInt/$toLong/$toDouble/$toDecimal/$toDate/$toBool to resolve data type inconsistencies. $convert offers a safer conversion method (allowing you to specify onError handling).

How It Works: The type conversion operator performs conversions on the field values of each document. $toInt('42') → 42, $toString(3.14) → '3.14'. If the conversion fails, $toXxx returns null and generates a warning; $convert can be used to specify that onError returns a custom value. Use conversions in $project or $addFields to ensure data type consistency in subsequent stages.

Challenges of Weakly Typed Systems: MongoDB’s weak typing is a double-edged sword—it offers flexibility (no need to predefine types) but also poses risks (the same field may contain different data types). Typical issues: 1. When migrating legacy data, price might be the string "599" or the number 599; 2. When $avg in $group encounters a string field, it returns null instead of throwing an error; 3. Sorting results with mixed types in $sort are unpredictable (order is determined by BSON type comparison). Solution: Use $convert in the first step of the aggregation pipeline to standardize types, or strictly enforce type constraints at the Mongoose schema level.

$convert's Safe Conversion Mode: $convert is safer than $toXxx—it supports the onError and onNull parameters, allowing you to return a custom default value instead of null if the conversion fails. Recommended pattern: $convert({input: '$price', to: 'decimal', onError: NumberDecimal('0'), onNull: NumberDecimal('0')}). This way, even if price is a non-numeric string or null, the aggregation pipeline can continue executing without causing null propagation issues. Aggregation pipelines in production should always use $convert instead of $toXxx.

Summary of Best Practices for Type Conversion: 1. Always use $convert + onError instead of the bare $toXxx (for error handling); 2. Standardize types at the very beginning of the pipeline ($addFields + $convert) to ensure data types remain consistent throughout subsequent stages; 3. Use $type to check field types and apply different conversion logic for different types ($switch + $type); 4. Use Decimal128 for currency calculations, Double for scientific calculations, and Int32 for counting; 5. Convert string dates to Date using $toDate (ISO 8601 format); for non-standard formats, use $dateFromString first or perform preprocessing at the application layer.

Tips for Debugging Type Conversions: Debugging type errors in aggregation pipelines is the most time-consuming task—1. Use the $type operator to check field types: $addFields: {priceType: {$type: '$price'}} to see what type the price field is in each document; 2. Use $cond + $type for conditional conversions: $switch: {branches: [{case: {$eq: [{$type: '$price'}, 'string']}, then: {$toDecimal: '$price'}}, {case: {$eq: [{$type: '$price'}, 'double']}, then: {$toDecimal: '$price'}}], default: '$price'}; 3. View the output step by step in Compass to locate where the type mismatch occurs. Prevention is better than cure—strictly enforce type constraints in the Mongoose schema to avoid weak typing issues at the source.

Conversion Operator Input → Output Typical Scenarios
$toString Any → String Convert numeric values to strings for display
$toInt String/Number→Int32 Convert string price to integer
$toLong String/Number→Int64 Large Number ID Conversion
$toDouble String/Number→Double Exact calculation
$toDecimal String/Number→Decimal128 Precise currency calculations
$toDate String/Number→Date String date to Date
$convert onError can be specified Safe conversion (recommended)

Safety Guidelines for Type Conversion: MongoDB’s weakly typed nature means that a single field may contain multiple types—for example, price could be a String, a Number, or even a Decimal128. Type conversion operators resolve this inconsistency, but the following safety guidelines should be observed: 1. Strict conversions such as $toInt and $toDouble will throw an error immediately upon encountering an incompatible value; 2. $convert combined with onError and onNull provides fault-tolerant conversion (recommended); 3. Use $type to check the field type before conversion to avoid blind conversions; 4. Data cleansing should be completed during the ETL phase; conversions in the aggregation pipeline should serve as a last resort.

$convert vs $toXxx Comparison:

Dimension $toXxx $convert
Syntax Simple Somewhat complex
Error Handling Error Interrupt onError Returns a Custom Value
Handling Null Values Return null onNull returns a custom value
Recommended Scenarios Data Confirmed to Be Clean Fault Tolerance in Production Environments

Common Pitfalls in Type Conversion: 1. $toInt('3.14') throws an error—you must first use $toDouble, then $toInt; 2. $toDate('2026-07-01') succeeds, but $toDate('07/01/2026') fails—MongoDB only recognizes the ISO 8601 format; 3. $toBool('false') returns true—non-empty strings are truthy; 4. $toDouble(null) returns null instead of 0—null propagation in subsequent arithmetic operations causes the entire expression to evaluate to null. General strategy to avoid these pitfalls: First use $ifNull to handle null values, then use $convert to handle type conversions.

JAVASCRIPT
// === Numeric Type Conversion ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      price: 1,
      priceString: { $toString: '$price' },           // Decimal128 → string
      priceInt: { $toInt: '$price' },                  // → Int32
      priceLong: { $toLong: '$price' },                // → Int64
      priceDouble: { $toDouble: '$price' },            // → Double
      priceDecimal: { $toDecimal: '$price' }           // → Decimal128
    }
  }
]);

// === Date Conversion ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      releaseDate: { $toDate: '$releaseDateStr' }     // string → Date
    }
  }
]);

5. String Operations

Performance Considerations for String Operations: String operations in the aggregation pipeline are executed on a per-document basis, which can become a bottleneck when processing large datasets. Key points to note: 1. $substr truncates by bytes; UTF-8 multibyte characters, such as Chinese characters, may be truncated—be sure to use $substrCP to truncate by characters; 2. When concatenating a large number of fields with $concat, be mindful of the resulting length (BSON Strings are limited to 16MB); 3. $regexMatch can be used with $filter to implement fuzzy matching filters; 4. String operations should be performed after $match (filter first to reduce the processing load).

Common Pitfalls in String Manipulation: 1. $concat and null: $concat returns null if any parameter is null—wrap each parameter in $ifNull ({$concat: [{$ifNull: ['$firstName', '']}, ' ', {$ifNull: ['$lastName', '']}]}); 2. $toLower/$toUpper and Multilingual Support: These operators only handle ASCII characters; Chinese, Japanese, and Korean are unaffected (since they have no case distinction), but the conversion of the German character ß to SS is not supported; 3. $split and empty strings: $split: ['', ','] returns an empty array [] (rather than ['']), which differs from the behavior of JavaScript’s ''.split(','); 4. $replaceOne vs. $replaceAll: MongoDB 4.4+ uses $replaceOne (replaces the first match) and $replaceAll (replaces all matches). Note that these operators are not available in older versions.

The Key Difference Between $substr and $substrCP: $substr extracts text based on byte offsets, while $substrCP extracts text based on character offsets—the two are equivalent for pure ASCII text, but since CJK characters, emojis, and accented letters are multibyte characters, $substr may truncate half a character and result in garbled text. For example: For "café," using $substr: ['$text', 0, 4] to extract the first 4 bytes results in garbled text (since "é" occupies 2 bytes in UTF-8, $substr would split it in half); using $substrCP: ['$text', 0, 4] to extract the first 4 characters correctly yields "café." Rule: If a field may contain non-ASCII characters, always use $substrCP.

How to Use $regexMatch: $regexMatch performs regular expression matching in the aggregation pipeline—it returns a boolean value and is often used in combination with $filter to filter array elements. For example: Filter tags in an array that start with "mongo"—$filter: {input: '$tags', cond: {$regexMatch: {input: '$$this', regex: '^mongo'}}}}. Note: $regexMatch does not use indexes—it matches each value individually, and performance scales linearly with the amount of data. For regular expression searches on large datasets, use $match (which can use indexes) first, followed by $regexMatch.

Handling NULL Values in $concat: When $concat encounters a NULL input, the entire expression returns NULL—this is not a bug, but rather the standard NULL propagation behavior in SQL. For example: $concat: ['$firstName', ' ', '$lastName']. If lastName is NULL, the entire result is NULL rather than "John NULL". Solution: 1. Wrap each field that might be null in $ifNull ($concat: [$ifNull: ['$firstName', ''], ' ', $ifNull: ['$lastName', '']]); 2. Replace $concat’s implicit conversion with $convert + onError; 3. Ensure string fields have a default value (default: '') when inserting data into the database. Null propagation is the most common cause of "unexpected null results" in string operations.

Combining $split and $arrayElemAt: $split splits a string into an array based on a delimiter, and $arrayElemAt retrieves an element by its index—combining the two allows you to "extract a specific part of a string." Typical scenarios: 1. Extracting the domain from an email address—$arrayElemAt: [$split: ['$email', '@'], 1] retrieves the part after the @ symbol; 2. Extracting a name—$arrayElemAt: [$split: ['$fullName', ' '], 0] retrieves the part before the space; 3. Parsing a path — $arrayElemAt: [$split: ['$path', '/'], -1] retrieves the last segment of the path. Note that $split returns null for null inputs (requires $ifNull protection) and returns [''] for an empty string (the array contains a single empty string, not an empty array).

Handling null values with $concat: A critical pitfall of $concat—if any single input is null, the entire result returns null. For example: $concat: ['$firstName', ' ', '$lastName']. If lastName is null, the result is null rather than "John null". Workaround: Wrap every input that might be null in an $ifNull$concat: [$ifNull: ['$firstName', ''], ' ', $ifNull: ['$lastName', '']]. This pattern is extremely common when concatenating fields such as addresses and names.

Combining $split and $arrayElemAt: $split splits a string into an array, and $arrayElemAt retrieves the element at a specified index—combining the two allows you to "extract substrings." Classic examples: 1. Extract the domain from an email address: $arrayElemAt: [{ $split: ['$email', '@'] }, 1] → "gmail.com"; 2. Extract the path from a URL: $arrayElemAt: [{ $split: ['$url', '/'], 3 }]; 3. Extract the last name from a full name: $arrayElemAt: [{ $split: ['$fullName', ' '] }, 0]. $split does not support regular expression delimiters—if the delimiter is not fixed, you must first use $trim to remove extra spaces.

JAVASCRIPT
// === $substr String Truncation ===
db.users.aggregate([
  {
    $project: {
      email: 1,
      emailPrefix: { $substr: ['$email', 0, 5] }  // First 5 characters of email
    }
  }
]);

// === $concat String Concatenation ===
db.users.aggregate([
  {
    $project: {
      fullName: { $concat: ['$firstName', ' ', '$lastName'] }
    }
  }
]);

// === $toUpper / $toLower Uppercase and lowercase ===
db.users.aggregate([
  {
    $project: {
      usernameUpper: { $toUpper: '$username' }
    }
  }
]);

6. Array Operations

The Role of Array Operations in the Aggregation Pipeline: Array operations are the core capability that distinguishes MongoDB’s aggregation pipeline from SQL—SQL’s row-level operations cannot handle nested arrays, whereas $map, $filter, and $reduce make it possible to transform, filter, and aggregate elements within arrays. This stems directly from the nested array nature of MongoDB’s document model. $map is equivalent to applying $project to each element of an array, $filter is equivalent to applying $match to the array, and $reduce is equivalent to applying $group to the array—mastering these three operators means mastering array-level “aggregation capabilities.”

Risks and Workarounds for $unwind: $unwind splits an array into multiple documents, with each document containing one element of the array. Risks: 1. Splitting a large array generates a large number of documents (e.g., a 1,000-element array → 1,000 documents), causing data bloat in the pipeline; 2. After splitting, $group is required to re-aggregate the data, increasing complexity; 3. By default, empty arrays result in the entire document being discarded (requires preserveNullAndEmptyArrays: true). Best Practices: Avoid using $unwind if the problem can be solved with $map or $filter; when $unwind is necessary, immediately follow it with $group to reorganize the data.

Correspondence Between $map, $filter, and $reduce and JavaScript Array Methods: The array operators in the aggregation pipeline correspond one-to-one with JavaScript array methods—$map ↔ Array.map() (transform each element), $filter ↔ Array.filter() (filter elements), $reduce ↔ Array.reduce() (accumulative computation), $concatArrays ↔ [...a, ...b] (merge arrays), $reverseArray ↔ Array.reverse() (reverse), $arrayElemAt ↔ Array[index] (retrieve value by index), $size ↔ Array.length (array length). This correspondence allows front-end developers to quickly get up to speed with the aggregation pipeline’s array operations.

Common Pitfalls in Array Operations: There are several common pitfalls in array operations—1. The cond parameter of $filter must return a Boolean value (expressions that return null or undefined will not filter the element; instead, it will be retained); 2. The initialValue parameter of $reduce must be specified (if not specified, the first element of the array is used as the initial value, but if the array is empty, null is returned); 3. Negative indices in $arrayElemAt are counted from the end (–1 is the last element, –2 is the second-to-last), but if the index is out of range, null is returned; 4. $size throws an error for null or undefined fields (requires protection with $ifNull: ['$array', []]); 5. The as parameter of $map defaults to 'this', but using a custom name improves readability ($map: {input: '$tags', as: 'tag', in: {$toUpper: '$$tag'}}).

Scope of $$this and $$value: $map/$filter/$reduce use $$this to refer to the current element in the array being iterated over, while $reduce uses $$value to refer to the accumulated value. Note the double dollar sign—$$ denotes system variables ($$this, $$value, $$ROOT, $$DESCEND), while a single $ denotes a field reference. Common mistake: Using $field instead of $$this.field in the cond of $filter—$field refers to the top-level field of the document, while $$this.field refers to the field of the current array element. Understanding these scope differences is key to using array operators correctly.

Operator Input Output Change in Number of Documents
$map N documents N documents Immutable (internal array transformation)
$filter N documents N documents Unchanged (filtering within the array)
$unwind N documents N×M documents Expansion (splitting subarrays)

Combining $map and $filter: $map and $filter are often used together—first use $filter to select the desired array elements, then use $map to transform their format. For example: Extract only the product names of shipped items from an order—$map: {input: {$filter: {input: '$items', cond: {$eq: ['$$this.status', 'shipped']}}}, in: '$$this.name'}. Note the order: apply filter before map (to reduce the number of elements processed by map). The reverse order (applying map before filter) results in poorer performance and lower readability.

$reduce Cumulative Calculation: $reduce reduces an array to a single value — syntax {input: array, initialValue: value, in: expression}. Typical scenarios: 1. Array sum $reduce: {initialValue: 0, in: {$add: ['$$value', '$$this']}}; 2. Array concatenation $reduce: {initialValue: '', in: {$concat: ['$$value', ',', '$$this']}}; 3. Array maximum $reduce: {initialValue: 0, in: {$cond: [{$gt: ['$$this', '$$value']}, '$$this', '$$value']}}. $$value is the accumulated value, $$this is the current element.

100%
graph LR
    A["tags: ['5g','amoled','fast']"] --> B["$arrayElemAt: 0<br/>→ '5g'"]
    A --> C["$size<br/>→ 3"]
    A --> D["$map: {$toUpper}<br/>→ ['5G','AMOLED','FAST']"]
    A --> E["$filter: len >= 3<br/>→ ['amoled','fast']"]
    
    style B fill:#d4edda
    style C fill:#cce5ff
    style D fill:#fff3cd
    style E fill:#e2d5f1
Operator Function Equivalent in JavaScript
$arrayElemAt Retrieve an element by index arr[index]
$size Array Length arr.length
$map Element-by-Element Transformation arr.map(fn)
$filter Condition Filter arr.filter(fn)
$reduce Cumulative Calculation arr.reduce(fn, init)
$concatArrays Merge Arrays [...a, ...b]
$reverseArray Reverse an Array arr.reverse()

Common Pitfalls with $size: $size can only be used with array fields—if the field is not an array (e.g., null or does not exist), $size will throw an error. Workarounds: 1. First, use $ifNull: ['$tags', []] to convert null to an empty array; 2. Use $type: '$tags' to check if it is an array before performing the calculation; 3. Set default: [] in the Schema (to ensure the field is always an array). $size returns an integer and can be used directly in $cond conditions—for example, $cond: [{$gte: [{$size: '$tags'}, 3]}, 'Plenty of tags', 'Not enough tags'].

Negative Indices for $arrayElemAt: $arrayElemAt supports negative indices—-1 retrieves the last element, and -2 retrieves the second-to-last element, which behaves the same as JavaScript’s arr.at(-1). Common uses: 1. Retrieve the most recent record {$arrayElemAt: ['$orders', -1]} (assuming orders are sorted by time); 2. Retrieve the first tag: {$arrayElemAt: ['$tags', 0]}; 3. Retrieve the last address: {$arrayElemAt: ['$addresses', -1]}. If the index is out of range, $arrayElemAt returns null (without throwing an error); you’ll need to use $ifNull as a fallback.

Chaining Array Operations: $map, $filter, and $reduce can be chained together—first use $filter to filter, then use $map to transform, and finally use $reduce to aggregate. For example: Calculating the total price of shipped items—$reduce: {input: {$map: {input: {$filter: {input: '$items', cond: {$eq: ['$$this.status', 'shipped']}}}, in: '$$this.price'}}, initialValue: 0, in: {$add: ['$$value', '$$this']}}. Note the execution order: first filter (to reduce the number of elements processed by map), then map (to extract the price), and finally reduce (to sum the values).

Performance Optimization for Chained Operations: While chained array operations are powerful, each level of nesting increases the computational load—1. Optimize the order: First use $filter (to reduce the data volume for subsequent operations), then $map (to extract only necessary fields), and finally $reduce (to calculate the final result); 2. Avoid redundant calculations: If multiple chained operations require the same intermediate result, use $addFields to calculate it once and then reuse it; 3. Note on large arrays: Performing $map + $filter + $reduce on arrays with 1,000+ elements may take a long time; consider using $unwind + $group as an alternative first ($unwind processes large arrays faster at the database layer than $map does at the expression layer); 4. Prepend $match to the pipeline: Apply array operations only to the required documents (e.g., process only the items array of paid orders).

Three Accumulation Modes of $reduce: The in parameter of $reduce determines the accumulation method—1. Numeric accumulation: in: {$add: ['$$value', '$$this']} (sum), in: {$max: ['$$value', '$$this']} (maximum); 2. String accumulation: in: {$concat: ['$$value', ',', '$$this']} (comma-separated concatenation); 3. Object accumulation: in: {$mergeObjects: ['$$value', '$$this']} (merge object properties). The initialValue parameter of $reduce determines the type of the initial value and the final output type—a numeric initial value of 0 → outputs a Number, a string initial value of '' → outputs a String, and an object initial value of {} → outputs an Object. The type of the initial value must match the output type of the in parameter.

JAVASCRIPT
// === $arrayElemAt Retrieve an Element by Index ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      firstTag: { $arrayElemAt: ['$tags', 0] },
      lastTag: { $arrayElemAt: ['$tags', -1] }
    }
  }
]);

// === $size Array Length ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      tagCount: { $size: '$tags' }
    }
  }
]);

// === $map Array Transformations ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      tagsUpper: {
        $map: {
          input: '$tags',
          as: 'tag',
          in: { $toUpper: '$$tag' }
        }
      }
    }
  }
]);

// === $filter Array Filtering ===
db.products.aggregate([
  {
    $project: {
      title: 1,
      expensiveTags: {
        $filter: {
          input: '$relatedProducts',
          as: 'product',
          cond: { $gte: ['$$product.price', 1000] }
        }
      }
    }
  }
]);

7. Comprehensive Practical Training

Aggregation Pipeline Performance Optimization Checklist: Aggregation pipelines in production environments require systematic optimization—1. Place $match at the beginning to filter data early and reduce the data volume; 2. Use $project after $group to streamline fields and reduce memory usage; 3. Use $sort + $limit instead of full sorting (Top N mode); 4. Avoid using complex expressions for $group’s _id (which affects grouping efficiency); 5. Set allowDiskUse: true for large datasets; 6. Use hint() to enforce index usage and avoid full collection scans; 7. Follow $unwind immediately with $group to prevent data bloat; 8. Sub-pipelines of $facet share inputs but operate independently; control the number of sub-pipelines.

Tips for Debugging Aggregation Pipelines: Aggregation pipelines use chained calls, making intermediate results invisible and debugging difficult. Three practical tips: 1. Execute stage by stage—add only one stage at a time and check whether the output matches expectations; 2. Use $project to retain only key fields, reducing output noise to facilitate analysis; 3. Use Compass’s Aggregation Pipeline Builder for visual debugging. If $group results do not match expectations, check whether the _id is correct—the most common errors involve misspelled field names or missing quotes in the _id.

Business Applications of RFM User Segmentation: RFM (Recency/Frequency/Monetary) is the most commonly used user segmentation model in e-commerce—1. Recency (time since last purchase): The lower the value of daysSinceLastOrder, the better (users who have recently made a purchase are more likely to buy again); 2. Frequency (purchase frequency): The higher the value of orderCount, the better (users who purchase frequently are loyal customers); 3. Monetary (Spending Amount): The higher the totalSpent, the better (high-spending users contribute the majority of revenue). Each of the three RFM dimensions is categorized into high and low tiers, resulting in 8 user types—"High R, High F, High M" represents the most valuable VIP users, while "Low R, Low F, Low M" represents churned users who need to be reactivated or written off. $switch maps RFM scores to user tags and serves as the aggregation pipeline for RFM analysis.

Preparation and Validation of Report Data: Test data for sales reports must be carefully designed—1. Cover a sufficiently long time period (at least 3 months; data from the previous month is required for month-over-month comparisons); 2. Include outliers (orders with a value of 0 yuan, refunded orders, to test the robustness of $match filtering); 3. Be reasonably distributed (many small-value orders + few large-value orders, to simulate the distribution of real orders); 4. Feature a variety of statuses (paid/pending/refunded, to test the filtering effectiveness of $match: {status: 'paid'}). After inserting test data in bulk using insertMany, first verify the data’s correctness with a simple find query, then execute the aggregation pipeline—data issues are more common than pipeline issues.

The Business Value of Sales Reports: Monthly sales reports are the core data for e-commerce operations—they answer three key questions: 1. What are the trends (is revenue growing or declining)?; 2. What are the reasons (which category or product is driving or dragging down performance)?; 3. How should we adjust (promotions, inventory, or product selection strategies)? Using $setWindowFields + $shift to calculate year-over-year growth is a crucial step in elevating the report from merely “displaying data” to “providing insights”—knowing that “this month’s revenue is 3,000” is less valuable than knowing that “revenue has increased by 100% month-over-month.”

Comparison of Aggregation Pipes and BI Tools: Aggregation Pipes vs. BI tools such as Tableau and Metabase—Aggregation Pipes are programming interfaces (flexible, automated, embeddable in applications), while BI tools are visual interfaces (drag-and-drop, suitable for non-technical users, interactive exploration). Best practices: 1. Use BI tools (connected via the MongoDB BI Connector) for ad-hoc queries by operations staff; 2. Use Aggregate Pipeline for fixed reports embedded in applications (controllable performance, cacheable results); 3. Use Jupyter + PyMongo for in-depth analysis by data scientists (the Python ecosystem is more robust). Aggregate Pipeline is suitable for “known, repetitive” analytical needs, while BI tools are suitable for “unknown, exploratory” analytical needs.

(1) Complex Business Scenarios

RFM User Segmentation Model: RFM (Recency/Frequency/Monetary) is a classic model for segmenting e-commerce users—Recency refers to the number of days since the last purchase, Frequency refers to the purchase frequency, and Monetary refers to the cumulative spending amount. Each of these three dimensions is categorized into high, medium, and low tiers, resulting in 27 distinct user types. Key insights: High R, High F, High M = Core VIP users (requires priority retention); R Low, F High, M High = Users at risk of churn (requires re-engagement); R High, F Low, M Low = New users (requires nurturing). MongoDB aggregation pipelines are perfectly suited for RFM calculations—$group aggregates the three dimensions by userId, and $switch maps the values to high, medium, or low tiers.

Business Applications of User Segmentation: RFM segmentation results drive differentiated operational strategies—1. VIP users: Exclusive discounts + priority shipping + VIP customer service; 2. Users at risk of churn: Targeted coupons + re-engagement text messages + limited-time special offers; 3. New Users: First-order discounts + onboarding guidance + product recommendations; 4. Inactive Users: Low-cost reactivation (push notifications rather than text messages). Each strategy corresponds to different marketing costs—VIP users have the highest ROI (low retention costs), followed by new users, with inactive users having the lowest.

JAVASCRIPT
// === Scene:User Segmentation Analysis ===
db.orders.aggregate([
  { $match: { status: 'paid' } },
  {
    $group: {
      _id: '$userId',
      totalSpent: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: '$total' },
      firstOrderAt: { $min: '$createdAt' },
      lastOrderAt: { $max: '$createdAt' }
    }
  },
  {
    $addFields: {
      userLevel: {
        $switch: {
          branches: [
            { case: { $gte: ['$totalSpent', 10000] }, then: 'VIP' },
            { case: { $gte: ['$totalSpent', 1000] }, then: 'Gold' },
            { case: { $gte: ['$totalSpent', 100] }, then: 'Silver' }
          ],
          default: 'Bronze'
        }
      },
      daysSinceLastOrder: {
        $divide: [
          { $subtract: [new Date(), '$lastOrderAt'] },
          1000 * 60 * 60 * 24
        ]
      }
    }
  },
  { $sort: { totalSpent: -1 } },
  { $limit: 100 }
]);

(2) Sales Reports

How the $setWindowFields Window Function Works: $setWindowFields (MongoDB 5.0+) is a window function in the aggregation pipeline, similar to the OVER() clause in SQL. It calculates window-based aggregate values—such as moving averages, cumulative sums, and values from the previous or next row—for each document without altering the number of documents. $shift is the offset operation in window functions; $shift: {output: '$revenue', by: -1} retrieves the revenue value from the previous row to calculate the month-over-month growth rate.

Calculating Month-over-Month Growth: To calculate year-over-year growth in monthly reports, you need data from the current month and the previous month—traditional SQL uses the LAG() window function, while MongoDB achieves the equivalent functionality using $setWindowFields + $shift. Calculation formula: (Current month - Previous month) / Previous month × 100%. Note the zero-division protection: when the previous month’s value is 0, the growth rate is set to 0 (checked using $cond).

Boundary Cases for Window Functions: When $shift retrieves the previous row for the first row, it returns null (since there is no previous row)—this is why the growthRate calculation requires $cond to handle cases where prevMonthRevenue is null. Similarly, retrieving the next row for the last row also returns null. Other boundary conditions for window functions: 1. An empty window returns null; 2. For a single-row window, $sum is the value of that row; 3. $rank and $denseRank behave differently when values are tied (rank skips a number, while denseRank does not). Understanding these boundary conditions is essential for using window functions correctly.

The Tiered Architecture of a Reporting System: A complete reporting system consists of three tiers—1. Data Tier (aggregation pipeline that calculates raw metrics); 2. Analysis Tier (derived metrics such as month-over-month, year-over-year, and rankings); 3. Presentation Layer (formatting, charts, and exporting). In this example, the data layer and analysis layer are combined into a single aggregation pipeline, while the presentation layer is handled by the front end. Reports in a production environment typically also require: a caching layer (report data is cached hourly in Redis to avoid redundant calculations), a permissions layer (different roles view data across different dimensions), and an audit layer (recording who viewed which reports and when).

JAVASCRIPT
// === Monthly Sales Report(Including year-over-year)===
db.orders.aggregate([
  {
    $group: {
      _id: {
        year: { $year: '$createdAt' },
        month: { $month: '$createdAt' }
      },
      revenue: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: '$total' }
    }
  },
  { $sort: { '_id.year': 1, '_id.month': 1 } },
  {
    $setWindowFields: {
      sortBy: { '_id.year': 1, '_id.month': 1 },
      output: {
        prevMonthRevenue: {
          $shift: {
            output: '$revenue',
            by: -1
          }
        }
      }
    }
  },
  {
    $addFields: {
      growthRate: {
        $cond: {
          if: { $gt: ['$prevMonthRevenue', 0] },
          then: {
            $divide: [
              { $subtract: ['$revenue', '$prevMonthRevenue'] },
              '$prevMonthRevenue'
            ]
          },
          else: 0
        }
      }
    }
  }
]);

In-Depth Analysis of Pipeline Memory Management: Each stage of an aggregation pipeline maintains a document stream in memory, with a default limit of 100 MB per stage. If this limit is exceeded, MongoDB throws an error and terminates the pipeline, unless allowDiskUse: true is set (allowing temporary writes to disk). However, this introduces a new problem—disk I/O is 100 times slower than memory, causing pipeline performance to drop dramatically. The correct approach is to optimize the pipeline to prevent overflow: 1. Use $match at the beginning to reduce the input volume; 2. Use $project to streamline fields and reduce the space occupied per document; 3. Avoid an excessive number of unique values for _id in $group; 4. Be mindful of array size limits in $push/$addToSet.

Practical Checklist for Aggregation Performance Tuning: 1. Ensure the first $match can use an index (check the explain output); 2. The ideal order is $match before $group and $project after $group; 3. $sort + $limit can be optimized to Top N mode (only requires maintaining a heap of N elements, rather than a full sort); 4. A $match following a $group can be moved forward (manual optimization—MongoDB does not do this automatically); 5. The foreignField in a $lookup must be indexed; 6. For large datasets, add maxTimeMS to prevent the pipeline from running indefinitely.

Boundary Cases for the $setWindowFields Window Function: $setWindowFields is a window function introduced in MongoDB 5.0 that performs calculations (such as moving averages, cumulative sums, and rankings) on documents within a "window." Boundary cases: 1. Window boundaries: "unbounded preceding" and "unbounded following" include all documents in the group, while "1 preceding" and "1 following" include only adjacent documents; 2. Empty Window: When a group contains only one document, $shift, $first, and $last return null; 3. Sorting Conflicts: The sortBy parameter must match the sort order of the group; otherwise, the window range becomes unpredictable; 4. Performance: Window functions must maintain window state in memory; large groups (> 100,000 documents) may exceed memory limits.

Practical Use Cases for $setWindowFields: $setWindowFields fills the gap left by MongoDB’s lack of window functions—1. Month-over-month/year-over-year calculations: $shift retrieves data from the previous period, $subtract calculates the change, and $divide calculates the growth rate; 2. Cumulative sums: The $sum window spans from “unbounded preceding” to the current row to calculate cumulative sales; 3. Moving averages: The $avg window takes data from the most recent N periods (e.g., 7 preceding periods to the current row) to smooth out short-term fluctuations; 4. Ranking/Pagination: $rank/$denseRank implements ranking, while $rowNumber implements pagination (more flexible than skip/limit); 5. Grouped Top N: After grouping with partitionBy, use $rank to rank the results, then use $match with rank <= N to retrieve the top N records from each group. These scenarios represent standard uses of window functions in SQL; MongoDB’s syntax is more verbose but functionally equivalent.

The Three-Tier Architecture of the Reporting System: A production-grade reporting system consists of three tiers: 1. Data Tier (MongoDB Aggregation Pipeline): Calculates statistical results from raw data and outputs them to an intermediate collection; 2. Service Tier (Node.js + Cache): Invokes the aggregation pipeline, caches results (Redis TTL 5 minutes), and provides REST APIs; 3. Presentation Layer (Front-end Chart Library): Retrieves data from the API, renders charts (ECharts/Chart.js), and enables interactive filtering. The benefits of separating these three layers—the data layer focuses on computation, the service layer focuses on performance, and the presentation layer focuses on user experience—allow each layer to be optimized and scaled independently.

Aggregation Pipelines vs. BI Tools: When should you use aggregation pipelines to build reports, and when should you use specialized BI tools (Metabase/Superset/Tableau)? Aggregation pipelines are suitable for: 1. Reports with simple logic (aggregation pipelines with 5–10 stages); 2. Applications that require embedding (API returns report data, and the front end renders charts on its own); 3. Moderate data volume (< 10 million records); 4. High real-time requirements (real-time calculation for each request). BI tools are suitable for—1. Self-service queries by non-technical users (drag-and-drop interface); 2. Complex multidimensional analysis (OLAP cubes, drill-down/drill-up); 3. Diverse data sources (MongoDB + MySQL + CSV); 4. Scheduled email delivery of reports. For small teams, an aggregation pipeline combined with ECharts is sufficient; for larger teams, BI tools offer greater efficiency.

▶ Example 1: Advanced Practical Application of Aggregation Pipelines - User Segmentation Analysis

JAVASCRIPT
// Scene:Classify users based on their spending VIP Layering
db.orders.insertMany([
  { userId: 'user_001', total: NumberDecimal('15000'), createdAt: new Date('2026-06-01'), status: 'paid' },
  { userId: 'user_002', total: NumberDecimal('500'),   createdAt: new Date('2026-06-05'), status: 'paid' },
  { userId: 'user_003', total: NumberDecimal('50'),    createdAt: new Date('2026-06-10'), status: 'paid' },
  { userId: 'user_001', total: NumberDecimal('800'),   createdAt: new Date('2026-06-15'), status: 'paid' }
]);

// Complete Pipeline:User Segmentation + Tag Conversion + Monthly Statistics
db.orders.aggregate([
  // Step 1: Count only paid orders
  { $match: { status: 'paid' } },

  // Step 2: Group by User
  {
    $group: {
      _id: '$userId',
      totalSpent: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: '$total' },
      lastOrderAt: { $max: '$createdAt' }
    }
  },

  // Step 3: Use $switch to Segment Users
  {
    $addFields: {
      userLevel: {
        $switch: {
          branches: [
            { case: { $gte: ['$totalSpent', 10000] }, then: 'VIP' },
            { case: { $gte: ['$totalSpent', 1000] },  then: 'Gold' },
            { case: { $gte: ['$totalSpent', 100] },   then: 'Silver' }
          ],
          default: 'Bronze'
        }
      },
      // Days until the last order
      daysSinceLastOrder: {
        $divide: [
          { $subtract: [new Date(), '$lastOrderAt'] },
          1000 * 60 * 60 * 24
        ]
      }
    }
  },

  // Step 4: Date Format
  {
    $project: {
      userId: '$_id',
      totalSpent: 1,
      avgOrderValue: { $toString: '$avgOrderValue' },  // Decimal128 -> String
      userLevel: 1,
      daysSinceLastOrder: { $round: ['$daysSinceLastOrder', 0] },  // Round to the nearest whole number
      lastOrderDate: {
        $dateToString: {
          format: '%Y-%m-%d',
          date: '$lastOrderAt',
          timezone: 'Asia/Tokyo'
        }
      }
    }
  },

  // Step 5: Sort by Amount Spent
  { $sort: { totalSpent: -1 } }
]);

// Output Results:
// [
//   { userId: 'user_001', totalSpent: '15800', avgOrderValue: '7900', userLevel: 'VIP', daysSinceLastOrder: 16, lastOrderDate: '2026-06-15' },
//   { userId: 'user_002', totalSpent: '500',   avgOrderValue: '500',  userLevel: 'Silver', daysSinceLastOrder: 26, lastOrderDate: '2026-06-05' },
//   { userId: 'user_003', totalSpent: '50',    avgOrderValue: '50',   userLevel: 'Bronze', daysSinceLastOrder: 21, lastOrderDate: '2026-06-10' }
// ]

Output: The three users are automatically segmented by spending amount. user_001 has a total spending of 15,800 and is marked as a VIP; it has been 16 days since their last order, and the date is formatted for the Tokyo time zone.

▶ Example 2: ShopHub Sales Reports + Date Formatting

Data Preparation for Reports: Test data for sales reports must cover multiple months—otherwise, month-over-month growth cannot be calculated (since the first month lacks data from the previous month). In this example, we’ve prepared order data for May through July: 1 order in May, 2 orders in June, and 1 order in July. Key points for data design: 1. At least 1 order per month (otherwise, $bucket will generate empty buckets); 2. The amount distribution is reasonable (including a small amount of 300 and a large amount of 2200); 3. The status is uniformly set to paid ($match filters out unpaid orders).

Interpreting Month-over-Month Growth: Month-over-month growth rate = (This Month - Last Month) / Last Month. Revenue of 3,000 in June represents a 100% increase from 1,500 in May—this is a “doubling” of growth. However, be mindful of the base effect—a rise from 100 to 200 also represents 100% growth, but the absolute increase is only 100; a rise from 10,000 to 15,000 represents only 50% growth, but the absolute increase is 5,000. When making business decisions, it is essential to consider both the growth rate and the absolute increase; relying on just one metric is insufficient.

Data Validation Methods for Reports: The output of the aggregation pipeline must be validated—1. Cross-validation: Compare the result of find().count() with the result of $sum: 1 in $group; the counts must match; 2. Sampling validation: Randomly select 2–3 rows of raw data and manually calculate them to verify that the aggregated results are correct; 3. Boundary validation: For an empty dataset ($match returns no matches) → Empty result rather than an error; single data row ($group contains only 1 group) → Month-over-month growth rate is 0 (no data from the previous month); 4. Consistency Validation: Monthly total = Annual total; totals for each category = Overall total. Any inconsistency indicates a bug in the pipeline logic.

Report Caching Strategy: Real-time aggregation pipelines take a long time to process large datasets (seconds) — 1. Scheduled materialization: Use $merge to write aggregation results to a report collection (e.g., monthly_reports) every hour; queries read from the report collection (milliseconds); 2. Incremental updates: Aggregate only new data ($match with an incremental time range), then use $merge to merge it into existing reports; 3. Caching layer: Use Node.js to cache aggregation results in Redis (TTL 5–30 minutes), suitable for report pages with many reads and few writes; 4. Expiration Strategy: Mark the cache as invalid when the source data changes (using version numbers or timestamps), and recalculate upon the next query. Criteria for selecting a strategy: Data change frequency (real-time requirements) × query frequency (performance requirements).

JAVASCRIPT
// Scene:ShopHub The operations team generates monthly sales reports.,Includes formatted dates and year-over-year growth
db.orders.insertMany([
  { orderId: 'ORD-001', userId: 'user_001', total: NumberDecimal('1500'), status: 'paid', createdAt: new Date('2026-05-15') },
  { orderId: 'ORD-002', userId: 'user_002', total: NumberDecimal('800'),  status: 'paid', createdAt: new Date('2026-06-01') },
  { orderId: 'ORD-003', userId: 'user_001', total: NumberDecimal('2200'), status: 'paid', createdAt: new Date('2026-06-20') },
  { orderId: 'ORD-004', userId: 'user_003', total: NumberDecimal('300'),  status: 'paid', createdAt: new Date('2026-07-05') }
]);

// Monthly Report:Format Month、Calculate the month-over-month growth、User Segmentation Labels
db.orders.aggregate([
  { $match: { status: 'paid' } },
  {
    $group: {
      _id: {
        year: { $year: '$createdAt' },
        month: { $month: '$createdAt' }
      },
      revenue: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: '$total' }
    }
  },
  { $sort: { '_id.year': 1, '_id.month': 1 } },
  {
    $addFields: {
      monthLabel: {
        $dateToString: {
          format: '%Y-%m',
          date: { $dateFromParts: { year: '$_id.year', month: '$_id.month' } }
        }
      },
      revenueStr: { $toString: '$revenue' },
      performance: {
        $switch: {
          branches: [
            { case: { $gte: ['$revenue', 2000] }, then: 'Excellent' },
            { case: { $gte: ['$revenue', 1000] }, then: 'Good' },
            { case: { $gte: ['$revenue', 500] }, then: 'Average' }
          ],
          default: 'Below Target'
        }
      }
    }
  }
]);

// Output:
// [
//   { _id: {year:2026,month:5}, monthLabel:'2026-05', revenue:1500, performance:'Good', ... },
//   { _id: {year:2026,month:6}, monthLabel:'2026-06', revenue:3000, performance:'Excellent', ... },
//   { _id: {year:2026,month:7}, monthLabel:'2026-07', revenue:300, performance:'Below Target', ... }
// ]

Output: Monthly reports include formatted month labels (2026-05), converted revenue strings, and automatic performance rating tags using $switch.

Production-Ready Adaptation of the Reporting System: The aggregation pipeline in this example is a training version—further modifications are required for the production environment— 1. Parameterized queries: Month ranges, category filters, and user ID filters should all be passed as API parameters (rather than hard-coded into the pipeline); 2. Error Handling: Pipeline execution may fail due to memory limits or timeouts; this requires wrapping the code in a try-catch block, setting a maxTimeMS limit, and using allowDiskUse as a fallback; 3. Caching Layer: Monthly report data changes infrequently (only a few new orders per day); use Redis to cache aggregation results (TTL 1 hour), so that 90% of report requests hit the cache and do not require pipeline execution; 4. Scheduled Materialization: Use Change Stream to monitor order changes and incrementally update the report collection (rather than performing a full aggregation each time); 5. Output Format Adaptation: The front end requires the Chart.js format ({labels: [...], datasets: [...]}), so the aggregated results are converted to chart format on the back end.

Aggregation Pipeline Troubleshooting Checklist: Steps to troubleshoot errors in the aggregation pipeline—1. Error Type Classification: "Buffer exceeds limit" → Memory limit exceeded (increase allowDiskUse or optimize the pipeline); "exceeded time limit" → Execution timeout (increase maxTimeMS or optimize indexes); "field path must start with '$'" → Field reference error (check for the $ prefix); "unknown operator" → Operator misspelling or unsupported by the version; 2. Debug stage by stage: Execute only one stage at a time; after confirming the output is correct, proceed to the next stage; 3. Data volume verification: Verify that the number of documents before and after $group matches expectations (how many were filtered by $match and how many were expanded by $unwind); 4. Index check: Use explain() to confirm that $match is using an index (IXSCAN rather than COLLSCAN); 5. Version compatibility: Operators such as $dateAdd (5.0+), $setWindowFields (5.0+), and $densify (6.1+) require the corresponding MongoDB version.

❓ FAQ

Design Intent Behind Common Questions: These questions are not just FAQs; they are an extension of design decisions—the choice between $cond and $switch involves a trade-off between readability and performance; time zones involve choosing a storage strategy; and type conversion involves risk management in a weakly typed system. Understanding "why" is more important than remembering "what."

Q Which has better performance, $cond or $switch?
A $cond is slightly faster (fewer CPU instructions). Use $switch only when there are multiple branches.
Q Does $dateToString support time zones?
A It supports the timezone parameter (IANA time zone names, such as 'Asia/Tokyo').
Q What happens if a type conversion fails?
A By default, it returns null. You can use $convert to specify an onError handler.

📖 Summary

Connecting Concepts: The five advanced topics in the aggregation pipeline form the data processing capability stack—conditional expressions constitute the “logic layer” (making decisions based on data), while date, type, string, and array operations constitute the “transformation layer” (converting data into the desired format). In actual pipelines, these operations are used in combination: $group calculates statistics → $addFields applies conditional layering with $switch → $project formats dates with $dateToString → output. Mastering the transformation layer is the essential path to becoming proficient in aggregation pipelines.

A Comprehensive Overview of Aggregation Pipeline Performance Optimization: The performance of an aggregation pipeline depends on three factors—1. I/O volume (number of documents scanned/index entries read): This can be optimized by adding a pre-filter before the $match stage and using hint() to select indexes; 2. Memory usage (how much memory intermediate pipeline results consume): Optimization can be achieved by streamlining fields in $project and using allowDiskUse to offload overflow to disk; 3. CPU computation ($group/$sort computational complexity): Optimization can be achieved by reducing the complexity of _id and leveraging indexes for sorting. Each dimension has corresponding design principles and tuning techniques; a systematic understanding is more effective than memorizing optimization tricks one by one.

From Aggregation Pipelines to ETL: An aggregation pipeline is essentially a lightweight ETL (Extract-Transform-Load) tool—$match is Extract (extracting data from a collection), $project/$addFields/$convert is Transform (data cleaning and transformation), and $out/$merge is Load (writing to the target collection). For simple data pipelines (single source → transformation → destination), the aggregation pipeline is lighter and faster than Spark or Airflow. However, when ETL involves multiple data sources (MongoDB + MySQL + S3) or complex scheduling (dependency chains, retries, alerts), you should use a dedicated ETL tool instead of the aggregation pipeline.


📝 Exercises

Skill Level: The 5 exercises correspond to 3 skill levels—Basic Exercises (⭐) test your ability to use a single operator; Intermediate Exercises (⭐⭐) test your ability to combine operators; and Challenge Exercises (⭐⭐⭐) test your ability to design independently. We recommend completing them in order. For each exercise, first describe in Chinese what each step of the pipeline should do (pseudo-pipeline), then translate that into code.

  1. Basic Question (⭐): Use a $switch statement to add Chinese labels to order statuses.
  2. Basic Question (⭐): Use $dateToString to format the order date.
  3. Advanced Problem (⭐⭐): User Segmentation ($switch to distinguish between VIP, Gold, and Silver).
  4. Advanced Problem (⭐⭐): Use $map to convert all labels to uppercase.
  5. Challenge Question (⭐⭐⭐): Monthly Sales Report + Year-over-Year Growth Rate ($setWindowFields + $shift).

Challenge Guide: The "Monthly Sales Report + Year-over-Year Growth Rate" challenge most closely resembles real-world business scenarios. Implementation steps: 1. Use $match to filter paid orders; 2. Use $group to group by {year, month} and calculate revenue, orderCount, and avgOrderValue; 3. Use $sort to sort by year and month; 4. Use $setWindowFields and $shift to retrieve last month’s revenue; 5. Use $addFields to calculate the month-over-month growth rate. Note that in $shift, by: -1 refers to the "previous row" (last month), and by: 1 refers to the "next row" (next month).

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏