Pipeline stages decide what happens to documents; expressions decide what values are computed along the way. The same expression language works inside $project, $set, $group, $match (through $expr) and even pipeline-style updates. After this lesson you will be able to compute derived fields with arithmetic, string, date and conditional operators, transform arrays without unwinding them, summarise groups with accumulators, and compare two fields of the same document in a query.
An expression is a field path ("$price"), a literal (5, "paid"), a system variable ("$$NOW", "$$ROOT") or an operator object whose arguments are themselves expressions. Operators that take several arguments use an array:
{ $multiply: ["$price", "$qty"] }
{ $concat: ["$first", " ", "$last"] }
{ $gt: ["$stock", "$reorderLevel"] } // comparison between two fields| Family | Operators |
|---|---|
| Arithmetic | $add, $subtract, $multiply, $divide, $mod, $round, $trunc, $abs, $ceil, $floor |
| Strings | $concat, $toUpper, $toLower, $trim, $split, $substrCP, $strLenCP, $replaceAll, $regexMatch |
| Comparison | $eq, $ne, $gt, $gte, $lt, $lte, $cmp |
| Conversion | $toInt, $toDouble, $toString, $toDate, $toObjectId, $convert |
db.products.aggregate([
{ $set: {
slug: { $toLower: { $replaceAll: { input: "$name", find: " ", replacement: "-" } } },
priceInt: { $convert: { input: "$priceText", to: "int", onError: null, onNull: 0 } }
} }
])$convert with onError and onNull is the safe way to clean up inconsistently typed fields; the short forms such as $toInt throw on bad input.
Conditionals belong to the same toolbox:
{ $cond: { if: { $gte: ["$stock", 1] }, then: "in stock", else: "sold out" } }
{ $cond: [ { $eq: ["$status", "paid"] }, "$total", 0 ] } // array form
{ $ifNull: ["$nickname", "$name", "anonymous"] } // first non-null value
{ $switch: { branches: [ { case: ..., then: ... } ], default: "other" } }$cond inside $sum is the standard way to count or total a subset within a group, for example paidTotal: { $sum: { $cond: [{ $eq: ["$status", "paid"] }, "$total", 0] } }.
{ $dateToString: { format: "%Y-%m-%d", date: "$placedAt", timezone: "Asia/Kolkata" } }
{ $dateTrunc: { date: "$placedAt", unit: "week", timezone: "Asia/Kolkata" } } // bucket by week
{ $dateDiff: { startDate: "$placedAt", endDate: "$$NOW", unit: "day" } }
{ $dateAdd: { startDate: "$placedAt", unit: "day", amount: 30 } }
{ $year: "$placedAt" }, { $month: "$placedAt" }, { $dayOfWeek: "$placedAt" }Dates are stored in UTC; pass timezone whenever a report must follow local calendar days.
db.orders.aggregate([
{ $set: {
itemCount: { $size: "$items" },
firstItem: { $first: "$items" },
bigLines: { $filter: { input: "$items", as: "i", cond: { $gte: ["$$i.qty", 5] } } },
lineTotals: { $map: { input: "$items", as: "i", in: { $multiply: ["$$i.price", "$$i.qty"] } } },
orderTotal: { $sum: { $map: { input: "$items", in: { $multiply: ["$$this.price", "$$this.qty"] } } } }
} }
])$filter, $map and $reduce operate on an array in place, which is usually cheaper than $unwind followed by $group. $in, $arrayElemAt, $slice, $concatArrays and $sortArray round out the family.
Inside $group the operators are accumulators: $sum, $avg, $min, $max, $count, $first, $last, $push, $addToSet, $mergeObjects, $stdDevPop, and the ranked $top, $bottom, $topN, $bottomN. $first and $last are only meaningful after a $sort.
Query operators compare a field with a constant; $expr lets a query use aggregation expressions, including comparisons between two fields:
db.products.find({ $expr: { $lt: ["$stock", "$reorderLevel"] } })
db.orders.find({ $expr: { $gte: [{ $size: "$items" }, 3] } })Field-to-field comparisons cannot use an index; a $expr comparing a field with a constant can.
$ prefix, so "price" is a string literal and $multiply fails.$sum ignores non-numeric values and returns 0 without warning; convert first.$first in $group without a preceding $sort, which returns an arbitrary document.What does `{ $concat: ["price", ": ", "$price"] }` produce for a document with `price: 10`?
"$field"), literals, variables ("$$NOW") and operator objects; multi-argument operators take arrays.$convert operators compute derived fields in $set and $project.$cond, $ifNull and $switch add branching; $cond inside $sum totals a subset of a group.timezone; $dateTrunc is the idiomatic way to bucket by day, week or month.$filter, $map and $reduce transform arrays in place, and $expr brings the same expressions into find().Next lesson: Joins with $lookup and Flattening with $unwind — combine data from several collections and turn array elements into documents.