分词的结果是:
{ "tokens": [ { "token": "hello", "start_offset": 0, "end_offset": 5, "type": "<ALPHANUM>", "position": 0 }, { "token": "world", "start_offset": 6, "end_offset": 11, "type": "<ALPHANUM>", "position": 1 }, { "token": "java", "start_offset": 13, "end_offset": 17, "type": "<ALPHANUM>", "position": 2 }, { "token": "spark", "start_offset": 18, "end_offset": 23, "type": "<ALPHANUM>", "position": 3 } ] }
从上述结果中,可以看到。ES在做分词的时候,除了将数据切分外,还会保留一个position。position代表的是这个词在整个数据中的下标。当ES执行matchphrase搜索的时候,首先将搜索条件hello world分词为hello和world。然后在倒排索引中检索数据,如果hello和world都在某个document的某个field出现时,那么检查这两个匹配到的单词的position是否是连续的,如果是连续的,代表匹配成功,如果是不连续的,则匹配失败。
**-2). match phrase搜索参数 -- slop **
在做搜索操作的是,如果搜索参数是hello spark。而ES中存储的数据是hello world, java spark。那么使用match phrase则无法搜索到。在这个时候,可以使用match来解决这个问题。但是,当我们需要在搜索的结果中,做一个特殊的要求:hello和spark两个单词距离越近,document在结果集合中排序越靠前,这个时候再使用match则未必能得到想要的结果。
ES的搜索中,对match phrase提供了参数slop。slop代表match phrase短语搜索的时候,单词最多移动多少次,可以实现数据匹配。在所有匹配结果中,多个单词距离越近,相关度评分越高,排序越靠前。
这种使用slop参数的match phrase搜索,就称为近似匹配(proximity search)
如:
数据为: hello world, java spark
搜索为: match phrase : hello spark。
slop为: 3 (代表单词最多移动3次。)
执行短语搜索的时候,将条件hello spark分词为hello和spark两个单词。并
且连续。
hello spark
接下来,可以根据slop参数执行单词的移动。
下标 | 0 | 1 | 2 | 3 |
doc | hello | world | java | spark |
搜索 | hello | spark | ||
移动 | hello | spark | ||
移动2 | hello | spark |
匹配成功,不需要移动第三次即可匹配。
如果:
数据为: hello world, java spark
搜索为: match phrase : spark hello。
slop为: 5 (代表单词最多移动5次。)执行短语搜索的时候,将条件hello spark分词为hello和spark两个单词。并且连续。
spark hello
接下来,可以根据slop参数执行单词的移动。
下标 : 0 1 2 3
doc : hello world java spark
搜索 : spark hello
移动1: spark/hello
移动2: hello spark
移动3: hello spark
移动4: hello spark
匹配成功,不需要移动第五次即可匹配。
如果当slop移动次数使用完毕,还没有匹配成功,则无搜索结果。如果使用中文分词,则
移动次数更加复杂,因为中文词语有重叠情况,很难计算具体次数,需要多次尝试才行。
测试案例:
英文:
GET _analyze { "text": "hello world, java spark", "analyzer": "standard" } POST /test_a/_doc/3 { "f" : "hello world, java spark" } GET /test_a/_search { "query": { "match_phrase": { "f" : { "query": "hello spark", "slop" : 2 } } } } GET /test_a/_search { "query": { "match_phrase": { "f" : { "query": "spark hello", "slop" : 4 } } } }
**中文: **
GET _analyze { "text": "中国,一个世界上最强的国家", "analyzer": "ik_max_word" } POST /test_a/_doc/1 { "f" : "中国,一个世界上最强的国家" } GET /test_a/_search { "query": { "match_phrase": { "f" : { "query": "中国最强", "slop" : 5 } } } } GET /test_a/_search { "query": { "match_phrase": { "f" : { "query": "最强中国", "slop" : 9 } } } }
六.经验分享
使用match和proximity search实现召回率和精准度平衡。
召回率:召回率就是搜索结果比率,如:索引A中有100个document,搜索时返回多少个document,就是召回率(recall)。
精准度:就是搜索结果的准确率,如:搜索条件为hello java,在搜索结果中尽可能让短语匹配和hello java离的近的结果排序靠前,就是精准度 (precision)。
如果在搜索的时候,只使用match phrase语法,会导致召回率底下,因为搜索
结果中必须包含短语(包括proximity search)。
如果在搜索的时候,只使用match语法,会导致精准度底下,因为搜索结果排
序是根据相关度分数算法计算得到。
那么如果需要在结果中兼顾召回率和精准度的时候,就需要将match和
proximity search混合使用,来得到搜索结果。
测试案例:
POST /test_a/_doc/3 { "f" : "hello, java is very good, spark is also very good" } POST /test_a/_doc/4 { "f" : "java and spark, development language " } POST /test_a/_doc/5 { "f" : "Java Spark is a fast and general‐purpose cluster computing system. I t provides high‐level APIs in Java, Scala, Python and R, and an optimized engi ne that supports general execution graphs." } POST /test_a/_doc/6 { "f" : "java spark and, development language " } GET /test_a/_search { "query": { "match": { "f": "java spark" } } } GET /test_a/_search { "query": { "bool": { "must": [ { "match": { "f": "java spark" } } ], "should": [ { "match_phrase": { "f": { "query": "java spark", "slop" : 50 } } } ] } } }
七、前缀搜索 prefix search
使用前缀匹配实现搜索能力。通常针对keyword类型字段,也就是不分词的字段。
语法:
GET /test_a/_search { "query": { "prefix": { "f.keyword": { "value": "J" } } } }
**注意:针对前缀搜索,是对keyword类型字段而言。而keyword类型字段数据大小写敏感。 **
前缀搜索效率比较低。前缀搜索不会计算相关度分数。前缀越短,效率越低。
如果使用前缀搜索,建议使用长前缀。因为前缀搜索需要扫描完整的索引内容,所以前缀越长,相对效率越高。
八、通配符搜索
ES中也有通配符。但是和java还有数据库不太一样。通配符可以在倒排索引中使用,也可以在keyword类型字段中使用。
常用通配符:
? - 一个任意字符
* - 0~n个任意字符
GET /test_a/_search { "query": { "wildcard": { "f.keyword": { "value": "?e*o*" } } } }
性能也很低,也是需要扫描完整的索引。不推荐使用。
九、正则搜索
ES支持正则表达式。可以在倒排索引或keyword类型字段中使用。 常用符号:
[] - 范围,如: [0-9]是0~9的范围数字
. - 一个字符
+ - 前面的表达式可以出现多次。
GET /test_a/_search { "query": { "regexp" : { "f.keyword" : "[A‐z].+" } } }
性能也很低,需要扫描完整索引。
十、搜索推荐
搜索推荐: search as your type, 搜索提示。如:索引中有若干数据以“hello”开头,那么在输入hello的时候,推荐相关信息。(类似百度输入框)
语法:
GET /test_a/_search { "query": { "match_phrase_prefix": { "f": { "query": "java s", "slop" : 10, "max_expansions": 10 } } } }
其原理和match phrase类似,是先使用match匹配term数据(java),然后在指定的slop移动次数范围内,前缀匹配(s),max_expansions是用于指定prefix 最多匹配多少个term(单词),超过这个数量就不再匹配了。
这种语法的限制是,只有最后一个term会执行前缀搜索。
执行性能很差,毕竟最后一个term是需要扫描所有符合slop要求的倒排索引的 term。
因为效率较低,如果必须使用,则一定要使用参数max_expansions。
十一、fuzzy模糊搜索技术
搜索的时候,可能搜索条件文本输入错误,如:hello world -> hello word。
这种拼写错误还是很常见的。fuzzy技术就是用于解决错误拼写的(在英文中很有效,在中文中几乎无效。)。其中fuzziness代表value的值word可以修改多少个字母来进行拼写错误的纠正(修改字母的数量包含字母变更,增加或减少字母。)。f代表要搜索的字段名称。
GET /test_a/_search { "query": { "fuzzy": { "f" : { "value" : "word", "fuzziness": 2 } } } }