Тип поля Nested
Тип nested — это специализированная версия типа данных object, которая позволяет индексировать массивы объектов таким образом, что к ним можно обращаться независимо друг от друга при запросе.
При обработке пар «ключ-значение» с большим произвольным набором ключей, вы можете рассмотреть возможность моделирования каждой пары «ключ-значение» как отдельного вложенного документа с полями key и value. Вместо этого, рассмотрите использование типа данных flattened, который отображает весь объект как одно поле и позволяет выполнять простые поиски по его содержимому. Вложенные документы и запросы обычно требуют больших затрат ресурсов, поэтому для этого случая лучше использовать тип данных flattened.
В Kibana поддержка вложенных полей неполная. Хотя они отображаются и доступны для поиска в Discover, их нельзя использовать для построения визуализаций в Lens.
Как сглаживаются массивы объектов
В Elasticsearch нет понятия внутренних объектов. Поэтому иерархия объектов сглаживается в простой список имён и значений полей. Например, рассмотрите следующий документ:
resp = client.index(
index="my-index-000001",
id="1",
document={
"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) const response = await client.index({
index: "my-index-000001",
id: 1,
document: {
group: "fans",
user: [
{
first: "John",
last: "Smith",
},
{
first: "Alice",
last: "White",
},
],
},
});
console.log(response); PUT my-index-000001/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
} | Поле |
Предыдущий документ будет преобразован во внутренний документ, который выглядит примерно так:
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
} Поля user.first и user.last сглаживаются в поля с множественными значениями, и связь между alice и white теряется. Этот документ неверно соответствовал бы запросу на alice AND smith:
resp = client.search(
index="my-index-000001",
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) const response = await client.search({
index: "my-index-000001",
query: {
bool: {
must: [
{
match: {
"user.first": "Alice",
},
},
{
match: {
"user.last": "Smith",
},
},
],
},
},
});
console.log(response); GET my-index-000001/_search
{
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" }},
{ "match": { "user.last": "Smith" }}
]
}
}
} Использование полей nested для массивов объектов
Если вам нужно индексировать массивы объектов и сохранить независимость каждого объекта в массиве, используйте тип данных nested вместо типа данных object.
Внутренне, вложенные объекты индексируют каждый объект в массиве как отдельный скрытый документ, что означает, что каждый вложенный объект может быть запрошен независимо от других с помощью запроса nested:
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"user": {
"type": "nested"
}
}
},
)
print(resp)
resp1 = client.index(
index="my-index-000001",
id="1",
document={
"group": "fans",
"user": [
{
"first": "John",
"last": "Smith"
},
{
"first": "Alice",
"last": "White"
}
]
},
)
print(resp1)
resp2 = client.search(
index="my-index-000001",
query={
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{
"match": {
"user.first": "Alice"
}
},
{
"match": {
"user.last": "Smith"
}
}
]
}
}
}
},
)
print(resp2)
resp3 = client.search(
index="my-index-000001",
query={
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{
"match": {
"user.first": "Alice"
}
},
{
"match": {
"user.last": "White"
}
}
]
}
},
"inner_hits": {
"highlight": {
"fields": {
"user.first": {}
}
}
}
}
},
)
print(resp3) 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)
} const response = await client.indices.create({
index: "my-index-000001",
mappings: {
properties: {
user: {
type: "nested",
},
},
},
});
console.log(response);
const response1 = await client.index({
index: "my-index-000001",
id: 1,
document: {
group: "fans",
user: [
{
first: "John",
last: "Smith",
},
{
first: "Alice",
last: "White",
},
],
},
});
console.log(response1);
const response2 = await client.search({
index: "my-index-000001",
query: {
nested: {
path: "user",
query: {
bool: {
must: [
{
match: {
"user.first": "Alice",
},
},
{
match: {
"user.last": "Smith",
},
},
],
},
},
},
},
});
console.log(response2);
const response3 = await client.search({
index: "my-index-000001",
query: {
nested: {
path: "user",
query: {
bool: {
must: [
{
match: {
"user.first": "Alice",
},
},
{
match: {
"user.last": "White",
},
},
],
},
},
inner_hits: {
highlight: {
fields: {
"user.first": {},
},
},
},
},
},
});
console.log(response3); 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": {}
}
}
}
}
}
} | Поле | |
| Этот запрос не соответствует, потому что | |
| Этот запрос соответствует, потому что | |
|
|
Взаимодействие с документами nested
Вложенные документы могут:
- быть запрошены с помощью запроса
nested. - быть проанализированы с помощью агрегаций
nestedиreverse_nested. - быть отсортированы с помощью сортировки по вложенным объектам.
- быть получены и выделены с помощью вложенных внутренних результатов.
Поскольку вложенные документы индексируются как отдельные документы, к ним можно получить доступ только в рамках запроса nested, агрегаций nested/reverse_nested или вложенных внутренних результатов.
Например, если строковое поле внутри вложенного документа имеет index_options установленное в значение offsets, чтобы разрешить использование индексов во время выделения, эти смещения не будут доступны во время основной фазы выделения. Вместо этого, выделение необходимо выполнять с помощью вложенных внутренних результатов. Такое же ограничение действует при загрузке полей во время поиска через docvalue_fields или stored_fields.
Параметры для полей nested
Следующие параметры принимаются полями nested:
-
dynamic - (Необязательно, строка) Указывает, добавляются ли новые
propertiesдинамически в существующий вложенный объект. Принимает значенияtrue(по умолчанию),falseиstrict. -
properties - (Необязательно, объект) Поля внутри вложенного объекта, которые могут быть любого типа данных, включая
nested. Новые свойства могут быть добавлены в существующий вложенный объект. -
include_in_parent - (Необязательно, логическое значение) Если
true, все поля вложенного объекта также добавляются в родительский документ как стандартные (плоские) поля. По умолчаниюfalse. -
include_in_root - (Необязательно, логическое значение) Если
true, все поля вложенного объекта также добавляются в корневой документ как стандартные (плоские) поля. По умолчаниюfalse.
Ограничения на карты nested и объекты
Как описано ранее, каждый вложенный объект индексируется как отдельный документ Lucene. Продолжая предыдущий пример, если мы проиндексировали один документ, содержащий 100 объектов user, то было бы создано 101 документ Lucene: один для родительского документа и один для каждого вложенного объекта. Из-за затрат, связанных с картами nested, Elasticsearch устанавливает параметры для предотвращения проблем с производительностью:
-
index.mapping.nested_fields.limit - Максимальное количество уникальных карт
nestedв индексе. Типnestedследует использовать только в особых случаях, когда массивы объектов необходимо запросить независимо друг от друга. Для защиты от плохо спроектированных карт это ограничение устанавливается на количество уникальных типовnestedна индекс. По умолчанию50.
В предыдущем примере карта user учитывается только как 1 в этом ограничении.
-
index.mapping.nested_objects.limit - Максимальное количество вложенных JSON-объектов, которые может содержать один документ во всех типах
nested. Это ограничение помогает предотвратить ошибки из-за нехватки памяти, когда документ содержит слишком много вложенных объектов. По умолчанию10000.
Чтобы проиллюстрировать, как работает этот параметр, добавьте другой тип nested, например, comments, в предыдущую карту. Для каждого документа общее количество объектов user и comment должно быть ниже предела.
См. Предотвращение взрыва карт для получения дополнительной информации о параметрах предотвращения взрыва карт.
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/nested.html