目录
1.需求场景
统计es索引中某字段的字符串长度有多种使用场景,例如:
1.想统计出索引文本内容分别在300字内、500字内、1000字内的文章数量;
2.将查询结果按某字段的字符串长度排序;
3.想实验过滤短文本后的查询效果 ......
2.实现方案
elasticsearch 5.0及之后的版本中存储字符串的类型有两种,keyword和text。
text属性的字段在es中存储时,会被自动分词存储,因此text属性的字段支持分词,但不支持 过滤、排序和聚合等操作。keyword属性的字段更方便统计,但通常情况下,对于存储文本内 容的content我们在设置索引mapping的时候都会将其设为text,这一设置方便了我们进行关键 词的分词全文检索,但当想要对text类型字段进行聚合等相关统计操作时带来不便。
下面针对两种类型分别介绍基于script统计字符串长度的步骤
2.1 keyword类型
#查找标签长度小于5
GET wendongmi_read/_search
{
"query": {
"bool": {
"filter": [
{
"script": {
"script": {
"source": """
if (doc['_tags'].size() !=0) {
doc['_tags'].value.length() < 5
}
""",
"lang": "painless"
}
}
}
]
}
},
"track_total_hits": true,
"size": 10
}
因为keyword类型本身便可进行聚合、过滤、排序等,可以直接使用 doc['field_name'].valu e.length() 的语法获取该属性值的长度进行相关过滤统计。
2.2 text类型
当尝试直接对text类型字段使用如keyword类型一样的语法时,会出现如下报错:
Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [Art_Content] in order to load field data by uninverting the inverted index. Note that this can use significant memory.
针对text类型,其实es官方报错的提示语已经给如何对text类型做统计指明了两个方向。
进行fielddata=true设 置或者把该字段变成keyword类型
设置fielddata=true
POST test_read/_mapping
{
"properties":{
"content": {
"type" : "text",
"fielddata": false
}
}
}
在es中,text类型的字段使用一种叫做fielddata的查询时内存数据结构。当字段被排序,聚合 或者通过脚本访问时这种数据结构会被创建。它是通过从磁盘读取每个段的整个反向索引来构 建的,然后存存储在java的堆内存中。
fileddata默认是不开启的。fileddata可能会消耗大量的堆空间,尤其是在加载高基数文本字段 时。一旦fielddata已加载到堆中,它将在该段的生命周期内保留。此外,加载fielddata是一个 昂贵的过程,可能会导致用户遇到延迟命中。这就是默认情况下禁用fielddata的原因。不推荐在生产环境使用
设置后,即可使用script统计成功(ps:速度很慢)
GET test_read/_search
{
"query": {
"bool": {
"filter": [
{
"script": {
"script": {
"source": """
if (doc['content'].size() !=0) {
doc['content'].value.length() < 5
}
""",
"lang": "painless"
}
}
}
]
}
},
"track_total_hits": true,
"size": 10
}
reindex把text文本改keyword
如果是暂时统计一下获取某个数据的话(比如资讯文章里文本300字内的文章数)可以新设置一个专用于统计的索引,将要统计的字段设为keyword,将需要统计的数据reindex到该索引
这里需要注意的是,ES5.X版本以后,keyword支持的最大长度为32766个UTF-8字符。如果待统计的本文较长,直接reindex会因为长度超过keyword支持的最大长度而报错,这时需要设置ignore_above,设置ignore_above后,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。
PUT test_stat
{
"aliases": {
"test_stat_read": {},
"test_stat_write": {}
},
"mappings": {
"properties": {
"type" : {
"type" : "keyword"
},
"content" : {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 3000
}
}
},
"title" : {
"type" : "keyword"
}
}
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_shards": "1",
"number_of_replicas": "0"
}
}
}
设置后,即可通过以下脚本进行统计
#查找content长度大于1000的
GET test_stat/_search
{
"query": {
"bool": {
"filter": [
{
"script": {
"script": {
"source": """
if (doc['content.keyword'].size() !=0) {
doc['content.keyword'].value.length() > 1000
}
""",
"lang": "painless"
}
}
}
]
}
},
"track_total_hits": true,
"size": 0
}
语法总结
-
type为text时,我们可以通过
doc['field_name'].length
或者doc['field_name'].size()
获取该属性对应数组的长度; -
type为keyword时,则使用
doc['field_name'].value.length()
获取属性值的长度,但是需要注意,如果doc['field_name'].value
的值存在为null的情况,因此需要使用doc['field_name'].size()
优先判空。
3.参考资料
[Text type family | Elasticsearch Guide [8.8] | Elastic]
Text type family | Elasticsearch Guide [8.8] | Elastic
[Elasticsearch:如何基于Script实现按照text属性值的字符串长度排序
https://www.cnblogs.com/mrzihan/p/15729353.html
[Lucene expressions language | Elasticsearch Guide [8.9] | Elastic]
Lucene expressions language | Elasticsearch Guide [8.9] | Elastic