Elasticsearch 有没有数组类型?有哪些坑?

2022-01-05 10:39:56 浏览数 (1)

1、Elasticsearch 数组常见问题清单

近期 Elasticsearch 数组问题被问到的比较多,为了方便大家对数组建立全局认知,我把数组相关实战问题梳理出来,让更多后来人遇到类似问题少走不必要的弯路。

精简几个核心问题列举如下:

  • Elasticsearch 是否有数组类型?
  • Elasticsearch 数据选型有没有坑?
  • Elasticsearch 如何获取数组中第i号位置的值?
  • Elasticsearch 如何获取数组最后一个元素的值?

2、Elasticsearch 是否有数组类型?

了解 Elasticsearch 版本历史的读者会知道 1.X 版本中没有独立模块强调数组 Array 类型。

Elasticsearch 2.X 版本之后,数组类型才单独被拎出来。

在 Elasticsearch 中,没有专门的数组数据类型。默认情况下,任何字段都可以包含零个或多个值,但是,数组中的所有值必须具有相同的数据类型。什么意思呢?

  • long 类型存储一个值是long类型,存储多个自然就成为 long 数组类型;
  • keyword 类型存储一个值是 keyword 类型,存储多个值就成为 keyword 数组类型。

实战一把:

代码语言:javascript复制
PUT my-index-1230
{
  "mappings": {
    "properties": {
      "horry": {
        "type": "keyword"
      }
    }
  }
}

# 单值 keyword 类型
POST my-index-1230/_doc
{
  "horry": "pingpang"
}

# keyword 数组类型
POST my-index-1230/_doc
{
  "horry": [
    "pingpang",
    "basketball",
    "football"
  ]
}

GET my-index-1230/_search

如上示例,可以更加清晰的看出,当我们选型使用数组时,和平时 Mapping 设置类型一样,不需要额外的修改 Mapping 任何内容,只需要导入相同数据类型的数据即可。

这时候,读者可能会问,我不小心写入了不同类型的数据咋办?

继续实战一把,探个究竟。

代码语言:javascript复制
# 导入不合规数据,数据依然可以写入
POST my-index-1230/_doc
{"horry":["11111",22222,33333]}

# 数据依然可以被召回
GET my-index-1230/_search
{
  "query": {
    "term": {
      "horry": {
        "value": "22222"
      }
    }
  }
}

由于 Elasticsearch 做了弱类型匹配校验检查,导致数据依然可以写入,我们肉眼看到的整形类型,实际本质存储为keyword 类型,这也是为什么“22222”能被检索召回的原因。

3、Elasticsearch 数据选型有没有坑?

3.1 动态导入数组类型数据,第一次写入数据的类型决定了数组的类型。

代码语言:javascript复制
# 默认检测指定为 long 类型
POST my-index-1230-01/_doc
{
  "horry": [
    11111,
    22222,
    33333
  ]
}

如上,写入的是 long 类型的数组。long 咋来的,动态类型匹配 date_detection 得到的!

https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html

3.2 不能独立的检索数组中的单个字段

如下示例,即便仅检索“basketball”不能独立只召回“basketball”,数组数据会一起返回。

代码语言:javascript复制
# 数组数据会作为一个整体被召回
GET my-index-1230/_search
{
  "query": {
    "match": {
      "horry":  "basketball"
    }
  }
}

如果非要实现单独召回数据,需要借助:nested 嵌套类型实现。

4、Elasticsearch 如何获取数组中第i号位置的值?

举例,仅检索召回 “basketball”,需要借助 script_field 或者 runtime field 实现。

实现参考如下:

代码语言:javascript复制
POST my-index-1230/_doc
{
  "horry": [
    "all",
    "pingpang",
    "basketball",
    "football"
  ]
}

GET my-index-1230/_search

注意:存储数组数据顺序是:

序号

0

all

1

pingpang

2

basketball

3

football

代码语言:javascript复制
GET my-index-1203/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "test1": {
      "script": {
        "lang": "painless",
        "source": "doc['horry'][2]"
      }
    }
  }
}

如上示例中的2,可以换成:0,1,2,3。

召回结果如下:

序号

0

all

1

basketball

2

football

3

pingpang

后台按照字母顺序做了处理,返回结果数据。

也就是说:我们以脚本的方式无法精准获取对应位次上的数据。

这块目前看,没有最优的获取方式。如果大家有,欢迎留言交流。

4、Elasticsearch 如何获取数组最后一个元素的值?

通过 ingest pipeline 预处理方式实现如下:

代码语言:javascript复制
DELETE my-index-123001
POST my-index-123001/_doc
{
  "horry": [
    "pingpang",
    "basketball",
    "football"
  ]
}

PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "script": {
        "source": """
             if(ctx.horry!=null)
             {
               List list = ctx.horry; 
              List ListOfLast = list.subList(list.size() - 1, list.size()); 
              String lastval=ListOfLast.toString(); 
              lastval=lastval.substring(1, lastval.length() - 1);
              ctx.lastval=lastval;
             }
          """
      }
    }
  ]
}


POST my-index-123001/_update_by_query?pipeline=my_pipeline
{
  "query": {
    "match_all": {}
  }
}

GET my-index-123001/_search

中间核心脚本不是最优实现方式,欢迎大家留言反馈精简处理方式。

  • 第一步:数组转 list;
  • 第二步:取 list 最后一个元素值,结果仍然为 list;
  • 第三步:list 转 string;
  • 第四步:string 取核心元素,去头、去尾。
  • 第五步:将中间结果赋值给新字段。

返回结果如下:

代码语言:javascript复制
{
      "_index" : "my-index-123001",
      "_type" : "_doc",
      "_id" : "RxLlC34BuPkjCUZU5-XW",
      "_score" : 1.0,
      "_source" : {
        "lastval" : "football",
        "horry" : [
          "pingpang",
          "basketball",
          "football"
        ]
      }
    }

上面的预处理方式对数组而言,也是仅适用于获取特定值的场景。

5、小结

Elasticsearch 数组选型需要结合业务场景需要。

数据相关的问题还有很多很多,比如:

  1. 数组高亮问题
  2. 数组聚合问题
  3. 数组召回数据问题
  4. .....

大家在选型或者实践的过程中,如果涉及大量脚本的时候,要多考虑能否通过写入前 ingest 预处理方式。本质是:以空间换时间,最大化提升检索效率。

欢迎大家就数组问题留言讨论。

0 人点赞