引言

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

Match Query 是最常用的 Full Text Query 。无论需要查询什么字段, match 查询都应该会是首选的查询方式。它既能处理全文字段,又能处理精确字段。

构建示例

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

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 basketball

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

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

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

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

如果将 operator 操作符的值改为 and ,则表示需要同时包含 footballbasketball , 得到的结果就只能是 文档 1 Jim 小朋友了。

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

Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了的更多相关文章

  1. Elasticsearch Query DSL 整理总结(四)—— Multi Match Query

    目录 引言 概要 fields 字段 通配符 提升字段权重 multi_match查询的类型 best_fields 类型 dis_max 分离最大化查询 best_fields 维权使者 tie_b ...

  2. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  3. Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介

    Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...

  4. Elasticsearch Query DSL 整理总结(三)—— Match Phrase Query 和 Match Phrase Prefix Query

    目录 引言 Match Phase Query slop 参数 analyzer 参数 zero terms query Match Phrase 前缀查询 max_expansions 小结 参考文 ...

  5. Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述

    目录 引言 概要 Query and filter context Match All Query 全文查询 Full text queries 小结 参考文档 引言 虽然之前做过 elasticse ...

  6. Python 3 入门,看这篇就够了(超全整理)

    史上最全Python资料汇总(长期更新).隔壁小孩都馋哭了 --- 点击领取 今天和大家分享的内容是Python入门干货,文章很长. 简介 Python 是一种高层次的结合了解释性.编译性.互动性和面 ...

  7. 【原创】阿里三面:搞透Kafka的存储架构,看这篇就够了

    阅读本文大约需要30分钟.这篇文章干货很多,希望你可以耐心读完. 你好, 我是华仔,在这个 1024 程序员特殊的节日里,又和大家见面了. 从这篇文章开始,我将对 Kafka 专项知识进行深度剖析, ...

  8. 搞透 IOC,Spring IOC 看这篇就够了!

    IOC与AOP属于Spring的核心内容,如果想掌握好Spring你肯定需要对IOC有足够的了解 @mikechen IOC的定义 IOC是Inversion of Control的缩写,多数书籍翻译 ...

  9. elasticsearch系列四:搜索详解(搜索API、Query DSL)

    一.搜索API 1. 搜索API 端点地址 从索引tweet里面搜索字段user为kimchy的记录 GET /twitter/_search?q=user:kimchy 从索引tweet,user里 ...

随机推荐

  1. .net图表之ECharts随笔06-这才是最简单的

    今天搞柱形图的时候,发现了一个更简单的用法.那就是直接使用带all的那个js文件 基本步骤: 1.为ECharts准备一个具备大小(宽高)的Dom 2.ECharts的js文件引入(echarts-a ...

  2. ASP.NET MVC 使用 Log4net 记录日志

    Log4net 介绍 Log4net 是 Apache 下一个开放源码的项目,它是Log4j 的一个克隆版.我们可以控制日志信息的输出目的地.Log4net中定义了多种日志信息输出模式.它可以根据需要 ...

  3. #loj3090 [BJOI2019] 勘破神机

    简单线性代数练习题 首先翻开具体数学生成函数一章,可以发现\(F(n),G(n)\)满足以下递推式 \[F(n)=F(n-1)+F(n-2),F(0)=1,F(1)=1\] \[G(n)=4G(n-2 ...

  4. OSX10.12搭建IPv6本地环境测试APP

    前记 最近刚换了工作,生活终于又安定下来了,又可以更博了 正文 最近公司在上线APP(整体全是用JS去写的,就用了我原生的一个控制器),然后APP就去上线,就被苹果巴巴给拒了.通过阅读苹果回复的邮件, ...

  5. 7. Bagging & Random Forest

    通过前面集成学习的介绍我们知道,欲得到泛化性能强的集成学习器,集成中个体学习器应尽量相互独立:虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能具有较大差异. 1. Bagging 自助采样 ...

  6. javascript数据结构与算法---检索算法(二分查找法、计算重复次数)

    javascript数据结构与算法---检索算法(二分查找法.计算重复次数) /*只需要查找元素是否存在数组,可以先将数组排序,再使用二分查找法*/ function qSort(arr){ if ( ...

  7. 安装ORACLE时 各Linux版本下载地址

    oracle linux :https://edelivery.oracle.com/osdc/faces/SearchSoftware 需要注册oracle账号 redhat官方下载 https:/ ...

  8. js 开发过程中经验及总结记录

    一   let 和 var 作用域    1  普通用法 for (var i = 0; i < 5; i++) { console.log(i); } console.log(i); //-- ...

  9. 用AOP思想改造一个服务器的数据存储

    背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增\删\改的 ...

  10. Centos 7 快速搭建IOS可用IPsec

    安装 strongswan yum install -y http://ftp.nluug.nl/pub/os/Linux/distr/fedora-epel/7/x86_64/Packages/e/ ...