Nested query #
Matches parent documents whose nested sub-documents — objects mapped
with "nested": true — satisfy a query within one element at a time.
It exists because a flattened object index cannot keep two field values
inside the same array element together — the pitfall the next section
makes concrete.
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):
graph LR
subgraph flat["flattened — one shared field space"]
F1("variants[0] is blue")
F2("variants[1] is M")
F1 --> FM["matches — 10 documents
the two values may come
from different elements"]
F2 --> FM
end
subgraph nest["nested — one element at a time"]
N1("variants[0]
blue, L — no M")
N2("variants[1]
red, M — no blue")
N1 --> NM["no match — 0 documents"]
N2 --> NM
end
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,phrase(also accepted:match_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": {
"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.
How it works #
nested rides the same block pipeline as every other query, with one
extra channel — the ordinal that identifies which array element
produced a posting:
- The inner query resolves against ordinal-tagged postings, where
each entry is a
(doc_id, ordinal, score)triple instead of the usual(doc_id, score). - Boolean combination happens on the composite key: intersection and
union match on
(doc_id, ordinal)pairs, so two clauses agree only when ONE element satisfies both — never element 0’s color plus element 1’s size. - A collapse producer folds the per-element matches back to unique
parent ids, combining the per-element scores by
score_mode.
Flat queries never touch the ordinal channel — the same block operators fall back to plain doc-id intersection — so non-nested traffic pays nothing for the feature. Documents still in the realtime layer (not yet frozen into segments) take the per-element stored-array path described under Performance characteristics below.
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.
Use cases #
- Variant correlation. The
productspair above — color and size must describe one SKU. Any catalog with option arrays has this shape; the flattened query overstates the match count (10 vs 0 in the teaching data). - Per-review constraints. “Reviews containing ‘quick fox’ AND rated
= 4” must hold within one review, not across the array — a phrase and a range scoped to the same element.
- Line-item queries. Orders and invoices whose line items pair a SKU with a quantity or a price band — the same-element AND, per line.
- Explaining the match.
inner_hitsreturns the exact elements that matched (identity, source, score), so a UI can highlight the blue/L variant instead of the whole product. - Sub-object nesting.
nestedinsidenestedwalks multi-level arrays (variant → components) with the same element-at-a-time rule at each level.
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).inner_hits
(Optional, object) When present, every top-level hit carries aninner_hitsblock keyed by the path: the matched ELEMENTS of that document’s nested array, each with_nested: {field, offset}identity, the element_source, and its score (default window: 3,from/sizeadjustable). Sub-nested requests materialize recursively inside each inner hit. An optionalhighlightblock highlights inner hits against the inner query; multi-field sub-fields (field.vectors) fall back to the parent field’s text.
Related #
join— the cross-collection counterpart: membership by key against another collection, instead of correlation inside one document.traverseandgraph— relations between documents over arelationedge field.- Nested aggregation — bucket analytics over the same nested structure.
- Children aggregation — the parent/child aggregation over joined hierarchies.
- Relational parameters
— the schema family (
references,ref,routing) the cross-collection pages build on.