文章目录
ElasticSearch - 玩转搜索之花式查询
1.term的多种查询
之前的文章我们讲过,
term
词条查询是属于一种单词级别的查询。这种查询通常用于结构化数据。⽐如number
、date
、keyword
等,⽽不是对text
,text
我们更多的还是会使用match
进行匹配。
也就是说全⽂本查询之前,会先对⽂本内容进⾏分词;⽽单词级别的查询直接在相应字段的反向索引中精确查找,单词级别的查询⼀般⽤于数值、⽇期等类型的字段上。
1.1 索引以及数据准备
我们依然以之前的NBA数据为例,我们新建一个NBA索引,这里大家只需要大概扫一眼映射各字段类型是什么即可。
PUT nba
{
"mappings": {
"properties": {
"birthDay": {
"type": "date"
},
"birthDayStr": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"code": {
"type": "text"
},
"country": {
"type": "text"
},
"countryEn": {
"type": "text"
},
"displayAffiliation": {
"type": "text"
},
"displayName": {
"type": "text"
},
"displayNameEn": {
"type": "text"
},
"draft": {
"type": "long"
},
"heightValue": {
"type": "float"
},
"jerseyNo": {
"type": "text"
},
"playYear": {
"type": "long"
},
"playerId": {
"type": "keyword"
},
"position": {
"type": "text"
},
"schoolType": {
"type": "text"
},
"teamCity": {
"type": "text"
},
"teamCityEn": {
"type": "text"
},
"teamConference": {
"type": "keyword"
},
"teamConferenceEn": {
"type": "keyword"
},
"teamName": {
"type": "keyword"
},
"teamNameEn": {
"type": "keyword"
},
"weight": {
"type": "text"
}
}
}
}
索引建完之后,要想玩转ES就需要有数据。数据来源的话大家可以通过爬虫或者一些其他方式都能够轻易获取,当然爬来得数据别进行商用,小心被抓哦!
这里我也是借用了一下其他途径的数据,是我一个平时经常学习的教程网站【小D课堂】,后面我上传到码云上会把地址更新到这。这里也不是打广告,毕竟咱也没分成也没人点,就是在学习分享的漫漫长路上,希望给大家分享一些我自己通过努力去辨别的好的学习方式和途径。
有了数据之后,上一篇博客【ElasticSearch - 批量导入数据】就起到关键作用了。我们通过
curl -X POST "http://111.229.125.192:9200/_bulk" -H 'Content-Type: application/json' --data-binary @player
很轻松就将数据导入到我们的ES当中。这里验证了一下也是OK的,那么接下来我们就要开始ES的花式查询之旅了。
1.2 term精准匹配查询
先来温习一下我们之前就使用过的,我们可以使用
term
精准匹配号码为1的球员信息。
1.3 Exist非空值文档查询
当我们需要在所有NBA球员当中找出现已签约球队的球员,我们需要如何做呢?
这里我们通过exist
就查询出所有teamName
非空值的文档。这里无论是teamName
字段不存在或在null
都会被排除在外。
1.4 Prefix前缀查询
我们无论是在软件使用还是开发过程当中,多多少少都遇见过这样一个场景。某个类型从A-Z可以获取以每个字母开头的数据。
假设我们需要获取所有球队属于R
开头的球员信息,那么我们通过prefix
即可查找到队名以R
开头的球员信息。
1.5 Wildcard通配符查询
ES支持通配符查询,我们还可以通过
wildcard
查找到队名以R
开头的球员信息。这里*
表示任意字符,?
表示任意单个字符。
比起前缀匹配的话,通配符匹配更加灵活,我们可以只匹配以球队R
开头以rs
结尾的球员信息。
甚至我们可以匹配球队以R
开头,以et
加任意一个字符为后缀的球员信息。
1.6 Regexp通配符查询
在匹配这件事上,正则表达式就没输过谁,ES同样也是支持正则表达式的。
这里我们匹配球队以R
开头以rs
结尾,除了上面的写法之外,我们还可以通过正则表达式这样来表示。
1.7 Ids批量ID查询
当我们知道一个文档的ID时,我们可以根据
GET /nba/_doc/1
去获取信息,那如果我们知道一堆ID的话能不能批量获取出来呢?
ES这么强大当然是提供了这种功能的,这里只要通过ids
的查询就可以批量获取出指定ID的信息。
2.Range范围查询
范围查询是一个很基本的应用场景,ES当然也是支持的。我们可以通过ES查找指定字段在指定范围内包含值的文档,一般主要用在数字、日期、字符串这些类型的条件上。
这里我们通过range
搭配gte
和lte
,找出了在NBA有20-25年球龄的球员,结果只有两名。文斯卡特和诺维斯基,妥妥的老球员了,相信看NBA的同学都应该认识玄冥二老了吧。
我们再来看一看20岁以下的球员,足足有43位。看来NBA还是年轻人占据大多数啊。
这里我们再来看看00后有哪些球员,这个排在首位的也是一名我比较喜欢的球员巴雷特,除了球打得好还和我让我想起了以前我穿越火线里面那把巴雷特。这里如果我们没有指定排序的话,ES会默认按照ID给我们进行排序。
3.Bool布尔查询
布尔查询是一个复合过滤器,可以接受多个其他过滤器作为参数,并且将这些过滤器结合成各种组合。布尔查询在平时业务中使用的也是比较多的一种,所以大家一定要牢记。
这里先介绍一下四个布尔查询的子句类型。
type | description |
---|---|
must | 必须出现在匹配⽂档中,即所有条件必须匹配,类似AND |
filter | 必须出现在匹配⽂档中,即所有条件必须匹配,但不计算分值 |
must_not | 不能出现在⽂档中,即所有语句都不能匹配 |
should | 应该出现在⽂档中,即至少有一个条件匹配,类似OR |
3.1 must
首先我们要查找名字叫做
詹姆斯
的球员。这里可能和我们直接使用match
查询没什么区别。
我们更进一步要查找名字叫做詹姆斯
,并且球队属于东部
的球员数据。这里可以发现很明显数据进行了更深层次的筛选。
我们上面了解到,must
类型的子句必须满足所有条件才可以匹配。那我们只要对一个字段同时构建两个不同的查询条件,那肯定就找不到任何数据了。
3.2 filter
filter
功能和must
完全一样,主要的区别就是不会进行计算分值,那么难道就仅仅如此吗?非也,介绍完四种之后我们浅析一下他们的关键区别。
3.3 must_not
我们通过
must_not
可以查找名字叫做保罗
并且不是东部的球员。
3.4 should
should
这个查询就很有意思了,他表示应该满足这个条件。即使不满足也会查询出来,只是打分标准不同。这里我们的语句表达查询出名字叫做勒布朗
并且应该球龄在15年以上的球员信息,可以看到有80位。
仔细翻一翻会发现,后面的数据似乎都和我们should
中的条件没啥关系。
这里给大家介绍一个有趣的参数minimum_should_match
,这个参数表示至少满足n个should
子句。当我们查询上加上minimum_should_match=1
,整个查询的意义就变成了名字叫勒布朗
并且球龄大于15年的球员信息。
3.5 must和filter的区别
这里回忆一下我们上面介绍
must
和filter
的时候,只是简单介绍了一下二者使用相同,主要区别就是filter
不会计算分值,那么这里我们就来看一看具体是什么情况。
我们用同一个Bool语句只是变更must
和filter
会发现,似乎在响应时间上并没有太大的提升?这主要还是因为我们现阶段使用的数据量十分少,所以其实大部分响应都花在了网络连接上。当我们数据量起来了之后,自然而然会有更明显的感受。
那既然我们数据量不够大,怎么就能确定filter
比must
效率更高呢?这里我们要先来了解一个概念,那就是ES当中的query context
和filter context
。
query context
关注的是文档与查询条件匹配的程度,而这个匹配的程度就由ES计算的相关性分数决定,也就是之前我给大家标注出来的每个文档都有一个的score
。所以除了需要关注查询条件匹配度,还需要去进行相关分数的计算。
filter context
关注的则仅仅只是是否匹配,无需额外计算相关性分数。并且这种查询ES还会自动缓存结果,进一步提升效率。
这里我们就来真实验证一下正确性。大家如果安装了kibana
的也可以自行去试试,kibana
当中提供了一个Search Profile
可供我们查看查询的详细过程。
我们先使用must
语句来看看整个过程,这里我们运行后可以查看BooleanQuery
的详情。
这里可以看到整个过程花费了0.345ms
,而score
占了11%的时间,说明是计算相关性分数的。
我们把条件换为filter
,我们先不管这个时间差多少,最关键的一点是这里我们可以看到score
为0,意味着并不会进行相关性分数计算。
而且在没有计算分数时,整体的查询时间确实也得到了提升。所以我们应该根据自己的业务场景选择具体使用哪一种条件去进行查询才能更合适。
4.Sort排序查询
排序也是我们在开发过程当中十分常见的一个需求,并且基本所有业务都多多少少都需要对数据进行排序。
ES中的排序也比较简单,这里我们简单举两个小例子大家就明白了。
这里我们先来讲名叫James
的球员按照球龄从大到小排序,果然第一妥妥就是我们的小皇帝James,而随后就是球龄稍小个几年的詹姆斯哈登。
这里我们会发现一个问题,球龄10年的球员有两三个。在Mysql中我们是不是有时候会根据多个字段按照优先级去排序,相同的情况下再比较下一个排序字段。
这里我们在球龄的基础上再加上年龄,这样的话哈登因为年轻就排到后面去了。这里我们可以关注几个点,第一是我们的排序是可以多字段去进行优先级排序的;第二就是每个查询出来的文档都会用一个数组把排序字段的值标记出来;第三就是排序后每条文档记录的分数都不会再次计算。