本文地址:
在有搜索引擎之前,我们查文档常使用顺序匹配。比如要搜索 "我的祖国是花园",需要在文档中顺序扫描,找到完全匹配的子句。
在有了搜索引擎后,我们对查询语句做的处理就不一样了。我们通常会先分词,然后查找对应的词条索引,最后得到评分由高到低的文档列表。我一度以为没法实现完全匹配了,直到一个硬需求的出现。花了一天时间,把完全匹配用搜索引擎的思维整理出来。
简要描述实现思路,字段按一字一词的形式分词,再利用短语查询来搜索。
ES中,可以实现一字一词的的分词器是NGram。
Ngram分词的官方文档地址: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
它其实是一个上下文相连续字符的分词工具,可以看官方文档中的例子。当我们将它 min_gram 和 max_gram 都设为1时,它会按一字一词的形式分词。比如“shinyke@189.cn”,分词的结果是["s" , "h" , "i" , "n" , "y" , "k" , "e" , "@" , "1" , "8" , "9" , "." , "c" , "n" ]。
/index_name/
{
"settings": {
"analysis": {
"analyzer": {
"charSplit": {
"type": "custom",
"tokenizer": "ngram_tokenizer"
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "nGram",
"min_gram": "1",
"max_gram": "1",
"token_chars": [
"letter",
"digit",
"punctuation"
]
}
}
}
}
}
以上语句中,构建了一个
名为“charSplit”的分析器。它使用一个名为“ngram_tokenizer”的Ngram分词器。
可以用如下语句测试charSplit分析器,可以看到一字一词的效果:
curl -POST http://IP:port/{index_name}/_analyze?pretty&analyzer=charSplit
"测试语句"
把这个分析器在mapping里用起来:
...
"sender": {
"type": "string",
"store": "yes",
"analyzer": "charSplit",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
},
...
接下来就可以用match_phrase来实现完全匹配查询。
/{index_name}/{type_name}/_search
{
"query": {
"multi_match": {
"query": "@189.cn",
"type": "phrase", //type指定为phrase
"slop": 0, //slop指定每个相邻词之间允许相隔多远。此处设置为0,以实现完全匹配。
"fields": [
"sender"
],
"analyzer": "charSplit", //分析器指定为charSplit
"max_expansions": 1
}
},
"highlight": { //测试高亮是否正常
"pre_tags": [
""
],
"post_tags": [
""
],
"fragment_size": 100,
"number_of_fragments": 2,
"require_field_match": true,
"fields": {
"sender": {}
}
}
}phrase查询原始的作用是用来做短语查询,
它有一个重要的特点:有顺序。我们利用了它匹配的有序性,限制slop为0,则可实现完全匹配查询。
以上语句返回的结果是:
{
"took": 18,
"timed_out": false,
"_shards": {
"total": 9,
"successful": 9,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.40239456,
"hits": [
{
"_index": "index_name",
"_type": "type_name",
"_id": "AU9OLIGOZN4dLecgyoKp",
"_score": 0.40239456,
"_source": {
"sender": "18977314000 <18977314000@189.cn>, 李X <18977314000@189.cn>, 秦X <18977314000@189.cn>, 刘X <18977314000@189.cn>"
},
"highlight": {
"sender": [
"18977314000 <18977314000@189.cn>, 李X <18977314000@189.cn>, 秦纯X <18977314000@189.cn>, 刘X <189773140"
]
}
}
]
}
}
到此,就实现了完全匹配查询。实际环境中用NGram做一字一词分析器的时候会更细致一些,比如有一些字符需要用stop word过滤掉。这些细节可以根据实际需要在构造分析器时添加filter实现,在此不做赘述。