Object类型
ES原生支持Object类型,也就是任意字段都可以是个对象,而ES又是所有字段都支持多值,即都可以是list。es的object类型虽然是对象类型,但是数据是打平存储的。
如下,声明一个对象,新增1条数据:
DELETE /test-index
PUT /test-index
{
"settings": {
"number_of_shards": 8,
"number_of_replicas": 1,
"codec": "best_compression"
},
"mappings": {
"test-type": {
"dynamic": "true",
"_routing": {
"required": false
},
"_all": {
"enabled": false
},
"properties": {
"keywordsWithCount": {
"dynamic": "false",
"properties": {
"keyword": {
"type": "keyword"
},
"count": {
"type": "keyword"
}
}
},
"companyName": {
"type": "keyword"
}
}
}
}
}
POST /test-index/test-type/1
{
"companyName": "大富翁",
"keywordsWithCount": [
{
"keyword": "NP0001",
"count": "5"
},
{
"keyword": "NP0002",
"count": "15"
}
]
}
GET /test-index/_search
但实际存储的时候,是打平这样存储的:
{
"companyName" : "大富翁",
"keywordsWithCount.keyword": ["NP0001", "NP0002"],
"keywordsWithCount.count": ["1", "2"]
}
就丢失了keyword和count之间的关联关系,就不知道谁是 1谁是2了。所以,这样查询也能查询出结果:
GET /test-index/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"keywordsWithCount.keyword": "NP0001"
}
},
{
"term": {
"keywordsWithCount.count": "2"
}
}
]
}
}
}
返参:
"hits" : [
{
"_index" : "test-index",
"_type" : "test-type",
"_id" : "1",
"_source" : {
"companyName" : "大富翁",
"keywordsWithCount" : [
{
"keyword" : "NP0001",
"count" : "1"
},
{
"keyword" : "NP0002",
"count" : "2"
}
]
}
}
]
可是NP0001是1,NP0002才是2 所以,为解决es object类型的数据扁平化存储问题,引入了nested类型。
Nested类型
nested类型:嵌套文档,对象数组的优先选择类型。Nested将数组中的每个对象作为单独的隐藏文档(hidden separate document)进行索引。
解决问题:对象数组的多字段匹配查询。
在独立索引每一个嵌套对象后,对象中每个字段的相关性得以保留。我们查询时,也仅仅返回那些真正符合条件的文档。
不仅如此,由于嵌套文档直接存储在文档内部,查询时嵌套文档和根文档联合成本很低,速度和单独存储几乎一样。
嵌套文档是隐藏存储的,我们不能直接获取。如果要增删改一个嵌套对象,我们必须把整个文档重新索引才可以。值得注意的是,查询的时候返回的是整个文档,而不是嵌套文档本身。
如果需要索引对象数组而不是单个对象,优先考虑使用嵌套数据类型Nested。
如果不需要对 Nested 子文档精确搜索的就选型 object,需要的选型 Nested。
nested类型的定义在声明时指定 "type": "nested" 即可!
DELETE /test-index-2
PUT test-index-2
{
"settings": {
"number_of_shards": 8,
"number_of_replicas": 1,
"codec": "best_compression"
},
"mappings": {
"test-type": {
"dynamic": "true",
"_routing": {
"required": false
},
"_all": {
"enabled": false
},
"properties": {
"keywordsWithCount": {
"type": "nested",
"dynamic": "false",
"properties": {
"keyword": {
"type": "keyword"
},
"count": {
"type": "keyword"
}
}
},
"companyName": {
"type": "keyword"
}
}
}
}
}
POST /test-index-2/test-type/1
{
"companyName": "大富翁",
"keywordsWithCount": [
{
"keyword": "NP0001",
"count": "5"
},
{
"keyword": "NP0002",
"count": "15"
}
]
}
GET /test-index-2/_search
Nested 因为是单独的子文档存储,因此在使用时,直接用 a.b.c 是无法访问的,需要将其套在nested查询里,且需要指定 "path" 。
GET /test-index-2/_search
{
"query": {
"nested": {
"path": "keywordsWithCount",
"query": {
"bool": {
"must": [
{
"term": {
"keywordsWithCount.keyword": "NP0001"
}
},
{
"term": {
"keywordsWithCount.count": "2"
}
}
]
}
}
}
}
}
这时返回的数据为空,满足我们的需求。
Nested注意点:
由于单独存储很耗资源,因此默认一个index最多只有50个nested字段。此外,虽然nested是单独存储的,但是其字段数也算入index总字段数,默认最多1000个。
Nested结构是个List结构。Nested Aggregation就是对这个list做agg操作,agg写法和普通的一样,只需要在外面套上nested即可。
能否用Nested做动态kv?
Nested除了存储固定的Object List,还有一种常用的场景就是用来存储动态的KV。虽然ES天然支持dynamic mapping,但是其key都是固化在每一个doc中的,如果存储用户自定义报表数据。每个用户的key差异很大,放在同一张表会出现大量空值。这是很浪费系统资源的行为,并且随着Key的不断增多,最终会超出index的最大key数量。
因此用nested结构来处理这种动态kv就比较合适。 nested的本质就是将
{"tags":{"k1":"v1","k2":"v2"}}
=>
{"tags":[{"key":"key1","value":"v1"},{"key":"key2","value":"v2"}]}
这样一来就可以轻松处理动态kv。并且查询依旧简单,例如k1:v1 AND k2:v2变为
{
"query": {
"bool": {
"must": [
{
"nested": {
"path": "tags",
"query": {
"query_string": {
"query": "tags.key:k1 AND tags.value:v1"
}
}
}
},
{
"nested": {
"path": "tags",
"query": {
"query_string": {
"query": "tags.key:k2 AND tags.value:v2"
}
}
}
}
]
}
}
}
Nested 新增或更新子文档操作,为什么需要更新整个文档?
嵌套 Nested 文档在物理上位于根文档旁边的 Lucene 段中。这是为什么当只想更改单个嵌套文档时必须重建根文档和所有嵌套 Nested 文档的原因。
参考文档: