嵌套字段类型编辑

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

当使用大量任意键集合提取键值对时,您可能会考虑将每个键值对建模为其自己的嵌套文档,并使用 keyvalue 字段。 您可以考虑使用 扁平化 数据类型,它将整个对象映射为单个字段,并允许对其内容进行简单搜索。 嵌套文档和查询通常开销很大,因此对于此用例,使用 flattened 数据类型是更好的选择。

嵌套字段在 Kibana 中的支持不完整。 虽然它们在 Discover 中可见且可搜索,但不能用于在 Lens 中构建可视化。

对象数组如何扁平化编辑

Elasticsearch 没有内部对象的 própriété。 因此,它将对象层次结构扁平化为字段名称和值的简单列表。 例如,请考虑以下文档

resp = client.index(
    index="my-index-000001",
    id="1",
    body={
        "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)
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",
    body={
        "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)
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",
    body={"mappings": {"properties": {"user": {"type": "nested"}}}},
)
print(resp)

resp = client.index(
    index="my-index-000001",
    id="1",
    body={
        "group": "fans",
        "user": [
            {"first": "John", "last": "Smith"},
            {"first": "Alice", "last": "White"},
        ],
    },
)
print(resp)

resp = client.search(
    index="my-index-000001",
    body={
        "query": {
            "nested": {
                "path": "user",
                "query": {
                    "bool": {
                        "must": [
                            {"match": {"user.first": "Alice"}},
                            {"match": {"user.last": "Smith"}},
                        ]
                    }
                },
            }
        }
    },
)
print(resp)

resp = 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": {}}}
                },
            }
        }
    },
)
print(resp)
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)
}
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 以允许在突出显示期间使用 postings,则这些偏移量在主要突出显示阶段将不可用。 相反,需要通过 嵌套内部匹配 执行突出显示。 当在搜索期间通过 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 对象的总数必须低于限制。

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