1、线上实战环节遇到问题
现在有一组数据,其中 resultChar 是 keyword 类型,但其中有数字也有字符串,请问怎么能在大于小于查询的时候将其中的数字按照数字的类型进行大于小于的查询,结构如下:
代码语言:javascript复制{
"lisCheckItemList": [
{
"resultChar": "1",
"itemName": "项目1"
},
{
"resultChar": "2",
"itemName": "项目2"
},
{
"resultChar": "11",
"itemName": "项目3"
},
{
"resultChar": "22",
"itemName": "项目4"
},
{
"resultChar": "25",
"itemName": "项目"
},
{
"resultChar": "3",
"itemName": "项目5"
},
{
"resultChar": "阴性",
"itemName": "项目6"
}
]
}
结果:如果查询 resultChar 大于 2 的数据的话目前不会查询出 resultChar 为 11 的数据,因为现在的 resultChar 类型为 keyword .但是因为 resultChar 里面有数字也有字符所以不可以使用数字的类型.请问有什么方法可以将 keyword 类型中的数字按照数字类型进行大于小于查询.
期待结果: 查询 resultChar 大于 2的数据时会过滤掉小于 2 的并查询出大于 2 的数据(查询出 resultChar 为阴性的那个数据也可以,只要数字的查询是对的就可以)
问题来源:Elasticsearch 中文社区
https://elasticsearch.cn/question/12809
2、问题拆解
2.1 发现问题:数据建模不合理
对于 resultchar
字段来说,这个字段前面几个值都是数值加了引号的字符串类型,后面是“阴性”。MySQL 有数据完整性这一说,要求相同字段的语义是合理的,没有歧义的,是相容的。
Elasticsearch 虽没有类似的完整性说明。这种写入数据方式,从 Elasticsearch 角度来看,没有问题,都是字符串。但是,从业务层面来看,这带来后来处理的不确定性和麻烦。
一句话,这种建模方式有“百害”而无一利。建议从业务角度出发,及时止损。严格遵守数据建模规范理论,重新建模,数据写入更加符合业务逻辑,从根源上解决这个问题。
2.2 问题本质
将:resultChar 类型转换成数值类型,以便执行 range query 范围查询操作。
因为:keyword 类型本质是字符串类型的一种,以 keyword 类型做字符串处理比的是首字符的 ASCII 码值,达不到预期的效果。
2.3 方案探讨
接下来是怎么转换字段类型的问题?
传统的处理方案如下:
方案一:重新建模、重新导入数据。
特点:从根源上解决问题。
方案二:reindex alias 别名零停机方案。
特点:重新建模,重新迁移数据,用别名方式方案让用户无感知。
这时候,我们会思考:有没有更为简洁的方式呢?
本文会提供如下方案三的一种方式, 让大家评说是否简洁。
方案三:convert ingest 预处理 reindex 结合方案。
特点:无需重新建模,哪个字段不满足要求就改哪个字段。
下面我们着重讲解一下方案三。
3、方案三的实战实现
3.1 数据建模
我们只有数据,得从头模拟,所以建模是第一步。
代码语言:javascript复制PUT test-20220529-04
{
"mappings": {
"properties": {
"lisCheckItemList": {
"type": "object",
"properties": {
"resultChar": {
"type": "keyword"
},
"itemName": {
"type": "keyword"
}
}
}
}
}
}
3.2 数据写入
代码语言:javascript复制POST test-20220529-04/_doc/1
{
"lisCheckItemList": [
{
"resultChar": "1",
"itemName": "项目1"
},
{
"resultChar": "2",
"itemName": "项目2"
},
{
"resultChar": "11",
"itemName": "项目3"
},
{
"resultChar": "22",
"itemName": "项目4"
},
{
"resultChar": "25",
"itemName": "项目"
},
{
"resultChar": "3",
"itemName": "项目5"
}
]
}
POST test-20220529-04/_doc/2
{
"lisCheckItemList": [
{
"resultChar": "1",
"itemName": "项目1"
},
{
"resultChar": "2",
"itemName": "项目2"
}
]
}
POST test-20220529-04/_doc/3
{
"lisCheckItemList": [
{
"resultChar": "30",
"itemName": "项目1"
},
{
"resultChar": "100",
"itemName": "项目2"
}
]
}
3.3 数据预处理 convert 实现
代码语言:javascript复制# 物理的mapping 不会变
PUT _ingest/pipeline/mytx_pipeline_20220530
{
"processors": [
{
"foreach": {
"field": "lisCheckItemList",
"processor": {
"convert": {
"field": "_ingest._value.resultChar",
"type": "integer",
"ignore_failure": true
}
}
}
}
]
}
解释一下,lisCheckItemList 是 object 对象,所以需要foreach 遍历其下面的值,并通过:ingest.value.resultChar 的方式实现字段类型的 convert 转换。
做了什么转换呢?由:“keyword”类型变成“integer”类型。
3.4 数据 reindex 迁移
代码语言:javascript复制POST _reindex
{
"source": {"index": "test-20220529-04"},
"dest": {"index": "test-20220529-05", "pipeline": "mytx_pipeline_20220530"}
}
强调一个语法知识点,也是大家认证考试容易出错的点。
这里的 pipeline 要写到“dest”目标索引部分实现,而不是“source”源索引部分。
3.5 数据检索,验证是否达到预期
代码语言:javascript复制POST test-20220529-05/_search
{
"query": {
"range": {
"lisCheckItemList.resultChar": {
"gte": 30,
"lt": 100
}
}
}
}
由于是在数据同步的时候,同时切换了数据类型。
所以,本质上是没有问题,rangquery 的检索自然会达到预期效果。
4、小结
同步一个小细节,如果我们上面的预处理 ingest 部分不是做迁移,而是做 update_by_query 会怎么样?
这是一个不大不小的“脑洞”。我们结论说一下, 留给大家去思考和实践。
如果仅是:update_by_query 和 ingest 结合,数据的类型也就是 Mapping 依然会是:keyword,但是数据的显示会去掉了“ ”,这实际是“治标不治本”的方式,不推荐大家使用。
小问题蕴含大道理。
比 reindex 更轻量级的更换字段类型的方式技能,你 Get 到了吗?
欢迎大家留言反馈!!