Nested field type
editNested field type
editThe nested
type is a specialised version of the object
data type
that allows arrays of objects to be indexed in a way that they can be queried
independently of each other.
When ingesting key-value pairs with a large, arbitrary set of keys, you might consider modeling each key-value pair as its own nested document with key
and value
fields. Instead, consider using the flattened data type, which maps an entire object as a single field and allows for simple searches over its contents.
Nested documents and queries are typically expensive, so using the flattened
data type for this use case is a better option.
Nested fields have incomplete support in Kibana. While they are visible and searchable in Discover, they cannot be used to build visualizations in Lens.
How arrays of objects are flattened
editElasticsearch has no concept of inner objects. Therefore, it flattens object hierarchies into a simple list of field names and values. For instance, consider the following document:
resp = client.index( index="my-index-000001", id="1", body={ "group": "fans", "user": [ {"first": "John", "last": "Smith"}, {"first": "Alice", "last": "White"}, ], }, ) print(resp)
response = client.index( index: 'my-index-000001', id: 1, body: { group: 'fans', user: [ { first: 'John', last: 'Smith' }, { first: 'Alice', last: 'White' } ] } ) puts response
res, err := es.Index( "my-index-000001", strings.NewReader(`{ "group": "fans", "user": [ { "first": "John", "last": "Smith" }, { "first": "Alice", "last": "White" } ] }`), es.Index.WithDocumentID("1"), es.Index.WithPretty(), ) fmt.Println(res, err)
PUT my-index-000001/_doc/1 { "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] }
The previous document would be transformed internally into a document that looks more like this:
{ "group" : "fans", "user.first" : [ "alice", "john" ], "user.last" : [ "smith", "white" ] }
The user.first
and user.last
fields are flattened into multi-value fields,
and the association between alice
and white
is lost. This document would
incorrectly match a query for alice AND smith
:
resp = client.search( index="my-index-000001", body={ "query": { "bool": { "must": [ {"match": {"user.first": "Alice"}}, {"match": {"user.last": "Smith"}}, ] } } }, ) print(resp)
response = client.search( index: 'my-index-000001', body: { query: { bool: { must: [ { match: { 'user.first' => 'Alice' } }, { match: { 'user.last' => 'Smith' } } ] } } } ) puts response
res, err := es.Search( es.Search.WithIndex("my-index-000001"), es.Search.WithBody(strings.NewReader(`{ "query": { "bool": { "must": [ { "match": { "user.first": "Alice" } }, { "match": { "user.last": "Smith" } } ] } } }`)), es.Search.WithPretty(), ) fmt.Println(res, err)
GET my-index-000001/_search { "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "Smith" }} ] } } }
Using nested
fields for arrays of objects
editIf you need to index arrays of objects and to maintain the independence of
each object in the array, use the nested
data type instead of the
object
data type.
Internally, nested objects index each object in
the array as a separate hidden document, meaning that each nested object can be
queried independently of the others with the nested
query:
resp = client.indices.create( index="my-index-000001", body={"mappings": {"properties": {"user": {"type": "nested"}}}}, ) print(resp) resp = client.index( index="my-index-000001", id="1", body={ "group": "fans", "user": [ {"first": "John", "last": "Smith"}, {"first": "Alice", "last": "White"}, ], }, ) print(resp) resp = client.search( index="my-index-000001", body={ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ {"match": {"user.first": "Alice"}}, {"match": {"user.last": "Smith"}}, ] } }, } } }, ) print(resp) resp = client.search( index="my-index-000001", body={ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ {"match": {"user.first": "Alice"}}, {"match": {"user.last": "White"}}, ] } }, "inner_hits": { "highlight": {"fields": {"user.first": {}}} }, } } }, ) print(resp)
response = client.indices.create( index: 'my-index-000001', body: { mappings: { properties: { user: { type: 'nested' } } } } ) puts response response = client.index( index: 'my-index-000001', id: 1, body: { group: 'fans', user: [ { first: 'John', last: 'Smith' }, { first: 'Alice', last: 'White' } ] } ) puts response response = client.search( index: 'my-index-000001', body: { query: { nested: { path: 'user', query: { bool: { must: [ { match: { 'user.first' => 'Alice' } }, { match: { 'user.last' => 'Smith' } } ] } } } } } ) puts response response = client.search( index: 'my-index-000001', body: { query: { nested: { path: 'user', query: { bool: { must: [ { match: { 'user.first' => 'Alice' } }, { match: { 'user.last' => 'White' } } ] } }, inner_hits: { highlight: { fields: { 'user.first' => {} } } } } } } ) puts response
{ res, err := es.Indices.Create( "my-index-000001", es.Indices.Create.WithBody(strings.NewReader(`{ "mappings": { "properties": { "user": { "type": "nested" } } } }`)), ) fmt.Println(res, err) } { res, err := es.Index( "my-index-000001", strings.NewReader(`{ "group": "fans", "user": [ { "first": "John", "last": "Smith" }, { "first": "Alice", "last": "White" } ] }`), es.Index.WithDocumentID("1"), es.Index.WithPretty(), ) fmt.Println(res, err) } { res, err := es.Search( es.Search.WithIndex("my-index-000001"), es.Search.WithBody(strings.NewReader(`{ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" } }, { "match": { "user.last": "Smith" } } ] } } } } }`)), es.Search.WithPretty(), ) fmt.Println(res, err) } { res, err := es.Search( es.Search.WithIndex("my-index-000001"), es.Search.WithBody(strings.NewReader(`{ "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" } }, { "match": { "user.last": "White" } } ] } }, "inner_hits": { "highlight": { "fields": { "user.first": {} } } } } } }`)), es.Search.WithPretty(), ) fmt.Println(res, err) }
PUT my-index-000001 { "mappings": { "properties": { "user": { "type": "nested" } } } } PUT my-index-000001/_doc/1 { "group" : "fans", "user" : [ { "first" : "John", "last" : "Smith" }, { "first" : "Alice", "last" : "White" } ] } GET my-index-000001/_search { "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "Smith" }} ] } } } } } GET my-index-000001/_search { "query": { "nested": { "path": "user", "query": { "bool": { "must": [ { "match": { "user.first": "Alice" }}, { "match": { "user.last": "White" }} ] } }, "inner_hits": { "highlight": { "fields": { "user.first": {} } } } } } }
Interacting with nested
documents
editNested documents can be:
-
queried with the
nested
query. -
analyzed with the
nested
andreverse_nested
aggregations. - sorted with nested sorting.
- retrieved and highlighted with nested inner hits.
Because nested documents are indexed as separate documents, they can only be
accessed within the scope of the nested
query, the
nested
/reverse_nested
aggregations, or nested inner hits.
For instance, if a string field within a nested document has
index_options
set to offsets
to allow use of the postings
during the highlighting, these offsets will not be available during the main highlighting
phase. Instead, highlighting needs to be performed via
nested inner hits. The same consideration applies when loading
fields during a search through docvalue_fields
or stored_fields
.
Parameters for nested
fields
editThe following parameters are accepted by nested
fields:
-
dynamic
-
(Optional, string)
Whether or not new
properties
should be added dynamically to an existing nested object. Acceptstrue
(default),false
andstrict
. -
properties
-
(Optional, object)
The fields within the nested object, which can be of any
data type, including
nested
. New properties may be added to an existing nested object. -
include_in_parent
-
(Optional, Boolean)
If
true
, all fields in the nested object are also added to the parent document as standard (flat) fields. Defaults tofalse
. -
include_in_root
-
(Optional, Boolean)
If
true
, all fields in the nested object are also added to the root document as standard (flat) fields. Defaults tofalse
.
Limits on nested
mappings and objects
editAs described earlier, each nested object is indexed as a separate Lucene document.
Continuing with the previous example, if we indexed a single document containing 100 user
objects,
then 101 Lucene documents would be created: one for the parent document, and one for each
nested object. Because of the expense associated with nested
mappings, Elasticsearch puts
settings in place to guard against performance problems:
-
index.mapping.nested_fields.limit
-
The maximum number of distinct
nested
mappings in an index. Thenested
type should only be used in special cases, when arrays of objects need to be queried independently of each other. To safeguard against poorly designed mappings, this setting limits the number of uniquenested
types per index. Default is50
.
In the previous example, the user
mapping would count as only 1 towards this limit.
-
index.mapping.nested_objects.limit
-
The maximum number of nested JSON objects that a single document can contain across all
nested
types. This limit helps to prevent out of memory errors when a document contains too many nested objects. Default is10000
.
To illustrate how this setting works, consider adding another nested
type called comments
to the previous example mapping. For each document, the combined number of user
and comment
objects it contains must be below the limit.
See Settings to prevent mapping explosion regarding additional settings for preventing mappings explosion.