Elasticsearch 入门教程 -前缀与通配符搜索

假设将邮编作为 not_analyzed 的精确值字段索引,所以可以为其创建索引,如下:

PUT /my_index
{
    "mappings": {
        "address": {
            "properties": {
                "postcode": {
                    "type":  "string",
                    "index": "not_analyzed"
                }
            }
        }
    }
}

然后索引一些邮编:

PUT /my_index/address/1
{ "postcode": "W1 3DG" }

PUT /my_index/address/2
{ "postcode": "W2F 8HW" }

PUT /my_index/address/3
{ "postcode": "W1 7HW" }

PUT /my_index/address/4
{ "postcode": "WC1N 1LZ" }

PUT /my_index/address/5
{ "postcode": "SW5 0BE" }

prefix 前缀查询

编辑

  为了找到所有以 W1 开始的邮编,可以使用简单的 prefix 查询:

   类似于SQL: select * from table where xx like 'xx%';

GET /my_index/address/_search
{
    "query": {
        "prefix": {
            "postcode": "W1"
        }
    }
}


Java 客户端代码:

  QueryBuilders.prefixQuery("postcode", "W1");

Match的短语匹配搜索:

短语匹配查询(match_phrase)

     在执行短语匹配查询时,ElasticSearch引擎首先分析(analyze)查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配短语中的所有分词,并且保证各个分词的相对位置不变:

POST /_search -d
{  
   "from":1,
   "size":100,
   "fields":[ "eventname"],
   "query":{  
      "match_phrase":{  
         "eventname":"Open Source"
      }
   }
}
 QueryBuilders.matchPhraseQuery("postcode", "W1");

短语前缀匹配查询(match_phrase_prefix)

    除了把查询文本的最后一个分词只做前缀匹配之外,match_phrase_prefix和match_phrase查询基本一样,参数 max_expansions 控制最后一个单词会被重写成多少个前缀,也就是,控制前缀扩展成分词的数量,默认值是50。扩展的前缀数量越多,找到的文档数量就越多;如果前缀扩展的数量太少,可能查找不到相应的文档,遗漏数据。如代码所示,能够查到eventname包含"Open Source Hack Night"的文档。

POST /_search -d
{  
   "from":1,
   "size":100,
   "fields":[ "eventname" ],
   "query":{  
      "match_phrase_prefix":{  
         "eventname":{  
            "query":"Open Source hac",
            "max_expansions":50
         }
      }
   }
}
 QueryBuilders.matchPhrasePrefixQuery("title", "C3").maxExpansions(50);

前缀搜索的原理

     prefix query不计算relevance score,与prefix filter唯一的区别就是,filter会cache bitset

   扫描整个倒排索引,举例说明

    前缀越短,要处理的doc越多,性能越差,尽可能用长前缀搜索

    前缀搜索,它是怎么执行的?性能为什么差呢?   

    回想倒排索引包含了一个有序的唯一词列表(本例是邮编)。 对于每个词,倒排索引都会将包含词的文档 ID 列入 倒排表(postings list) 。与示例对应的倒排索引是:

   不分词的情况:

Term:          Doc IDs:
-------------------------
"SW5 0BE"    |  5
"W1 7HW"    |  3
"W1 3DG"    |  1
"W2F 8HW"    |  2
"WC1N 1LZ"   |  4
-------------------------
W1--> 先扫描到了W1 7HW,很棒,找到了一个前缀带W1的字符串 --> 还是要继续搜索的,因为后面还有一个W1 3DG,也许还有其他很多的前缀带W1的字符串 -->
 你扫描到了一个前缀匹配的term,不能停,必须继续搜索 --> 直到扫描完整个的倒排索引,才能结束

   分词的情况:

Term:          Doc IDs:
-------------------------
"SW5"    |    1
"W1"     |    1,3
"3DG"    |    1
"W2F"    |    2
-------------------------

     使用match性能往往是很高的,W1–> 扫描倒排索引 –> 一旦扫描到W1,就可以停了,因为带W1的就2个doc,已经找到了 –> 没有必要继续去搜索其他的term了;

  那么有哪些场景是全文检索解决不了的了:

如: 

  C3D0-KD345

分词后:  c3d0  kd345

  c3 –> match –> 扫描整个倒排索引,能找到吗

   c3 –> 只能用prefix

通配符与正则表达式查询

      与 prefix 前缀查询的特性类似, wildcard 通配符查询也是一种底层基于词的查询, 与前缀查询不同的是它允许指定匹配的正则式。它使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。

  这个查询会匹配包含 W1F 7HW 和 W2F 8HW 的文档:

GET /my_index/address/_search
{
   "query": {
       "wildcard": {
           "postcode": "W?F*HW"
       }
   }
}

   QueryBuilders.wildcardQuery("postcode""W?F*HW"); 

? 匹配 1 和 2 , * 与空格及 7 和 8 匹配。

设想如果现在只想匹配 W 区域的所有邮编,前缀匹配也会包括以 WC 开头的所有邮编,与通配符匹配碰到的问题类似,如果想匹配只以 W 开始并跟随一个数字的所有邮编, regexp 正则式查询允许写出这样更复杂的模式:

GET /my_index/address/_search
{
   "query": {
       "regexp": {
           "postcode": "W[0-9].+"
       }
   }
}

 QueryBuilders.regexpQuery("postcode", "W[0-9].+");

    这个正则表达式要求词必须以 W 开头,紧跟 0 至 9 之间的任何一个数字,然后接一或多个其他字符。

     wildcard和regexp,与prefix原理一致,都会扫描整个索引,性能很差;数据在索引时的预处理有助于提高前缀匹配的效率,而通配符和正则表达式查询只能在查询时完成,尽管这些查询有其应用场景,但使用仍需谨慎。

      prefix 、 wildcard 和 regexp 查询是基于词操作的,如果用它们来查询 analyzed 字段,它们会检查字段里面的每个词,而不是将字段作为整体来处理。

    比方说包含 “Quick brown fox” (快速的棕色狐狸)的 title 字段会生成词: quick 、 brown 和 fox 。

会匹配以下这个查询:

{ "regexp": { "title": "br.*" }}

但是不会匹配以下两个查询:

{ "regexp": { "title": "Qu.*" }} { "regexp": { "title": "quick br*" }} 

在索引里的词是 quick 而不是 Quick 。

quick 和 brown 在词表中是分开的。

发表评论