Terms aggregation #
A multi-bucket value source based aggregation where buckets are dynamically
built - one per unique value. It answers “group by this field, count (and
aggregate) per value” without knowing the values in advance, which a
fixed-bucket aggregation like histogram or range cannot do.
How it works #
Every matching document is placed in the bucket keyed by its value of
field. Multi-valued fields bucket per element: a document whose tags
holds ["search", "architecture"] is counted once in the search bucket
and once in the architecture bucket. Documents without the field are
skipped — unless missing is set, in which case they land together in one
bucket keyed by that value.
Buckets are ordered by doc_count descending, with ties broken by key
ascending. The tie-break is numeric-aware: when every key parses as a
number, keys compare as numbers (so 2 sorts before 10); otherwise they
compare as strings. Numeric keys are normalized — an integral double
and a long with the same value share one bucket key.
min_doc_count filtering happens before the size truncation, so size
counts surviving buckets only. With min_doc_count: 0, terms the query
never matched still materialize as count-0 buckets (drawn from the column
dictionary) — useful with an explicit include list to guarantee a
complete report.
include/exclude restrict candidate keys before counting. Both accept
an exact-value array (numeric-aware: "10", 10 and 10.0 are the same
key) or a single wildcard string (prefix*, *suffix). include also
accepts { "partition": p, "num_partitions": n }: keys are routed to a
partition by a Murmur3 hash (numeric keys hashed as
longs), so several requests can split a high-cardinality field into
disjoint slices — request p = 0..n-1 to cover every key exactly once.
Examples #
One bucket per type — GROUP BY on the SQL side (the pill beside the
Run button), ordered by the count:
POST /sales/_search
{
"aggs": {
"by_type": {
"terms": {
"field": "type"
}
}
}
}
Response:
{
...
"aggregations": {
"by_type": {
"buckets": [
{ "key": "t-shirt", "doc_count": 60 },
{ "key": "hat", "doc_count": 30 },
{ "key": "mug", "doc_count": 20 },
{ "key": "sticker", "doc_count": 10 }
]
}
}
}
Use cases #
- Category leaderboards.
sales.terms(type)returns thet-shirt: 60 / hat: 30 / mug: 20 / sticker: 10ranking shown above; nest asumsub-aggregation to rank by revenue instead of count. - Multi-value tag clouds.
articlescarry atagsarray; a terms aggregation on it spreads each article across every tag it carries, giving per-tag article counts. - Log level mix.
logs.terms(level)yieldsinfo: 100 / error: 25 / warn: 25— a one-request health summary. - Segment rollups.
customers.terms(tier)splits the 20 customers intosilver: 8 / bronze: 8 / gold: 4for per-tier metrics. - Sliced exhaustive scans. Combine
includepartitioning withsizeto walk a high-cardinality field slice by slice when one request cannot hold all buckets.
Parameters for terms
#
field
(Required, string) Field you wish to aggregate.size
(Optional, integer) Maximum number of buckets to return. Defaults to10.min_doc_count
(Optional, integer) Drop buckets with fewer matching documents. Defaults to1;0keeps empty buckets — only meaningful together with an explicitincludelist.missing
(Optional, value) Value substituted for documents without the field (any scalar; those documents land in one bucket keyed by it).include
(Optional) Restricts bucket keys: an exact-value array, a wildcard string (prefix*/*suffix), or{ "partition": <p>, "num_partitions": <n> }.exclude
(Optional) Excludes bucket keys: an exact-value array or a wildcard string (negatedinclude).aggs
(Optional, object) Nested sub-aggregations, computed per bucket.
Bucket order is fixed to doc_count descending (key-ascending
tie-break); the order parameter is not accepted.
Related #
multi_terms— same idea over a tuple of fieldsrare_terms— the mirror image: keep the long tailsignificant_terms— unusual values in a scopecomposite— paginated terms for very large key sets