ES第十三天-扩展查询-前缀匹配、通配符查询、正则查询、模糊匹配、句子前缀匹配

前言

ES的无论什么搜索,对于text类型字段其实都是基于倒排索引去进行搜索的,也就是进行分词后的,因此如果想像传统数据库一样的模糊匹配,一般可以使用它的keyword进行搜索。(keyword不会被分词)
以下的搜索在大型生产环境都不推荐使用。

前缀索引查询

以xx开头的搜索,不计算相关度评分,和filter比,没有bitcache。前缀搜索,尽量把前缀长度设置的更长,性能差,一般大规模产品不使用。(是去倒排索引中去匹配前缀,需要遍历每一个倒排索引才能找到所有匹配的)

语法

GET index/_search
{
  "query": {
    "prefix": {
      "title": {
        "value": "text"
      }
    }
  }
}

为了加快前缀搜索速度,可以设置默认的 前缀索引 (空间换时间)

PUT my_index
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "index_prefixes": {
          "min_chars":2,  
          "max_chars":4
        }    
      }
    }
  }
}

上面这个设置的意思是,把分词后的每个词项的2-4个字符额外进行建立前缀倒排索引,从而提高后续前缀匹配的速度,但是占用空间也是相对变大。
index_prefixes: 默认 “min_chars” : 2, “max_chars” : 5 。

通配符查询

通配符查询类似于正则,但没正则强大,允许对匹配表达式进行通配符占位。

  • 表示匹配任意长度的任意字符
    ? 表示匹配一个任意字符
    […]则表示匹配括号中列出的字符中的任意一个
    [!..]表示不匹配括号中列出的字符中的任意一个

语法

{
  "query": {
    "wildcard": {
      "text": {
        "value": "eng?ish"
      }
    }
  }
}

正则查询

regexp查询的性能可以根据提供的正则表达式而有所不同。为了提高性能,应避免使用通配符模式,如.或 .?+未经前缀或后缀

语法

{
  "query": {
    "regexp": {
      "name": {
        "value": "[\\s\\S]*nfc[\\s\\S]*",
        "flags": "ALL",
        "max_determinized_states": 10000, #防止正则内存过大的保护措施
        "rewrite": "constant_score"
      }
    }
  }
}

关于参数flags,有几个配置可选:

ALL (Default)

启用所有可选操作符。

COMPLEMENT

启用操作符。可以使用对下面最短的模式进行否定。例如
a~bc # matches ‘adc’ and ‘aec’ but not ‘abc’
INTERVAL
启用<>操作符。可以使用<>匹配数值范围。例如
foo<1-100> # matches ‘foo1’, ‘foo2’ … ‘foo99’, ‘foo100’
foo<01-100> # matches ‘foo01’, ‘foo02’ … ‘foo99’, ‘foo100’

INTERSECTION

启用&操作符,它充当AND操作符。如果左边和右边的模式都匹配,则匹配成功。例如:
aaa.+&.+bbb # matches ‘aaabbb’

ANYSTRING

启用@操作符。您可以使用@来匹配任何整个字符串。
您可以将@操作符与&和~操作符组合起来,创建一个“everything except”逻辑。例如:
@&~(abc.+) # matches everything except terms beginning with ‘abc’

Fuzzy模糊(容错)匹配

场景

1、混淆字符 (box → fox)
2、缺少字符 (black → lack)
3、多出字符 (sic → sick)
4、颠倒次序 (act → cat)

在出现上面情况的时候,我们也希望用户可以搜索到想要的内容,那么这个时候可以使用fuzzy。

语法

以下两种都可以:

1、第一种-手动档
可以手动多指定一些参数,但一般也不建议改动

{
  "query": {
    "fuzzy": {
      "desc": {
        "value": "quangemneng",
        "fuzziness": 5
      }
    }
  }
}

① value:(必需,字符串)
② fuzziness:(可选,字符串)最大误差 并非越大越好, 因为大了虽然召回率高 但是结果不准确
1) 两段文本之间的Damerau-Levenshtein距离是使一个字符串与另一个字符串匹配所需的插入、删除、替换和调换的数量
2) 距离公式:Levenshtein是lucene的,es改进版:Damerau-Levenshtein
3) axe=>aex Levenshtein=2 Damerau-Levenshtein=1

③ max_expansions:可选,整数)匹配的最大词项数量。默认为50。
④ prefix_length:创建扩展时保留不变的开始字符数。默认为0
1)避免在max_expansions参数中使用较高的值,尤其是当prefix_length参数值为时0。max_expansions由于检查的变量数量过多,参数中的高值 可能导致性能不佳。

⑤ transpositions:(可选,布尔值)指示编辑是否包括两个相邻字符的变位(ab→ba)。默认为true。
⑥ rewrite:(可选,字符串)用于重写查询的方法
https://www.elastic.co/cn/blog/found-fuzzy-search#performance-considerations

2、第二种,"自动挡"时代

{
  "query": {
    "match": {
      "desc": {
        "query": "quangengneng nfc",
        "fuzziness": "AUTO"
      }
    }
  }
}

match_phrase_prefix(最简陋的Suggest)

match_phrase_prefix与match_phrase相同,但是它多了一个特性,就是它允许在文本的最后一个词项(term)上的前缀匹配,如果 是一个单词,比如a,它会匹配文档字段所有以a开头的文档,如果是一个短语,比如 “this is ma” ,他会先在倒排索引中做以ma做前缀搜索,然后在匹配到的doc中做match_phrase查询,(网上有的说是先match_phrase,然后再进行前缀搜索, 是不对的)

语法

{
  "query": {
    "match_phrase_prefix": {
      "desc": {
        "query": "zhichi quangongneng nf",
        "analyzer": "whitespace",
        "max_expansions": 1,
        "slop": 2,
        "boost": 1
      }
    }
  }
}

参数

analyzer

指定何种分析器来对该短语进行分词处理

max_expansions

限制匹配的最大term数。

  1. 一般来讲,前缀匹配是会全索引进行扫描匹配的,为了提高效率,可以进行限制它可以进行扫描的索引的个数,但即使设置为1,也不意味着返回的doc结果只有一个,主要有2点:
    1、一个索引可能有多个doc
    2、这个限制扫描个数是针对每个分片来说的(每个分片都可以扫描1个),因此也就是说该索引的每个分片扫描的第一个term都可能被匹配上。

boost

用于设置该查询的权重

slop

允许短语间的词项(term)间隔

slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配, 什么是相隔多远? 意思是说为了让查询和文档匹配你需要移动词条多少次?举个例子:

如果我们输入的是:de zhong shouji hongzhaji
而期望匹配的句子是:shouji zhong de hongzhaji
那么要怎么移动呢?
1、首先要把“shouji”词条向左移动2个词条:
shouji/de zhong shouji hongzhaji
2、接下来在把de向右移动2个词条:
shouji zhong de shouji hongzhaji

这样下来,共需要4次移动,因此slop需要设置为4的时候,输入的才能匹配上。

N-gram-tokenFilter

上面的前缀匹配还是存在性能问题,那有没有相对好一点的方法呢? 我们可以从分词角度出发。
在设置索引的时候,可以进行指定分词器的相关属性,其中有一项是指定fliter,可以通过指定ngram:

{
  "settings": {
    "analysis": {
      "filter": {
        "2_3_grams": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 2
        }
      },
      "analyzer": {
        "my_ngram": {
          "type":"custom",
          "tokenizer": "standard",
          "filter": [ "2_3_grams" ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text",
        "analyzer":"my_ngram",
        "search_analyzer": "standard"
      }
    }
  }
}

经过ngram设置的min_gram:1和"max_gram": 3,分析以下语句:

GET _analyze
{
  "tokenizer": "ik_max_word",
  "filter": [ "edge_ngram" ],
  "text": "reba always"
}

会先按最小粒度1进行拆分,也就是拆分出“r”,“e”,“b”,“a”,“a”,“l”,“w”,“a”,“y”,“s”
然后按粒度2拆分,拆分成:“re”,“eb”,“ba”,“al”,“lw”,“wa”,“ay”,“ys”
然后按最大粒度3进行拆分,拆分成:“reb”,“eba”,“alw”,“lwa”,“way”,“ays”

Edge-N-gram-tokenFilter

另外一个filter,根据min_gram和max_gram对分词的开头部分进行拆分。
这个可能更常用一些,因为更多的我们的搜索是从一个词的开头进行部分搜索,而不是中间进行搜索。

GET _analyze
{
  "tokenizer": "ik_max_word",
  "filter": [ "edge_ngram" ],
  "text": "reba always loves me"
}

min_gram =1 “max_gram”: 1
拆分情况:r a l m

min_gram =1 “max_gram”: 2
拆分情况:
r a l m
re al lo me

min_gram =2 “max_gram”: 3
拆分情况:
re al lo me
reb alw lov me

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值