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 |
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 数组选型需要结合业务场景需要。
数据相关的问题还有很多很多,比如:
- 数组高亮问题
- 数组聚合问题
- 数组召回数据问题
- .....
大家在选型或者实践的过程中,如果涉及大量脚本的时候,要多考虑能否通过写入前 ingest 预处理方式。本质是:以空间换时间,最大化提升检索效率。
欢迎大家就数组问题留言讨论。