嵌套字段类型

编辑

nested 类型是 object 数据类型的专门版本,它允许以可以彼此独立查询的方式索引对象数组。

当摄取具有大量任意键的键值对时,您可以考虑将每个键值对建模为具有 keyvalue 字段的嵌套文档。相反,请考虑使用 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.firstuser.last 字段被展平为多值字段,并且 alicewhite 之间的关联丢失。此文档将错误地匹配 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 类型。

此查询不匹配,因为 AliceSmith 不在同一个嵌套对象中。

此查询匹配,因为 AliceWhite 在同一个嵌套对象中。

inner_hits 允许我们突出显示匹配的嵌套文档。

nested 文档交互

编辑

嵌套文档可以

由于嵌套文档被索引为单独的文档,因此它们只能在 nested 查询、nested/reverse_nested 聚合或 嵌套内部命中的范围内访问。

例如,如果嵌套文档中的字符串字段的 index_options 设置为 offsets 以允许在突出显示期间使用帖子,则这些偏移量在主突出显示阶段将不可用。相反,突出显示需要通过 嵌套内部命中执行。当在搜索期间通过docvalue_fieldsstored_fields加载字段时,也适用相同的考虑因素。

nested 字段的参数

编辑

nested 字段接受以下参数

dynamic
(可选,字符串)是否应将新的 properties 动态添加到现有的嵌套对象。接受 true (默认)、falsestrict
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
单个文档可以在所有 nested 类型中包含的最大嵌套 JSON 对象数。此限制有助于防止当文档包含太多嵌套对象时出现内存不足错误。默认为 10000

为了说明此设置的工作原理,请考虑向先前的示例映射添加另一个名为 commentsnested 类型。对于每个文档,它包含的 usercomment 对象总数必须低于限制。

有关防止映射爆炸的其他设置,请参阅防止映射爆炸