ネストされたフィールドタイプ

nested タイプは、object データタイプの特別なバージョンであり、オブジェクトの配列をインデックス化し、それぞれを独立してクエリできるようにします。

大規模で任意のキーセットを持つキー-バリューペアを取り込む際には、各キー-バリューペアを key および value フィールドを持つ独自のネストされたドキュメントとしてモデル化することを検討するかもしれません。代わりに、フラット化された データタイプを使用することを検討してください。これは、全体のオブジェクトを単一のフィールドとしてマッピングし、その内容に対して簡単な検索を可能にします。ネストされたドキュメントとクエリは通常高コストであるため、このユースケースには flattened データタイプを使用する方が良い選択肢です。

ネストされたフィールドは Kibana でのサポートが不完全です。Discover では表示および検索可能ですが、Lens でのビジュアライゼーションの構築には使用できません。

オブジェクトの配列がフラット化される方法

Elasticsearch には内部オブジェクトの概念がありません。したがって、オブジェクトの階層をフィールド名と値の単純なリストにフラット化します。たとえば、次のドキュメントを考えてみてください:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="1",
  4. document={
  5. "group": "fans",
  6. "user": [
  7. {
  8. "first": "John",
  9. "last": "Smith"
  10. },
  11. {
  12. "first": "Alice",
  13. "last": "White"
  14. }
  15. ]
  16. },
  17. )
  18. print(resp)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. group: 'fans',
  6. user: [
  7. {
  8. first: 'John',
  9. last: 'Smith'
  10. },
  11. {
  12. first: 'Alice',
  13. last: 'White'
  14. }
  15. ]
  16. }
  17. )
  18. puts response

Go

  1. res, err := es.Index(
  2. "my-index-000001",
  3. strings.NewReader(`{
  4. "group": "fans",
  5. "user": [
  6. {
  7. "first": "John",
  8. "last": "Smith"
  9. },
  10. {
  11. "first": "Alice",
  12. "last": "White"
  13. }
  14. ]
  15. }`),
  16. es.Index.WithDocumentID("1"),
  17. es.Index.WithPretty(),
  18. )
  19. fmt.Println(res, err)

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 1,
  4. document: {
  5. group: "fans",
  6. user: [
  7. {
  8. first: "John",
  9. last: "Smith",
  10. },
  11. {
  12. first: "Alice",
  13. last: "White",
  14. },
  15. ],
  16. },
  17. });
  18. console.log(response);

コンソール

  1. PUT my-index-000001/_doc/1
  2. {
  3. "group" : "fans",
  4. "user" : [
  5. {
  6. "first" : "John",
  7. "last" : "Smith"
  8. },
  9. {
  10. "first" : "Alice",
  11. "last" : "White"
  12. }
  13. ]
  14. }
user フィールドは、object タイプのフィールドとして動的に追加されます。

前のドキュメントは、内部的に次のようなドキュメントに変換されます:

Js

  1. {
  2. "group" : "fans",
  3. "user.first" : [ "alice", "john" ],
  4. "user.last" : [ "smith", "white" ]
  5. }

user.first および user.last フィールドはマルチバリューフィールドにフラット化され、alicewhite の関連付けが失われます。このドキュメントは、alice AND smith のクエリと不正に一致します:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. query={
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "user.first": "Alice"
  9. }
  10. },
  11. {
  12. "match": {
  13. "user.last": "Smith"
  14. }
  15. }
  16. ]
  17. }
  18. },
  19. )
  20. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. body: {
  4. query: {
  5. bool: {
  6. must: [
  7. {
  8. match: {
  9. 'user.first' => 'Alice'
  10. }
  11. },
  12. {
  13. match: {
  14. 'user.last' => 'Smith'
  15. }
  16. }
  17. ]
  18. }
  19. }
  20. }
  21. )
  22. puts response

Go

  1. res, err := es.Search(
  2. es.Search.WithIndex("my-index-000001"),
  3. es.Search.WithBody(strings.NewReader(`{
  4. "query": {
  5. "bool": {
  6. "must": [
  7. {
  8. "match": {
  9. "user.first": "Alice"
  10. }
  11. },
  12. {
  13. "match": {
  14. "user.last": "Smith"
  15. }
  16. }
  17. ]
  18. }
  19. }
  20. }`)),
  21. es.Search.WithPretty(),
  22. )
  23. fmt.Println(res, err)

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. query: {
  4. bool: {
  5. must: [
  6. {
  7. match: {
  8. "user.first": "Alice",
  9. },
  10. },
  11. {
  12. match: {
  13. "user.last": "Smith",
  14. },
  15. },
  16. ],
  17. },
  18. },
  19. });
  20. console.log(response);

コンソール

  1. GET my-index-000001/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. { "match": { "user.first": "Alice" }},
  7. { "match": { "user.last": "Smith" }}
  8. ]
  9. }
  10. }
  11. }

オブジェクトの配列に対するネストされたフィールドの使用

オブジェクトの配列をインデックス化し、配列内の各オブジェクトの独立性を維持する必要がある場合は、object データタイプの代わりに nested データタイプを使用してください。

内部的に、ネストされたオブジェクトは配列内の各オブジェクトを別々の隠れたドキュメントとしてインデックス化します。つまり、各ネストされたオブジェクトは、nested クエリ を使用して他のオブジェクトとは独立してクエリできます:

Python

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "user": {
  6. "type": "nested"
  7. }
  8. }
  9. },
  10. )
  11. print(resp)
  12. resp1 = client.index(
  13. index="my-index-000001",
  14. id="1",
  15. document={
  16. "group": "fans",
  17. "user": [
  18. {
  19. "first": "John",
  20. "last": "Smith"
  21. },
  22. {
  23. "first": "Alice",
  24. "last": "White"
  25. }
  26. ]
  27. },
  28. )
  29. print(resp1)
  30. resp2 = client.search(
  31. index="my-index-000001",
  32. query={
  33. "nested": {
  34. "path": "user",
  35. "query": {
  36. "bool": {
  37. "must": [
  38. {
  39. "match": {
  40. "user.first": "Alice"
  41. }
  42. },
  43. {
  44. "match": {
  45. "user.last": "Smith"
  46. }
  47. }
  48. ]
  49. }
  50. }
  51. }
  52. },
  53. )
  54. print(resp2)
  55. resp3 = client.search(
  56. index="my-index-000001",
  57. query={
  58. "nested": {
  59. "path": "user",
  60. "query": {
  61. "bool": {
  62. "must": [
  63. {
  64. "match": {
  65. "user.first": "Alice"
  66. }
  67. },
  68. {
  69. "match": {
  70. "user.last": "White"
  71. }
  72. }
  73. ]
  74. }
  75. },
  76. "inner_hits": {
  77. "highlight": {
  78. "fields": {
  79. "user.first": {}
  80. }
  81. }
  82. }
  83. }
  84. },
  85. )
  86. print(resp3)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  5. properties: {
  6. user: {
  7. type: 'nested'
  8. }
  9. }
  10. }
  11. }
  12. )
  13. puts response
  14. response = client.index(
  15. index: 'my-index-000001',
  16. id: 1,
  17. body: {
  18. group: 'fans',
  19. user: [
  20. {
  21. first: 'John',
  22. last: 'Smith'
  23. },
  24. {
  25. first: 'Alice',
  26. last: 'White'
  27. }
  28. ]
  29. }
  30. )
  31. puts response
  32. response = client.search(
  33. index: 'my-index-000001',
  34. body: {
  35. query: {
  36. nested: {
  37. path: 'user',
  38. query: {
  39. bool: {
  40. must: [
  41. {
  42. match: {
  43. 'user.first' => 'Alice'
  44. }
  45. },
  46. {
  47. match: {
  48. 'user.last' => 'Smith'
  49. }
  50. }
  51. ]
  52. }
  53. }
  54. }
  55. }
  56. }
  57. )
  58. puts response
  59. response = client.search(
  60. index: 'my-index-000001',
  61. body: {
  62. query: {
  63. nested: {
  64. path: 'user',
  65. query: {
  66. bool: {
  67. must: [
  68. {
  69. match: {
  70. 'user.first' => 'Alice'
  71. }
  72. },
  73. {
  74. match: {
  75. 'user.last' => 'White'
  76. }
  77. }
  78. ]
  79. }
  80. },
  81. inner_hits: {
  82. highlight: {
  83. fields: {
  84. 'user.first' => {}
  85. }
  86. }
  87. }
  88. }
  89. }
  90. }
  91. )
  92. puts response

Go

  1. {
  2. res, err := es.Indices.Create(
  3. "my-index-000001",
  4. es.Indices.Create.WithBody(strings.NewReader(`{
  5. "mappings": {
  6. "properties": {
  7. "user": {
  8. "type": "nested"
  9. }
  10. }
  11. }
  12. }`)),
  13. )
  14. fmt.Println(res, err)
  15. }
  16. {
  17. res, err := es.Index(
  18. "my-index-000001",
  19. strings.NewReader(`{
  20. "group": "fans",
  21. "user": [
  22. {
  23. "first": "John",
  24. "last": "Smith"
  25. },
  26. {
  27. "first": "Alice",
  28. "last": "White"
  29. }
  30. ]
  31. }`),
  32. es.Index.WithDocumentID("1"),
  33. es.Index.WithPretty(),
  34. )
  35. fmt.Println(res, err)
  36. }
  37. {
  38. res, err := es.Search(
  39. es.Search.WithIndex("my-index-000001"),
  40. es.Search.WithBody(strings.NewReader(`{
  41. "query": {
  42. "nested": {
  43. "path": "user",
  44. "query": {
  45. "bool": {
  46. "must": [
  47. {
  48. "match": {
  49. "user.first": "Alice"
  50. }
  51. },
  52. {
  53. "match": {
  54. "user.last": "Smith"
  55. }
  56. }
  57. ]
  58. }
  59. }
  60. }
  61. }
  62. }`)),
  63. es.Search.WithPretty(),
  64. )
  65. fmt.Println(res, err)
  66. }
  67. {
  68. res, err := es.Search(
  69. es.Search.WithIndex("my-index-000001"),
  70. es.Search.WithBody(strings.NewReader(`{
  71. "query": {
  72. "nested": {
  73. "path": "user",
  74. "query": {
  75. "bool": {
  76. "must": [
  77. {
  78. "match": {
  79. "user.first": "Alice"
  80. }
  81. },
  82. {
  83. "match": {
  84. "user.last": "White"
  85. }
  86. }
  87. ]
  88. }
  89. },
  90. "inner_hits": {
  91. "highlight": {
  92. "fields": {
  93. "user.first": {}
  94. }
  95. }
  96. }
  97. }
  98. }
  99. }`)),
  100. es.Search.WithPretty(),
  101. )
  102. fmt.Println(res, err)
  103. }

Js

  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. user: {
  6. type: "nested",
  7. },
  8. },
  9. },
  10. });
  11. console.log(response);
  12. const response1 = await client.index({
  13. index: "my-index-000001",
  14. id: 1,
  15. document: {
  16. group: "fans",
  17. user: [
  18. {
  19. first: "John",
  20. last: "Smith",
  21. },
  22. {
  23. first: "Alice",
  24. last: "White",
  25. },
  26. ],
  27. },
  28. });
  29. console.log(response1);
  30. const response2 = await client.search({
  31. index: "my-index-000001",
  32. query: {
  33. nested: {
  34. path: "user",
  35. query: {
  36. bool: {
  37. must: [
  38. {
  39. match: {
  40. "user.first": "Alice",
  41. },
  42. },
  43. {
  44. match: {
  45. "user.last": "Smith",
  46. },
  47. },
  48. ],
  49. },
  50. },
  51. },
  52. },
  53. });
  54. console.log(response2);
  55. const response3 = await client.search({
  56. index: "my-index-000001",
  57. query: {
  58. nested: {
  59. path: "user",
  60. query: {
  61. bool: {
  62. must: [
  63. {
  64. match: {
  65. "user.first": "Alice",
  66. },
  67. },
  68. {
  69. match: {
  70. "user.last": "White",
  71. },
  72. },
  73. ],
  74. },
  75. },
  76. inner_hits: {
  77. highlight: {
  78. fields: {
  79. "user.first": {},
  80. },
  81. },
  82. },
  83. },
  84. },
  85. });
  86. console.log(response3);

コンソール

  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "user": {
  6. "type": "nested"
  7. }
  8. }
  9. }
  10. }
  11. PUT my-index-000001/_doc/1
  12. {
  13. "group" : "fans",
  14. "user" : [
  15. {
  16. "first" : "John",
  17. "last" : "Smith"
  18. },
  19. {
  20. "first" : "Alice",
  21. "last" : "White"
  22. }
  23. ]
  24. }
  25. GET my-index-000001/_search
  26. {
  27. "query": {
  28. "nested": {
  29. "path": "user",
  30. "query": {
  31. "bool": {
  32. "must": [
  33. { "match": { "user.first": "Alice" }},
  34. { "match": { "user.last": "Smith" }}
  35. ]
  36. }
  37. }
  38. }
  39. }
  40. }
  41. GET my-index-000001/_search
  42. {
  43. "query": {
  44. "nested": {
  45. "path": "user",
  46. "query": {
  47. "bool": {
  48. "must": [
  49. { "match": { "user.first": "Alice" }},
  50. { "match": { "user.last": "White" }}
  51. ]
  52. }
  53. },
  54. "inner_hits": {
  55. "highlight": {
  56. "fields": {
  57. "user.first": {}
  58. }
  59. }
  60. }
  61. }
  62. }
  63. }
user フィールドは nested タイプとしてマッピングされ、object タイプとしてではありません。
このクエリは、AliceSmith が同じネストされたオブジェクトにないため、一致しません。
このクエリは、AliceWhite が同じネストされたオブジェクトにあるため、一致します。
inner_hits は、一致するネストされたドキュメントをハイライトすることを可能にします。

ネストされたドキュメントとの対話

ネストされたドキュメントは次のことができます:

ネストされたドキュメントは別々のドキュメントとしてインデックス化されるため、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 を追加することを考えてみてください。各ドキュメントについて、usercomment オブジェクトの合計数は制限を下回る必要があります。

マッピングの爆発を防ぐための追加設定については、マッピング爆発を防ぐための設定 を参照してください。