iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

$facet

\$facet runs multiple aggregation pipelines over the same input set in a single pass. The classic use is a search results page that needs both the paged hits AND aggregate counts (by category, by price bucket, by rating). One round trip, one shared scan — vastly faster than firing N queries from the app.

Search results + facets in one aggregation

EXAMPLE
// products in 'shop': { name, category, price, rating, brand }
const pipeline = [
  // 1) Filter once; $facet sees the filtered set
  { $match: {
      $text: { $search: 'wool jacket' },
      price: { $gte: 50, $lte: 500 },
      status: 'active',
  } },

  // 2) Fan out into multiple pipelines, each independent
  { $facet: {
      hits: [
        { $sort: { score: { $meta: 'textScore' }, _id: 1 } },
        { $skip: 0 },
        { $limit: 20 },
        { $project: { name: 1, category: 1, price: 1, rating: 1, brand: 1,
                       score: { $meta: 'textScore' } } },
      ],
      total: [
        { $count: 'value' },
      ],
      categories: [
        { $group: { _id: '$category', count: { $sum: 1 } } },
        { $sort:  { count: -1 } },
        { $limit: 10 },
      ],
      brands: [
        { $group: { _id: '$brand', count: { $sum: 1 } } },
        { $sort:  { count: -1 } },
        { $limit: 10 },
      ],
      price_buckets: [
        { $bucket: {
            groupBy: '$price',
            boundaries: [0, 50, 100, 200, 500, 1000, Infinity],
            default: 'other',
            output: { count: { $sum: 1 } },
        } },
      ],
      avg_rating: [
        { $group: { _id: null, avg: { $avg: '$rating' } } },
      ],
  } },

  // 3) Flatten the single-element scalar facets for the client
  { $project: {
      hits: 1,
      categories: 1,
      brands: 1,
      price_buckets: 1,
      total:      { $ifNull: [{ $arrayElemAt: ['$total.value', 0] }, 0] },
      avg_rating: { $ifNull: [{ $arrayElemAt: ['$avg_rating.avg', 0] }, null] },
  } },
];

const [result] = await db.collection('products').aggregate(pipeline).toArray();
// result.hits         -> 20 matching products
// result.total        -> total matching count
// result.categories   -> [{ _id: 'Outerwear', count: 312 }, ...]
// result.price_buckets-> [{ _id: 50, count: 90 }, ...]

Why it matters

\$facet runs each sub-pipeline serially on the server, so do not throw 20 facets in there — three or four is the right ceiling. If you need many, run the heavy ones on background materialised views (\$merge) and only compute the cheap counts on each request.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Run several pipelines on the same input
{ $facet: {
    paid:   [{ $match: { status: 'paid' } }],
    counts: [{ $count: 'n' }],
} }
Try it Yourself »

Discussion

Loading…