全文搜索时要实现精确匹配,需要用到 match_phrase 语句。
建索引时必须分词,分词操作会把一个字符串拆分成一系列 token,每个 token 有三个位置参数:start_offset,end_offset,position。
中文分词常用的一个插件叫 ik,但使用 ik 分词后,match_phrase 查询会出现问题 (参考:类似#195的match_phrase问题 · Issue #204 · medcl/elasticsearch-analysis-ik,注意 ik 的作者 @medcl 在 2017 年 1 月 5 号的回答),举例如下:
假设我们用 ik_max_word 索引了两个文档,内容分别为:
{
"title": "首次公开发行"
}
{
"title": "首次公开发行股票"
}ik_max_word 使用的词库包含“首次、公开、发行、开发行、发行股票”等词语。上述两个文档被分词后的结果是
# 0 1 2 3 4 5 6 --- position
首次 公开 开发行 开发 发行 发 行
# 0 1 2 3 4 5 6 7 8 9 10 --- position
首次 公开 开发行 开发 发行股票 发行 发 行 股票 股 票需要注意两点:
现在,我们通过 match_phrase 来搜索“首次公开发行” (搜索词与第一个文档的内容一样,并且是第二个文档的内容的子字符串,所以我们预期两个文档都会命中):
{
"match_phrase": {
"title": {
"query": "首次公开发行",
"slop":
}
}
}结果是,只有文档 1 被命中,而搜不到文档 2,而这是不符合预期的。
搜不到文档 2 的原因在于,当搜索引擎把搜索词“首次公开发行”分词后的结果与文档 2 进行匹配时,“开发”这个 token 对应的位置序号是 3,“发行”对应的位置序号是 5,而它们在搜索词的 token 序列里的位置序号分别是 3 和 4。由于我们搜索时设置了 slop = 0,也就是要求每个 token 在命中文档里的顺序和位置间隔跟在搜索词里的顺序和位置间隔一模一样,于是就不能匹配文档 2 了。
解决方案把 slop 设置为 1 或者更大的数字可以让两个文档都匹配,但这会导致一般情况下会有预期之外的文档被命中,所以这不是一个好的解决方案。
使用 ik_smart 分词器不能解决这个问题,因为我们多半会希望搜“首次公开发”时能搜到“首次公开发行”这个文档,但后者会被分词为“首次 公开 发行”,而搜索词会被分词为“首次 公开 发”,“发”这个字匹配不上任何词,导致未能命中。
当我们对搜索结果的评价是以精确匹配为标准时,分词器以及相应的词库就不重要了。我们只需要把文档按字符拆分,就可以保证看上去相邻的字在分词后的 token 列表里仍是相邻的。这样,用 match_phrase 来查询就能得到预期的结果。
要实现按字符拆分,可以使用自带的 standard 分析器。
重建包含三百多万个文档的索引耗时三个多小时。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31509949/viewspace-3171077/ ,如需转载,请注明出处,否则将追究法律责任。