Spec-Zone.ru › Elasticsearch 8
›Elasticsearch Guide [8.17] ›Картирование ›Типы данных полей

Тип поля 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"
    }
  ]
}

Поле user динамически добавляется как поле типа object.

Предыдущий документ будет преобразован во внутренний документ, который выглядит примерно так:

{
  "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": {}
          }
        }
      }
    }
  }
}

Поле user отображается как тип nested вместо типа object.

Этот запрос не соответствует, потому что Alice и Smith не находятся в одном вложенном объекте.

Этот запрос соответствует, потому что Alice и White находятся в одном вложенном объекте.

inner_hits позволяют выделить соответствующие вложенные документы.

Взаимодействие с документами 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API