﻿---
title: Nested approximate kNN search
description: Run approximate kNN search on nested dense_vector fields for passage retrieval, filtering, inner hits, and chunked content in Elasticsearch.
url: https://www.elastic.co/elastic/docs-builder/docs/4300/solutions/search/vector/knn/nested-knn-search
products:
  - Elastic Documentation
applies_to:
  - Elastic Cloud Serverless: Generally available
  - Elastic Stack: Generally available
---

# Nested approximate kNN search
Nested approximate kNN search lets you find the most relevant passage or chunk inside long documents by storing a separate vector for each nested section and returning parent documents ranked by their best match. This approach is useful when a single document is too long to embed as one vector, such as when a support portal needs to surface the most relevant paragraph from a long troubleshooting guide in response to a user question.
This page covers a basic mapping and query example, filtering, inner hits, and chunked content retrieval. For other approximate kNN query examples, refer to [Approximate kNN query examples](https://www.elastic.co/elastic/docs-builder/docs/4300/solutions/search/vector/knn/approximate-knn-query-examples).

## Run a basic nested approximate kNN search

When text exceeds a model’s token limit, chunking must be performed before generating embeddings for each chunk. By combining [`nested`](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/mapping-reference/nested) fields with [`dense_vector`](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/mapping-reference/dense-vector), you can perform nearest passage retrieval without copying top-level document metadata.
<note>
  Nested kNN queries only support [score_mode](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/query-languages/query-dsl/query-dsl-nested-query#nested-top-level-params)=`max`.
</note>

Here is a basic passage vectors index that stores vectors and some top-level metadata for filtering.
```json

{
    "mappings": {
        "properties": {
            "full_text": {
                "type": "text"
            },
            "creation_time": {
                "type": "date"
            },
            "paragraph": {
                "type": "nested",
                "properties": {
                    "vector": {
                        "type": "dense_vector",
                        "dims": 2,
                        "index_options": {
                            "type": "hnsw"
                        }
                    },
                    "text": {
                        "type": "text",
                        "index": false
                    },
                    "language": {
                        "type": "keyword"
                    }
                }
            },
            "metadata": {
                "type": "nested",
                "properties": {
                    "key": {
                        "type": "keyword"
                    },
                    "value": {
                        "type": "text"
                    }
                }
            }
        }
    }
}
```

With the above mapping, you can index multiple passage vectors along with storing the individual passage text.
```json

{ "index": { "_id": "1" } }
{ "full_text": "first paragraph another paragraph", "creation_time": "2019-05-04", "paragraph": [ { "vector": [ 0.45, 45 ], "text": "first paragraph", "paragraph_id": "1", "language": "EN" }, { "vector": [ 0.8, 0.6 ], "text": "another paragraph", "paragraph_id": "2", "language": "FR" } ], "metadata": [ { "key": "author", "value": "Jane Doe" }, { "key": "source", "value": "Internal Memo" } ] }
{ "index": { "_id": "2" } }
{ "full_text": "number one paragraph number two paragraph", "creation_time": "2020-05-04", "paragraph": [ { "vector": [ 1.2, 4.5 ], "text": "number one paragraph", "paragraph_id": "1", "language": "EN" }, { "vector": [ -1, 42 ], "text": "number two paragraph", "paragraph_id": "2", "language": "EN" }] , "metadata": [ { "key": "author", "value": "Jane Austen" }, { "key": "source", "value": "Financial" } ] }
```

The query uses the same structure as a typical kNN search:
```json

{
    "fields": ["full_text", "creation_time"],
    "_source": false,
    "knn": {
        "query_vector": [
            0.45,
            45
        ],
        "field": "paragraph.vector",
        "k": 2
    }
}
```

Note that even with 4 total nested vectors, the response still returns two documents. Approximate kNN search over nested dense vectors will always diversify the top results over the top-level document. `"k"` top-level documents will be returned, scored by their nearest passage vector (for example, `"paragraph.vector"`).
```json
{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "passage_vectors",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "creation_time": [
                        "2019-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "first paragraph another paragraph"
                    ]
                }
            },
            {
                "_index": "passage_vectors",
                "_id": "2",
                "_score": 0.9997144,
                "fields": {
                    "creation_time": [
                        "2020-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "number one paragraph number two paragraph"
                    ]
                }
            }
        ]
    }
}
```


## Filter in nested approximate kNN search

Use filters in nested kNN search when you want the most similar passages, but only from documents or chunks that match specific criteria. For example, you might search for relevant paragraphs in documents created in a date range, written in a particular language, or authored by a specific person.
Add a `filter` to your `knn` clause to apply these restrictions during the search.
To ensure correct results, each individual filter must target either:
- Top-level metadata
- `nested` metadata <applies-to>Elastic Stack: Generally available since 9.2</applies-to>
  <note>
  A single `knn` search can include multiple filters: some over top-level metadata and others over nested metadata.
  </note>

```json

{
    "fields": [
        "creation_time",
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [0.45, 45],
        "field": "paragraph.vector",
        "k": 2,
        "filter": {
            "range": {
                "creation_time": {
                    "gte": "2019-05-01",
                    "lte": "2019-05-05"
                }
            }
        }
    }
}
```

With the top-level `creation_time` filter applied, only document `1` falls within the specified range, so the response contains a single hit.

## Filter on nested metadata

<applies-to>
  - Elastic Stack: Generally available since 9.2
</applies-to>

Filter on nested metadata when your criteria apply to individual passages or chunks, not the whole document. For example, you might search for similar paragraphs but only among sections in a specific language or tagged with a particular category.
The following query filters on `paragraph.language` so parent documents are scored only from nested vectors where the language is `EN`.
```json

{
    "fields": [
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [0.45, 45],
        "field": "paragraph.vector",
        "k": 2,
        "filter": {
            "match": {
                "paragraph.language": "EN"
            }
        }
    }
}
```

The next example combines two filters: one on nested metadata and one on top-level metadata. Parent documents are scored only by vectors with "paragraph.language": "EN" and whose parent documents fall within the specified time range.
```json

{
    "fields": [
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [0.45,45],
        "field": "paragraph.vector",
        "k": 2,
        "filter": [
            {"match": {"paragraph.language": "EN"}},
            {"range": { "creation_time": { "gte": "2019-05-01", "lte": "2019-05-05"}}}
        ]
    }
}
```


## Filter by sibling nested fields

<applies-to>
  - Elastic Stack: Generally available since 9.2
</applies-to>

Filter by sibling nested fields when passage vectors and the metadata you want to filter on live in separate nested structures within the same document. For example, you might search `paragraph.vector` for similar passages but only in documents whose `metadata` nested field lists a specific author or source.
Use a `nested` query in the `filter` clause to target the sibling nested field, such as `metadata.key` and `metadata.value`.
<important>
  Sibling nested field filtering is only supported with the **top-level `knn` section** shown in the examples on this page. It does not work when using a [`knn` query](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/query-languages/query-dsl/query-dsl-knn-query) inside a `nested` query. Retrieving `inner_hits` when filtering on sibling nested fields is also not supported.
</important>

```json

{
    "fields": [
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [0.45, 45],
        "field": "paragraph.vector",
        "k": 2,
        "filter": {
            "nested": {
                "path": "metadata",
                "query": {
                    "bool": {
                        "must": [
                            { "match": { "metadata.key": "author" } },
                            { "match": { "metadata.value": "Doe" } }
                        ]
                    }
                }
            }
        }
    }
}
```


## Nested approximate kNN search with inner hits

Use `inner_hits` when nested approximate kNN search should return both the matching parent document and the specific passage that produced the score.
Add [inner_hits](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/rest-apis/retrieve-inner-hits) to the `knn` clause to include the nearest matching nested passage in the response.
<note>
  When using `inner_hits` with multiple `knn` clauses, set a unique [`inner_hits.name`](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/rest-apis/retrieve-inner-hits#inner-hits-options) for each clause to avoid naming collisions that would fail the search request.
</note>

```json

{
    "fields": [
        "creation_time",
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [
            0.45,
            45
        ],
        "field": "paragraph.vector",
        "k": 2,
        "num_candidates": 2,
        "inner_hits": {
            "_source": false,
            "fields": [
                "paragraph.text"
            ],
            "size": 1
        }
    }
}
```

The response now includes an `inner_hits` section with the nearest matching passage for each parent document.
```json
{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "passage_vectors",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "creation_time": [
                        "2019-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "first paragraph another paragraph"
                    ]
                },
                "inner_hits": {
                    "paragraph": {
                        "hits": {
                            "total": {
                                "value": 2,
                                "relation": "eq"
                            },
                            "max_score": 1.0,
                            "hits": [
                                {
                                    "_index": "passage_vectors",
                                    "_id": "1",
                                    "_nested": {
                                        "field": "paragraph",
                                        "offset": 0
                                    },
                                    "_score": 1.0,
                                    "fields": {
                                        "paragraph": [
                                            {
                                                "text": [
                                                    "first paragraph"
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {
                "_index": "passage_vectors",
                "_id": "2",
                "_score": 0.9997144,
                "fields": {
                    "creation_time": [
                        "2020-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "number one paragraph number two paragraph"
                    ]
                },
                "inner_hits": {
                    "paragraph": {
                        "hits": {
                            "total": {
                                "value": 2,
                                "relation": "eq"
                            },
                            "max_score": 0.9997144,
                            "hits": [
                                {
                                    "_index": "passage_vectors",
                                    "_id": "2",
                                    "_nested": {
                                        "field": "paragraph",
                                        "offset": 1
                                    },
                                    "_score": 0.9997144,
                                    "fields": {
                                        "paragraph": [
                                            {
                                                "text": [
                                                    "number two paragraph"
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                }
            }
        ]
    }
}
```


## Chunked content retrieval

The patterns on this page apply directly to chunked content retrieval. Whether you chunk documents into paragraphs, sections, or other structures, the approach is the same: store each chunk's vector in a `nested` field and use `inner_hits` to return the most relevant chunk per document. If you use [`semantic_text`](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/mapping-reference/semantic-text) fields, chunking and embedding are handled automatically. Use nested `dense_vector` fields when you need control over the chunking strategy, the embedding model, or chunk-level metadata filtering. For custom models, use the [basic nested kNN example](#nested-knn-basic-example) and [inner hits](#nested-knn-search-inner-hits) patterns on this page.

## Resources

- [Tune approximate kNN search](https://www.elastic.co/elastic/docs-builder/docs/4300/deploy-manage/production-guidance/optimize-performance/approximate-knn-search): Production guidance for vector memory, node sizing, indexing, filesystem cache, and on-disk rescoring.
- [Profile kNN search](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/rest-apis/search-profile#profiling-knn-search): Inspect query timing and vector operation counts to diagnose slow kNN searches.
- [`dense_vector` field type](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/elasticsearch/mapping-reference/dense-vector): API reference for vector field mapping, including `index`, `similarity`, `index_options`, and quantization parameters.
- [`knn` query](https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/4300/reference/query-languages/query-dsl/query-dsl-knn-query): API reference for the `knn` query, including parameters, `query_vector_builder` options, and usage with `dense_vector` and `semantic_text` fields.