Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了

引言

昨天是感恩节,上幼儿园的女儿在老师的叮嘱下,晚上为我和老婆洗了脚(形式上的^_^),还给咱们每人端了一杯水。看着孩子一每天的长大,懂事,感受很开心,话说我们程序员这么辛苦是为了什么?不就是为了老婆,孩子,热炕头,有一个温暖幸福的家庭,再捎带着用代码改变一下世界吗?想到这里,顿时以为学习,创做博客的劲头也的更足了。哈哈,扯远了,书归正传,今天咱们来聊聊 Match Query。java

Match Query 是最经常使用的 Full Text Query 。不管须要查询什么字段, match 查询都应该会是首选的查询方式。它既能处理全文字段,又能处理精确字段。程序员

构建示例

为了可以在后面能深刻理解 Match Query 中的各个属性的意义,咱们先构建一个 index 示例(有兴趣的同窗只要将下面字段粘贴到 sense 中就能够建立)。json

PUT matchtest
{ 
}

PUT matchtest/_mapping/people
{
  "properties": {
    "age": {
      "type": "integer"
    },
    "hobbies": {
      "type": "text"
    },
    "name": {
      "type": "keyword"
    }
  }
}

PUT matchtest/people/1
{
  "name" : "Jim",
  "age": 10,
  "hobbies": "football, basketball, pingpang"
}


PUT matchtest/people/2
{
  "name" : "Tom",
  "age": 12,
  "hobbies": "swimming, football"
}

match

operator 参数

match 查询是一种 bool 类型的查询。什么意思呢?举个例子,查询 people type 的 hobbies 为 football basketballapi

GET matchtest/people/_search
{
  "query": {
    "match": {
      "hobbies": "football basketball"
    }
  }
}

会将上面的两个文档都搜索出来。为何?上面的查询其实隐藏了一个默认参数operator , 它的默认值是 or ,也就是说上面的查询也能够写成这种形式app

GET matchtest/people/_search
{
  "query": {
    "match": {
      "hobbies": {
        "query": "football basketball",
        "operator": "or"
      }
    }
  }
}

这样就比较容易理解了,既然是 or 操做符,就表示只要查询的文档的 hobbies 字段中含有 footballbasketball 任意一个,就能够被匹配到。elasticsearch

若是将 operator 操做符的值改成 and ,则表示须要同时包含 footballbasketball , 获得的结果就只能是 文档 1 Jim 小朋友了。ide

analyzer

analyzer 属性是指在对查询文本分析时的分析器学习

  • 若是没有指定则会使用字段mapping 时指定的分析器
  • 若是字段在 mapping 时也没有明显指定,则会使用默认的 search analyzer。

这里咱们也没有指定,就会使用默认的,就不举例了,在后面文章讲解 analyzer 时再拓展。优化

lenient 参数

默认值是 false , 表示用来在查询时若是数据类型不匹配且没法转换时会报错。若是设置成 true 会忽略错误。

例如, 例子中的 ageinteger 类型的,若是查询 age=xxy ,就会致使没法转换而报错。

GET matchtest/_search
{
  "query": {
    "match": {
      "age" : {
        "query": "xxx"
      }
    }
  }
}

而若是将 lenient 参数设置为 true ,就会忽略这个错误

GET matchtest/_search
{
  "query": {
    "match": {
      "age" : {
        "query": "xxx",
        "lenient": true
      }
    }
  }
}

注意,若是将 age 字段的值设置为字符串 "10", 来查询,因为可以转换成整数,这时 elastic 内部会将 字符串先转换成整数再作查询,不会报错。

Fuzziness

fuzzniess 参数

fuzziness 参数可使查询的字段具备模糊搜索的特性。来先了解下什么是模糊搜索。

什么是模糊搜索?

模糊搜索是指系统容许被搜索信息和搜索提问之间存在必定的差别,这种差别就是“模糊”在搜索中的含义。例如,查找名字Smith时,就会找出与之类似的Smithe, Smythe, Smyth, Smitt等。

——百度百科

经过模糊搜索能够查询出存在必定类似度的单词,那么怎么计算两个单词是否有类似度以及类似度的大小呢?这就要了解下另一个概念:Levenshtein Edit Distance

Levenshtein Edit Distance

Levenshtein Edit Distance 叫作莱文斯坦距离**,是编辑距离的一种。指两个字串之间,由一个转成另外一个所需的最少编辑操做次数。容许的编辑操做包括将一个字符替换成另外一个字符,插入一个字符,删除一个字符。

例如,单词 "god" 只须要插入一个 'o' 字符就能够变为 "good",所以它们之间的编辑距离为 1。

fuzziness 参数取值规则

了解了上面两个概念,回过头再来看下 fuzziness 参数。

在查询 text 或者 keyword 类型的字段时, fuzziness 能够看作是莱文斯坦距离。

fuzziness 参数的取值以下

  • 0,1,2 表示最大可容许的莱文斯坦距离

  • AUTO

    会根据词项的长度来产生可编辑距离,它还有两个可选参数,形式为AUTO:[low],[high], 分别表示短距离参数和长距离参数;若是没有指定,默认值是 AUTO:3,6 表示的意义以下

    • 0..2

      单词长度为 0 到 2 之间时必需要精确匹配,这其实很好理解,单词长度过短是没有类似度可言的,例如 'a' 和 'b'。

    • 3..5

      单词长度 3 到 5 个字母时,最大编辑距离为 1

    • >5

      单词长度大于 5 个字母时,最大编辑距离为 2

    最佳实践: fuzziness 在绝大多数场合都应该设置成 AUTO

若是不设置 fuziness 参数,查询是精确匹配的。

来看例子,上面建立了一个 doc

PUT matchtest/people/1
{
  "name" : "Jim",
  "age": 10,
  "hobbies": "football, basketball, pingpang"
}

设置 fuzzinessAUTO

  • 其中 hobbies 字段的值 football 长度 > 5, 此时咱们找一个编辑距离为 2 的单词 footba22 来查询,应该匹配到
  • 其中 name 字段的值 jim 长度在 3 和 5 之间,此时找一个编辑距离为 1 的单词 jiO 应匹配到,而编辑距离为 2 的 jOO 就不该匹配到。

来,验证下

GET matchtest/_search
{
  "query": {
    "match": {
      "hobbies": {
        "query": "footba22",
        "fuzziness": "AUTO"
      }
    }
  }
}

GET matchtest/_search
{
  "query": {
    "match": {
      "name": {
        "query": "jiO",
        "fuzziness": "AUTO"
      }
    }
  }
}


GET matchtest/_search
{
  "query": {
    "match": {
      "name": {
        "query": "jOO",
        "fuzziness": "AUTO"
      }
    }
  }
}

prefix_length

prefix_length 表示不能没模糊化的初始字符数。因为大部分的拼写错误发生在词的结尾,而不是词的开始,使用 prefix_length 就能够完成优化。注意 prefix_length 必须结合 fuzziness 参数使用。

例如,在查询 hobbies 中的 football 时,将 prefix_length 参数设置为 3,这时 foatball 将不能被匹配。

GET matchtest/_search
{
  "query": {
    "match": {
      "hobbies": {
        "query": "foatball",
        "fuzziness": "AUTO",
        "prefix_length": 3
      }
    }
  }
}

TODO(max_expansions 参数对于 match 查询而言,没理解表示的意义,若是您知道这个参数的用法,请给我留言告知,不胜感谢! )

Zero terms Query

先看例子, 先建立一个文档 zero_terms_query_test 其中 message 字段使用 stop 分析器,这个分析器会将 stop words 停用词在索引时全都去掉。

PUT matchtest1

PUT matchtest1/_mapping/zero_terms_query_test
{
  "properties": {
    "message": {
      "type": "text",
      "analyzer": "stop"
    }
  }
}


PUT matchtest1/zero_terms_query_test/1
{
  "message": "to be or not to be"
}

GET matchtest1/_search
{
  "query": {
    "match": {
      "message": {
        "query": "to be or not to be",
        "operator": "and",
        "zero_terms_query": "none"
      }
    }
  }
}

那么就像 message 字段中的 to be or not to be 这个短语中所有都是中止词,一过滤,就什么也没有了,得不到任何 tokens, 那搜索时岂不什么都搜不到。

POST _analyze
{
  "analyzer": "stop",
  "text": "to be or not to be"
}

zero_terms_query 就是为了解决这个问题而生的。它的默认值是 none ,就是搜不到中止词(对 stop 分析器字段而言),若是设置成 all ,它的效果就和 match_all 相似,就能够搜到了。

GET matchtest1/_search
{
  "query": {
    "match": {
      "message": {
        "query": "to be or not to be",
        "operator": "and",
        "zero_terms_query": "all"
      }
    }
  }
}

Cutoff frequency

查询字符串时的词项会分红低频词(更重要)和高频词(次重要)两类,像前面所说的停用词 (stop word)就属于高频词,它虽然出现频率较高,但在匹配时可能并不太相关。实际上,咱们每每是想要文档能尽量的匹配那些低频词,也就是更重要的词项

要实现这个需求,只要在查询时配置 cutoff_frequency 参数就能够了。假设咱们将 cutoff_frequency 设置成 0.01 就表示

  • 任何词项在文档中超过 1%, 被认为是高频词
  • 其余的词项会被认为低频词

从而将高频词(次重要的词)挪到可选子查询中,让它们只参与评分,而不参与匹配;高频词(更重要的词)参与匹配和评分。

这样一来,就再也不须要 stopwords 停用词文件了,从而变成了动态生成停用词: 高频词就会被看作是停用词。这种配置只是对于词项比较多的场合如 email body,文章等适用,文字太少, cutoff_frequency 选项设置的意义就不大了。

cutoff_frequency 配置有两种形式

  • 指定为一个分数( 0.01 )表示出现频率
  • 指定为一个正整数( 5 )则表示出现次数

下面给个例子, 在建立的 3 个文档中都包含 "be " 的单词,在查询时将 cutoff_frequency 参数设置为 2, 表示 "be" 就是高频词,只会参与评分,但在匹配时不作考虑。

此时查询的内容为 "to be key" ,因为 "be" 词项是高频词,由于在文档中必需要存在 "to" 或者 "key" 才能匹配,所以文档 3 不能匹配。

PUT /matchtest2

PUT matchtest2/_mapping/cutoff_frequency_test
{
  "properties": {
    "message": {
      "type": "text"
    }
  }
}

PUT matchtest2/cutoff_frequency_test/1
{
  "message": "to be or not to be to be or"
}

PUT matchtest2/cutoff_frequency_test/2
{
  "message": "be key or abc"
}

PUT matchtest2/cutoff_frequency_test/3
{
  "message": "or to be or to to be or"
}

GET matchtest2/_search
{
  "query": {
    "match": {
      "message": {
        "query": "to be key",
        "cutoff_frequency": 2
      }
    }
  }
}

synonyms

synonyms 是指同义词,只要索引和字段中配置了同义词过滤器,match 查询是支持多词条的同义词扩展的。在应用过滤器后,解析器会对每一个屡次条同义词建立一个语句查询。

例如,同义词 USA, united states of America 就会构建出 (USA OR ("united states of America"))。看下面例子:

PUT /matchtest4
{
    "settings": {
        "index" : {
            "analysis" : {
                "analyzer" : {
                    "synonym" : {
                        "tokenizer" : "whitespace",
                        "filter" : ["synonym"]
                    }
                },
                "filter" : {
                    "synonym" : {
                        "type" : "synonym",
                        "synonyms" : [
                            "USA, united states of America"
                        ]
                    }
                }
            }
        }
    }
}

PUT /matchtest4/_mapping/synonyms_test
{
  "properties": {
    "message": {
      "type": "text",
      "analyzer": "synonym"
    }
  }
}

PUT /matchtest4/synonyms_test/1
{
  "message": "united states of America people"
}


GET /matchtest4/_search
{
  "query": {
    "match": {
      "message": {
        "query": "USA"
      }
    }
  }
}

小结

本文以代码实例的方式完整的讲解了 Match Query 的各类使用场景和参数意义。下篇会讲解 Match Phrase Query 敬请期待。

参考文档

系列文章列表

Query DSL

  1. Query DSL 概要,MatchAllQuery,全文查询简述

Java Rest Client API

  1. Elasticsearch Java Rest Client API 整理总结 (一)——Document API
  2. Elasticsearch Java Rest Client API 整理总结 (二) —— SearchAPI
  3. Elasticsearch Java Rest Client API 整理总结 (三)——Building Queries
相关文章
相关标签/搜索