Pipeline Basics
A pipeline is an array of stages; each stage transforms the documents and passes them to the next.
aggregate()
db.orders.aggregate([
{ $match: { status: "shipped" } },
{ $group: { _id: "$region", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
])
$match and $limit early to reduce work.$match, filter
{ $match: {
status: "active",
amount: { $gte: 100 },
tags: { $in: ["vip", "new"] }
} }
$count
{ $count: "numDocs" }
{ numDocs: N }.Reshaping Stages
Select, rename, compute, and restructure fields.
$project
{ $project: {
_id: 0,
name: 1,
year: { $year: "$createdAt" },
fullName: { $concat: ["$first", " ", "$last"] }
} }
$addFields / $set
{ $addFields: {
total: { $multiply: ["$price", "$qty"] }
} }
$set is an alias for $addFields. Keeps existing fields.$unset
{ $unset: ["password", "internal.notes"] }
$replaceRoot
{ $replaceRoot: { newRoot: "$profile" } }
Grouping & Accumulators
$group buckets documents by a key and rolls them up with accumulators.
$group
{ $group: {
_id: "$category",
count: { $sum: 1 },
revenue:{ $sum: "$amount" },
avg: { $avg: "$amount" }
} }
_id is the group key; use null to aggregate everything.$sum $avg $min $max
$first $last
$push // array of values
$addToSet // array of unique values
$count // (as a stage)
$push/$addToSet to collect values per group.{ $group: {
_id: { region: "$region", month: { $month: "$date" } },
total: { $sum: "$amount" }
} }
Arrays & Joins
Flatten arrays and pull in related collections.
$unwind
{ $unwind: "$items" }
// or, keep empty arrays:
{ $unwind: {
path: "$items",
preserveNullAndEmptyArrays: true
} }
$lookup, join
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
} }
customer is an array, often followed by $unwind.$lookup with pipeline
{ $lookup: {
from: "orders",
let: { cid: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$custId", "$$cid"] } } },
{ $project: { total: 1 } }
],
as: "orders"
} }
Sort, Page & Sample
Order and slice the result set.
$sort / $limit / $skip
{ $sort: { createdAt: -1 } } // -1 desc, 1 asc
{ $limit: 20 }
{ $skip: 40 }
$facet, multi-pipeline
{ $facet: {
rows: [ { $skip: 0 }, { $limit: 20 } ],
total: [ { $count: "n" } ]
} }
$bucket
{ $bucket: {
groupBy: "$price",
boundaries: [0, 50, 100, 200],
default: "200+",
output: { count: { $sum: 1 } }
} }
$bucketAuto picks boundaries for you.Expression Operators
Use inside $project / $addFields / $group. Prefix field names with $.
{ $cond: [ { $gte: ["$score", 60] }, "pass", "fail" ] }
{ $ifNull: [ "$nickname", "$name" ] }
{ $switch: { branches: [
{ case: { $gte: ["$n",90] }, then: "A" },
{ case: { $gte: ["$n",80] }, then: "B" }
], default: "C" } }
$add $subtract $multiply $divide $mod
$eq $ne $gt $gte $lt $lte $cmp
$and $or $not
$concat $toUpper $toLower $trim
$substrCP $strLenCP $split
$regexMatch: { input:"$x", regex:"^A" }
$year $month $dayOfMonth $hour
$dateToString: { format:"%Y-%m-%d", date:"$createdAt" }
$dateTrunc: { date:"$ts", unit:"day" }
$size $arrayElemAt $slice
$filter: { input:"$items", as:"i",
cond:{ $gt:["$$i.qty",0] } }
$map $reduce $in $concatArrays
Common Recipes
Copy-paste starting points for frequent tasks.
db.events.aggregate([
{ $group: { _id: "$type", n: { $sum: 1 } } },
{ $sort: { n: -1 } }
])
db.orders.aggregate([
{ $lookup: { from:"customers", localField:"custId",
foreignField:"_id", as:"c" } },
{ $unwind: "$c" },
{ $project: { total:1, name:"$c.name" } }
])
db.products.aggregate([
{ $match: { inStock: true } },
{ $facet: {
data: [ { $sort:{price:1} }, { $skip:0 }, { $limit:20 } ],
count: [ { $count: "total" } ]
} }
])
db.sales.aggregate([
{ $setWindowFields: {
sortBy: { date: 1 },
output: { runningTotal: {
$sum: "$amount",
window: { documents: ["unbounded","current"] }
} }
} }
])