Aggregations — Analytics Built In

Elasticsearch / OpenSearch

Chapter 7 · Aggregations — Analytics Built In

This chapter resolves postgres1-6's own explicit "faceted search as a first-class feature" callout — a capability genuinely different in kind from SQL's own GROUP BY, not just a different syntax for the same idea.

What Aggregations Actually Are

An aggregation computes summary statistics or groupings over the same document set a search query matches — in the same request as the search itself. This is a genuinely combined "search + analyze" operation, not two separate steps.

Metric Aggregations — The Simple Case

avg, sum, min, max, and stats (all of these at once) are conceptually similar to SQL's own aggregate functions (SUM, AVG, COUNT). This part alone isn't dramatically different from SQL.

Bucket Aggregations — Where It Diverges From GROUP BY

A terms aggregation groups documents by the distinct values of a field — conceptually the closest analog to SQL's own GROUP BY ("how many products are in each category"). The real divergence starts here: aggregations can be nested arbitrarily deep — a terms aggregation on category, with a nested terms aggregation on brand within each category, with a nested avg aggregation on price within each of those — producing a genuinely multi-level breakdown in a single request that would require either a complex multi-level GROUP BY/ROLLUP construction in SQL, or several separate queries entirely.

range/histogram aggregations bucket numeric values into ranges ($0–25, $25–50, $50+) or fixed-width histograms — directly usable for the classic "price range" faceted-search filter UI. date_histogram buckets by time interval (documents per day/week/month) — genuinely common in log/analytics use cases.

Combining Search and Aggregations in One Request

POST /products/_search
{
  "query": { "match": { "description": "wireless mouse" } },
  "aggs": {
    "by_category": { "terms": { "field": "category" } },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 25 }, { "from": 25, "to": 50 }, { "from": 50 }
        ]
      }
    }
  }
}

This is exactly what powers a real e-commerce faceted search sidebar — search results, category counts ("12 in Electronics, 8 in Home Goods"), and price-range checkboxes, all computed over the same filtered result set, in one round trip. This directly, explicitly resolves postgres1-6's own "faceted search as a first-class feature" callout.

Why This Is a Genuinely Different Capability Class From GROUP BY

SQL's GROUP BY operates on stored, structured rows — a natural fit for relational data, but with no built-in way to combine "full-text relevance-ranked search results" and "a faceted breakdown of those same results" in one single, efficient operation, because SQL's own execution model was never built around search-then-facet as a first-class combined pattern. Aggregations here also operate at genuinely large scale efficiently, across distributed shards (previewed here, covered fully in search1-8) — a real analytical, OLAP-style workload built into the same engine handling the search itself, rather than requiring a separate analytics system.

Aggregating on an analyzed text field doesn't do what you'd expect
Aggregating directly on an analyzed text field creates buckets per individual token, not per whole value — a category field containing "Wireless Mouse" would bucket into separate "wireless" and "mouse" buckets, rather than one distinct "Wireless Mouse" category. The fix is a "keyword" sub-field — an exact, unanalyzed mapping alongside the analyzed text field — used specifically for aggregation and exact-matching purposes. This is genuinely the same underlying analyzed-vs-exact tension already seen twice: search1-3's own mapping material, and search1-5's own match-vs-term distinction — now showing up a third time, in a third context.
postgres1-6's own callout, resolved
This closes the "faceted search as a first-class feature" item postgres1-6 named explicitly. search1-8 covers the distributed sharding/replication architecture that makes aggregations like this fast at real scale.

Hands-On Exercises

Exercise 1

Explain the difference between a metric aggregation and a bucket aggregation, and explain how the terms aggregation is the closest conceptual analog to SQL's own GROUP BY.

📄 View solution
Exercise 2

Using this chapter's own worked example, explain how combining a search query and aggregations in one request is what actually powers a real e-commerce faceted search sidebar, and explain why this resolves postgres1-6's own "faceted search as a first-class feature" callout specifically.

📄 View solution
Exercise 3

Using this chapter's own warn-box, explain the analyzed-field aggregation gotcha with a concrete example, and explain how this is the same underlying analyzed-vs-exact tension already seen in search1-3's mapping material and search1-5's match-vs-term distinction.

📄 View solution

Chapter 7 Quick Reference

  • Metric aggregations (avg/sum/min/max/stats) — close to SQL's own aggregate functions
  • Bucket aggregations (terms/range/histogram/date_histogram) — arbitrarily nestable in one request, genuinely beyond a single SQL GROUP BY
  • Search + aggregations combined in one request is what powers a real faceted-search sidebar — resolving postgres1-6's own callout
  • Aggregations run efficiently across distributed shards — a real OLAP-style workload built into the same engine (search1-8)
  • Aggregating on an analyzed field buckets by TOKEN, not by whole value — use a keyword sub-field; the same analyzed-vs-exact tension as search1-3's mapping and search1-5's match-vs-term
  • Next chapter: Sharding & Replication — Distributed by Design