ネストされたフィールドタイプ
nested
タイプは、object
データタイプの特別なバージョンであり、オブジェクトの配列をインデックス化し、それぞれを独立してクエリできるようにします。
大規模で任意のキーセットを持つキー-バリューペアを取り込む際には、各キー-バリューペアを key
および value
フィールドを持つ独自のネストされたドキュメントとしてモデル化することを検討するかもしれません。代わりに、フラット化された データタイプを使用することを検討してください。これは、全体のオブジェクトを単一のフィールドとしてマッピングし、その内容に対して簡単な検索を可能にします。ネストされたドキュメントとクエリは通常高コストであるため、このユースケースには flattened
データタイプを使用する方が良い選択肢です。
ネストされたフィールドは Kibana でのサポートが不完全です。Discover では表示および検索可能ですが、Lens でのビジュアライゼーションの構築には使用できません。
オブジェクトの配列がフラット化される方法
Elasticsearch には内部オブジェクトの概念がありません。したがって、オブジェクトの階層をフィールド名と値の単純なリストにフラット化します。たとえば、次のドキュメントを考えてみてください:
Python
resp = client.index(
index="my-index-000001",
id="1",
document={
"group": "fans",
"user": [
{
"first": "John",
"last": "Smith"
},
{
"first": "Alice",
"last": "White"
}
]
},
)
print(resp)
Ruby
response = client.index(
index: 'my-index-000001',
id: 1,
body: {
group: 'fans',
user: [
{
first: 'John',
last: 'Smith'
},
{
first: 'Alice',
last: 'White'
}
]
}
)
puts response
Go
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)
Js
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 タイプのフィールドとして動的に追加されます。 |
前のドキュメントは、内部的に次のようなドキュメントに変換されます:
Js
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
user.first
および user.last
フィールドはマルチバリューフィールドにフラット化され、alice
と white
の関連付けが失われます。このドキュメントは、alice AND smith
のクエリと不正に一致します:
Python
resp = client.search(
index="my-index-000001",
query={
"bool": {
"must": [
{
"match": {
"user.first": "Alice"
}
},
{
"match": {
"user.last": "Smith"
}
}
]
}
},
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
body: {
query: {
bool: {
must: [
{
match: {
'user.first' => 'Alice'
}
},
{
match: {
'user.last' => 'Smith'
}
}
]
}
}
}
)
puts response
Go
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)
Js
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" }}
]
}
}
}
オブジェクトの配列に対するネストされたフィールドの使用
オブジェクトの配列をインデックス化し、配列内の各オブジェクトの独立性を維持する必要がある場合は、object
データタイプの代わりに nested
データタイプを使用してください。
内部的に、ネストされたオブジェクトは配列内の各オブジェクトを別々の隠れたドキュメントとしてインデックス化します。つまり、各ネストされたオブジェクトは、nested
クエリ を使用して他のオブジェクトとは独立してクエリできます:
Python
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)
Ruby
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
Go
{
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)
}
Js
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
およびreverse_nested
集約で分析されます。- ネストされたソート でソートされます。
- ネストされたインナーヒット で取得およびハイライトされます。
ネストされたドキュメントは別々のドキュメントとしてインデックス化されるため、nested
クエリ、nested
/reverse_nested
集約、または ネストされたインナーヒット の範囲内でのみアクセスできます。
たとえば、ネストされたドキュメント内の文字列フィールドが、ハイライト中にポスティングを使用できるように index_options
](/read/elasticsearch-8-15/839bf080f94155d6.md) に offsets
が設定されている場合、これらのオフセットはメインのハイライトフェーズ中には利用できません。代わりに、ハイライトは ネストされたインナーヒット を介して実行する必要があります。同様の考慮事項は、docvalue_fields
または stored_fields
を介して検索中にフィールドを読み込む際にも適用されます。
ネストされたフィールドのパラメータ
nested
フィールドで受け入れられるパラメータは次のとおりです:
dynamic
- (オプション、文字列) 既存のネストされたオブジェクトに新しい
properties
を動的に追加するかどうか。true
(デフォルト)、false
およびstrict
を受け入れます。 properties
- (オプション、オブジェクト) ネストされたオブジェクト内のフィールドで、
nested
を含む任意の データタイプ である可能性があります。新しいプロパティは既存のネストされたオブジェクトに追加できます。 include_in_parent
- (オプション、ブール値)
true
の場合、ネストされたオブジェクト内のすべてのフィールドも親ドキュメントに標準 (フラット) フィールドとして追加されます。デフォルトはfalse
です。 include_in_root
- (オプション、ブール値)
true
の場合、ネストされたオブジェクト内のすべてのフィールドもルートドキュメントに標準 (フラット) フィールドとして追加されます。デフォルトはfalse
です。
ネストされたマッピングとオブジェクトの制限
前述のように、各ネストされたオブジェクトは別々の Lucene ドキュメントとしてインデックス化されます。前の例を続けると、100 user
オブジェクトを含む単一のドキュメントをインデックス化した場合、101 の Lucene ドキュメントが作成されます: 親ドキュメント用の 1 つと、各ネストされたオブジェクト用の 1 つです。nested
マッピングに関連するコストのため、Elasticsearch はパフォーマンスの問題を防ぐための設定を設けています:
index.mapping.nested_fields.limit
- インデックス内の異なる
nested
マッピングの最大数。nested
タイプは、オブジェクトの配列を互いに独立してクエリする必要がある特別な場合にのみ使用されるべきです。設計が不適切なマッピングを防ぐために、この設定はインデックスごとのユニークなnested
タイプの数を制限します。デフォルトは50
です。
前の例では、user
マッピングはこの制限に対して 1 つとしてカウントされます。
index.mapping.nested_objects.limit
- 単一のドキュメントがすべての
nested
タイプにわたって含むことができるネストされた JSON オブジェクトの最大数。この制限は、ドキュメントが多くのネストされたオブジェクトを含む場合のメモリエラーを防ぐのに役立ちます。デフォルトは10000
です。
この設定がどのように機能するかを示すために、前の例のマッピングに nested
タイプの comments
を追加することを考えてみてください。各ドキュメントについて、user
と comment
オブジェクトの合計数は制限を下回る必要があります。
マッピングの爆発を防ぐための追加設定については、マッピング爆発を防ぐための設定 を参照してください。