Nested query #
Matches parent documents whose nested sub-documents — objects mapped
with "nested": true — satisfy a query within one element at a time.
The cross-element problem #
A flattened index correlates across array elements, not within them:
variants.color = blue AND variants.size = L matches a document as soon
as some element is blue and some element is L — not necessarily the
same one. The teaching dataset’s products makes the difference visible
(each element pairs blue with L, never with M):
The flattened query above reports 10 documents — every product with some blue variant and some M variant, even when they are different elements. The nested query below evaluates each variant as a whole element and reports 0: in this dataset blue variants only ever come in size L.
Same-element matching #
When the pair lives in one element, only the nested query is guaranteed correct (blue always pairs with L in this dataset):
20 of 30 products have a blue/L variant. Any leaf composes inside the inner query:
Inner queries and minimum_should_match
#
The inner query supports:
term,match,multi_match,match_phrase(phrase),range,existsbool—minimum_should_matchincluded, scoped to one elementnesteditself, for sub-objects
Not supported inside nested yet: terms, prefix, wildcard,
fuzzy, regexp, suffix, the
span family
and
query_string. These are
rejected with an HTTP 400 naming the offending clause and the nested
path — never a silent zero-hit answer. The same shapes keep working
at the top level.
bool.minimum_should_match applies within one element: a parent
matches when at least one element satisfies that many should clauses
by itself. Clauses matched by different elements never add up —
minimum_should_match: 2 over two should clauses is the same-element
AND:
The same 20 blue/L products as the must example above — with
minimum_should_match dropped to 1 (the default when the bool has no
must/filter) it becomes the same-element OR.
Phrases compose with the same element scoping — the adjacent word pair
must live in ONE element, and slop (default 0) buys holes between
the words, in query order:
POST /my-collection/_search
{
"query": {
"nested": {
"path": "reviews",
"query": {
"match_phrase": {
"field": "reviews.body",
"query": "quick fox",
"slop": 1
}
}
}
}
}
quick fox with slop: 1 matches an element holding “quick brown fox”
(one word between); at slop: 0 nothing matches unless the words are
adjacent. Reversed word order ("fox quick") never matches at any
slop — phrase terms keep query order.
Schema requirement #
Declare the object as nested so elements are indexed with ordinal-tagged postings (server-side collections). Both forms are accepted — they are the same schema:
PUT /my-collection
{
"schema": {
"properties": {
"variants": {
"type": "nested",
"properties": {
"color": { "type": "keyword" },
"size": { "type": "keyword" }
}
}
}
}
}
The explicit form spells the parameter out ("type": "object" +
"nested": true) for readers who prefer it.
Documents index variants as an array of objects; each element is
addressable through its dotted sub-fields inside the nested query.
Performance characteristics #
Correlation is resolved in one of two ways, depending on where the matching documents live:
- Built segments — every element’s postings carry a
(doc_id, ordinal)pair, and the engine intersects / unions on that composite key. Nested queries resolve at close to ordinaryboolcost. - Realtime (not yet frozen) documents — the inner query is
evaluated against each stored array element, one document at a time.
Exact and available immediately after the write, but the cost is
linear in documents × elements: a nested query over 20 000 documents
× 5 elements measured roughly 10–15× the latency of the equivalent
flattened
bool(sub-second either way). Keep realtime buffers bounded when nested queries dominate a large collection.
Scoring note: realtime-layer elements match with a constant score, so
score_mode only differentiates on built segments.
Parameters for nested
#
path
(Required, string) Path of the nested object (e.g."variants").query
(Required, object) The query applied to each inner element; a parent matches when at least one element satisfies it.score_mode
(Optional, string) How per-element scores collapse into the parent’s score:sum(default),avg,min,max, ornone(constant 1.0). Note: Elasticsearch defaults toavg— Pizza defaults tosum.